summaryrefslogtreecommitdiffhomepage
path: root/packages/cli/src/render.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/render.ts')
-rw-r--r--packages/cli/src/render.ts49
1 files changed, 48 insertions, 1 deletions
diff --git a/packages/cli/src/render.ts b/packages/cli/src/render.ts
index 1853963..9f25dd3 100644
--- a/packages/cli/src/render.ts
+++ b/packages/cli/src/render.ts
@@ -5,7 +5,7 @@
* Consumers write these to process.stdout / process.stderr.
*/
-import type { AgentEvent } from "@dispatch/transport-contract";
+import type { AgentEvent, ConversationMeta } from "@dispatch/transport-contract";
interface RenderOpts {
readonly showReasoning: boolean;
@@ -43,3 +43,50 @@ export function renderEvent(e: AgentEvent, opts: RenderOpts): RenderOutput | und
return undefined;
}
}
+
+/**
+ * Accumulate ALL `text-delta` events' `delta` text into one string — the full
+ * assistant reply assembled from a turn's event stream. Pure: input → output,
+ * no I/O. Returns the empty string when the stream had no text deltas.
+ */
+export function extractLastText(events: readonly AgentEvent[]): string {
+ let text = "";
+ for (const e of events) {
+ if (e.type === "text-delta") {
+ text += e.delta;
+ }
+ }
+ return text;
+}
+
+/**
+ * Render an epoch-ms timestamp as a short relative phrase ("just now", "5m ago",
+ * "3h ago", "2d ago") or, past a week, the `YYYY-MM-DD` date. `now` is injected
+ * (clock is an outermost edge) so the function is pure and testable.
+ */
+export function formatRelativeTime(epochMs: number, now: number): string {
+ const delta = now - epochMs;
+ if (delta < 60_000) return "just now";
+ const minutes = Math.floor(delta / 60_000);
+ if (minutes < 60) return `${minutes}m ago`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+ const days = Math.floor(hours / 24);
+ if (days < 7) return `${days}d ago`;
+ return new Date(epochMs).toISOString().slice(0, 10);
+}
+
+/**
+ * Format the conversation list as a table — one row per conversation:
+ * `shortId | title | lastActivity (relative)`. Empty input yields the empty
+ * string. `now` is injected for `formatRelativeTime` (pure + testable).
+ */
+export function formatConversationList(
+ conversations: readonly ConversationMeta[],
+ now: number,
+): string {
+ if (conversations.length === 0) return "";
+ return conversations
+ .map((c) => `${c.id.slice(0, 8)} | ${c.title} | ${formatRelativeTime(c.lastActivityAt, now)}`)
+ .join("\n");
+}