@fungi.computer/parakeet 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +17 -1
  2. package/dist/client-contributions.d.ts +1 -0
  3. package/dist/commands/awaiting-driver.d.ts +20 -0
  4. package/dist/commands/awaiting-driver.js +121 -0
  5. package/dist/commands/builtins.js +6 -4
  6. package/dist/commands/interactions.d.ts +25 -0
  7. package/dist/commands/interactions.js +76 -2
  8. package/dist/commands/run.js +3 -3
  9. package/dist/commands/slash.d.ts +7 -3
  10. package/dist/commands/slash.js +2 -0
  11. package/dist/commands/use-slash-commands.d.ts +13 -0
  12. package/dist/commands/use-slash-commands.js +58 -1
  13. package/dist/components/ChatComposer.d.ts +2 -0
  14. package/dist/components/ChatComposer.js +158 -40
  15. package/dist/components/ClientRows.js +42 -26
  16. package/dist/components/CodeBlock.js +3 -2
  17. package/dist/components/CompletionMenu.d.ts +54 -0
  18. package/dist/components/CompletionMenu.js +52 -0
  19. package/dist/components/ComposerModelKey.d.ts +11 -0
  20. package/dist/components/ComposerModelKey.js +9 -0
  21. package/dist/components/ComposerShell.d.ts +47 -0
  22. package/dist/components/ComposerShell.js +29 -0
  23. package/dist/components/DecisionComposer.d.ts +30 -0
  24. package/dist/components/DecisionComposer.js +145 -0
  25. package/dist/components/InteractionPanel.d.ts +10 -1
  26. package/dist/components/InteractionPanel.js +9 -4
  27. package/dist/components/SlashMenu.d.ts +1 -3
  28. package/dist/components/SlashMenu.js +5 -13
  29. package/dist/components/TimelineItem.d.ts +1 -0
  30. package/dist/components/TimelineItem.js +4 -4
  31. package/dist/components/ToolGroup.d.ts +1 -0
  32. package/dist/components/ToolGroup.js +8 -8
  33. package/dist/conversation-opening.d.ts +1 -1
  34. package/dist/conversation-opening.js +2 -2
  35. package/dist/conversation.d.ts +5 -1
  36. package/dist/conversation.js +84 -29
  37. package/dist/core/approval-decision.d.ts +198 -0
  38. package/dist/core/approval-decision.js +291 -0
  39. package/dist/core/awaiting-answer.d.ts +229 -0
  40. package/dist/core/awaiting-answer.js +303 -0
  41. package/dist/core/build-timeline.js +61 -7
  42. package/dist/core/client-actions.d.ts +1 -0
  43. package/dist/core/client-actions.js +7 -1
  44. package/dist/core/composer-model-name.d.ts +3 -0
  45. package/dist/core/composer-model-name.js +21 -0
  46. package/dist/core/display-text.d.ts +2 -0
  47. package/dist/core/display-text.js +7 -0
  48. package/dist/core/draft-storage.d.ts +6 -0
  49. package/dist/core/draft-storage.js +25 -5
  50. package/dist/core/session-projection.d.ts +1 -0
  51. package/dist/core/session-projection.js +3 -2
  52. package/dist/core/session-query.d.ts +8 -0
  53. package/dist/core/session-query.js +13 -11
  54. package/dist/core/session-view-state.d.ts +2 -4
  55. package/dist/core/session-view-state.js +27 -9
  56. package/dist/core/tool-groups.d.ts +3 -0
  57. package/dist/core/tool-groups.js +37 -1
  58. package/dist/core/tool-view.d.ts +6 -0
  59. package/dist/core/tool-view.js +33 -0
  60. package/dist/core/use-timeline-item-key.d.ts +3 -4
  61. package/dist/core/use-timeline-item-key.js +8 -14
  62. package/dist/headless.d.ts +10 -5
  63. package/dist/headless.js +9 -4
  64. package/dist/index.d.ts +3 -0
  65. package/dist/index.js +3 -0
  66. package/dist/parakeet.d.ts +6 -2
  67. package/dist/parakeet.js +24 -17
  68. package/dist/store/types.d.ts +14 -1
  69. package/dist/styles.css +911 -162
  70. package/dist/use-approval-awaiting.d.ts +22 -0
  71. package/dist/use-approval-awaiting.js +81 -0
  72. package/dist/use-decision.d.ts +20 -0
  73. package/dist/use-decision.js +79 -0
  74. package/docs/api/commands/slash.md +30 -26
  75. package/docs/api/headless.md +917 -54
  76. package/docs/api/index.md +166 -5
  77. package/package.json +17 -8
  78. package/src/completion-menu.css +72 -0
  79. package/src/composer-shell.css +125 -0
  80. package/src/styles.css +99 -53
  81. package/dist/components/WaitingNotice.d.ts +0 -7
  82. package/dist/components/WaitingNotice.js +0 -8
@@ -4,6 +4,7 @@ import { reportFailure } from "../lib/failure.js";
4
4
  const emptyProjection = Object.freeze({
5
5
  observation: "connecting",
6
6
  snapshot: undefined,
7
+ lastReplyAt: undefined,
7
8
  latestPrompt: undefined,
8
9
  });
9
10
  // A watch starts with a bounded page, but its update frames keep arriving for
@@ -60,7 +61,7 @@ async function* observeSession(session, signal) {
60
61
  yield { kind: "snapshot", value: snapshot };
61
62
  }
62
63
  else {
63
- yield { kind: "frame", value: frame };
64
+ yield { kind: "frame", value: frame, snapshot };
64
65
  }
65
66
  }
66
67
  throw new Error("Session watch ended; current activity is unavailable");
@@ -81,21 +82,21 @@ function reduceObservation(projection, observation) {
81
82
  return {
82
83
  observation: "live",
83
84
  snapshot: observation.value,
85
+ lastReplyAt: Date.now(),
84
86
  latestPrompt: latestPrompt(observation.value.messages, projection.latestPrompt),
85
87
  };
86
- if (projection.snapshot === undefined)
87
- return observation.value.kind === "reset"
88
- ? {
89
- observation: "live",
90
- snapshot: observation.value.baseline,
91
- latestPrompt: latestPrompt(observation.value.baseline.messages),
92
- }
93
- : projection;
94
- const snapshot = applySessionFrame(projection.snapshot, observation.value);
88
+ // The generator owns frame application and always delivers its baseline
89
+ // first. The cache only reduces already-projected observations.
90
+ const snapshot = observation.snapshot;
95
91
  return {
96
92
  observation: "live",
97
93
  snapshot,
98
- latestPrompt: latestPrompt(snapshot.messages, projection.latestPrompt),
94
+ lastReplyAt: Date.now(),
95
+ latestPrompt: observation.value.kind === "reset" ||
96
+ (observation.value.kind === "update" &&
97
+ observation.value.messages.some((message) => message.value.role === "user"))
98
+ ? latestPrompt(snapshot.messages, projection.latestPrompt)
99
+ : projection.latestPrompt,
99
100
  };
100
101
  }
101
102
  /** One shared TanStack observation keyed by Shiitake's logical Session address. */
@@ -119,6 +120,7 @@ export function seedSessionQuery(queryClient, session, snapshot) {
119
120
  queryClient.setQueryData(sessionQueryOptions(session).queryKey, {
120
121
  observation: "connecting",
121
122
  snapshot,
123
+ lastReplyAt: undefined,
122
124
  latestPrompt: latestPrompt(snapshot.messages),
123
125
  });
124
126
  }
@@ -56,10 +56,8 @@ export type SessionActivityStatus = "cancelling" | "idle" | "queued" | "retrying
56
56
  /** Decide a Session's view state from its activity, terminals and progress. */
57
57
  export declare function sessionViewState(facts: SessionViewFacts): SessionViewState;
58
58
  export declare function sameRunningWork(left: AgentRunningWork | null, right: AgentRunningWork | null): boolean;
59
- /** Explain a queued Session using its owner's executor fact. */
60
- export declare function queuedSessionLabel(state: Extract<SessionViewState, {
61
- kind: "queued";
62
- }>): string;
59
+ /** Activity only: queued messages belong in the transcript, never in this label. */
60
+ export declare function sessionStateLabel(state: SessionViewState, waitingOnYou?: boolean): "thinking" | "streaming" | "running a tool" | "idle" | "waiting on you" | "failed";
63
61
  /** Whether two view states say the same thing, so a readout can stay stable. */
64
62
  export declare function sameSessionViewState(left: SessionViewState, right: SessionViewState): boolean;
65
63
  /** Coarse activity for a view state: what the composer gates on. */
@@ -67,19 +67,37 @@ export function sameRunningWork(left, right) {
67
67
  if (left === null || right === null)
68
68
  return left === right;
69
69
  if (left.kind === "session" && right.kind === "session")
70
- return (left.sessionId === right.sessionId && left.commandId === right.commandId);
70
+ return (left.sessionId === right.sessionId &&
71
+ left.commandId === right.commandId &&
72
+ left.title === right.title);
71
73
  if (left.kind === "host" && right.kind === "host")
72
74
  return left.lane === right.lane && left.jobId === right.jobId;
73
75
  return false;
74
76
  }
75
- /** Explain a queued Session using its owner's executor fact. */
76
- export function queuedSessionLabel(state) {
77
- const work = state.runningWork;
78
- if (work === null)
79
- return "Queued for host";
80
- return work.kind === "session"
81
- ? `Waiting for Session ${collapsed(work.sessionId, 256)}`
82
- : `Waiting for host job ${collapsed(work.lane, 256)} · ${collapsed(work.jobId, 256)}`;
77
+ /** Activity only: queued messages belong in the transcript, never in this label. */
78
+ export function sessionStateLabel(state, waitingOnYou = false) {
79
+ switch (state.kind) {
80
+ case "queued":
81
+ // Executor occupancy is the only Agent activity fact in a queued view.
82
+ // Its identity/title is not a state and must never become a status label.
83
+ return state.runningWork === null ? "idle" : "thinking";
84
+ case "thinking":
85
+ case "cancelling":
86
+ case "compacting":
87
+ case "retrying":
88
+ return "thinking";
89
+ case "responding":
90
+ return "streaming";
91
+ case "running_tool":
92
+ return "running a tool";
93
+ case "error":
94
+ return "failed";
95
+ case "idle":
96
+ case "waiting_for_you":
97
+ // A completed turn is idle; an actual attention/approval request is
98
+ // what makes the caller show "waiting on you".
99
+ return waitingOnYou ? "waiting on you" : "idle";
100
+ }
83
101
  }
84
102
  /** Whether two view states say the same thing, so a readout can stay stable. */
85
103
  export function sameSessionViewState(left, right) {
@@ -1,5 +1,8 @@
1
1
  import type { ProjectedItem, TimelineItem, ToolGroupItem, ToolItem } from "../store/types.js";
2
2
  import { type ToolCallView } from "./tool-view.js";
3
+ import type { SessionToolOutput } from "@fungi.computer/shiitake/client";
4
+ /** Overlay only running calls; settled rows and their lazy result views stay shared. */
5
+ export declare function overlayToolOutput(items: readonly TimelineItem[], output: readonly SessionToolOutput[]): readonly TimelineItem[];
3
6
  /** Build one group from a non-empty run of consecutive calls. */
4
7
  export declare function toolGroup(tools: readonly [ToolItem, ...ToolItem[]]): ToolGroupItem;
5
8
  /**
@@ -1,5 +1,41 @@
1
1
  import { assistantMessageVisible } from "./assistant-visibility.js";
2
- import { toolCallView } from "./tool-view.js";
2
+ import { liveOutputLines, toolCallView, } from "./tool-view.js";
3
+ /** Overlay only running calls; settled rows and their lazy result views stay shared. */
4
+ export function overlayToolOutput(items, output) {
5
+ if (output.length === 0)
6
+ return items;
7
+ return items.map((item) => {
8
+ if (item.kind !== "tool_group")
9
+ return item;
10
+ const tools = item.tools.map((tool) => {
11
+ const slot = output.find((candidate) => candidate.toolCallId === tool.id);
12
+ if (tool.status !== "running" || slot === undefined)
13
+ return tool;
14
+ return {
15
+ ...tool,
16
+ live: {
17
+ lines: liveOutputLines(slot.tail),
18
+ droppedBytes: slot.droppedBytes,
19
+ revision: slot.revision,
20
+ },
21
+ };
22
+ });
23
+ if (tools.every((tool, index) => tool === item.tools[index]))
24
+ return item;
25
+ const calls = item.calls.map((call, index) => {
26
+ const tool = tools[index];
27
+ return tool === undefined || tool === item.tools[index]
28
+ ? call
29
+ : toolCallView(tool);
30
+ });
31
+ return {
32
+ ...item,
33
+ tools,
34
+ calls,
35
+ active: calls.find((call) => call.id === item.active?.id),
36
+ };
37
+ });
38
+ }
3
39
  const nouns = {
4
40
  code: ["ran", "script", "scripts"],
5
41
  command: ["ran", "command", "commands"],
@@ -173,7 +173,13 @@ export type ToolCallView = Readonly<{
173
173
  error?: string | undefined;
174
174
  /** Expanded body sections, in display order. */
175
175
  sections: readonly ToolCallSection[];
176
+ /** Replaceable output while the call runs. */
177
+ live?: ToolItem["live"];
176
178
  }>;
179
+ /** Neutral lines from a bounded live tail. */
180
+ export declare function liveOutputLines(tail: string): readonly string[];
181
+ /** Live output stays visible when folded, independent of result disclosure. */
182
+ export declare function toolLiveLines(call: ToolCallView, open: boolean): readonly string[];
177
183
  /** Items shown before "more" for excerpts, matches, and output. */
178
184
  export declare const toolPreviewLineLimit = 12;
179
185
  /** Build the renderer-neutral view of one call. */
@@ -4,6 +4,7 @@ import { formatPatch, parsePatch, structuredPatch, } from "diff";
4
4
  import { z } from "zod";
5
5
  import stripAnsi from "strip-ansi";
6
6
  import { reportFailure } from "../lib/failure.js";
7
+ import { sanitizeDisplayText } from "./display-text.js";
7
8
  /** Shared tool status meaning, used by the browser and native presentations. */
8
9
  export function toolStatusTone(status) {
9
10
  if (status === "error")
@@ -27,6 +28,37 @@ export const toolKindSigils = {
27
28
  /** Artwork for search results. */
28
29
  search: "search",
29
30
  };
31
+ /** Neutral lines from a bounded live tail. */
32
+ export function liveOutputLines(tail) {
33
+ if (tail === "")
34
+ return [];
35
+ // Replacement dominance counts decoded code points, not rendered graphemes.
36
+ const characters = Array.from(tail);
37
+ if (characters.filter((character) => character === "\ufffd").length >
38
+ characters.length / 2)
39
+ return [
40
+ `[binary output, ${String(new TextEncoder().encode(tail).byteLength)} bytes]`,
41
+ ];
42
+ return stripAnsi(tail)
43
+ .replace(/\r\n/gu, "\n")
44
+ .replace(/\n$/u, "")
45
+ .split("\n")
46
+ .map((line) => sanitizeDisplayText(line.slice(line.lastIndexOf("\r") + 1)));
47
+ }
48
+ /** Live output stays visible when folded, independent of result disclosure. */
49
+ export function toolLiveLines(call, open) {
50
+ const live = call.live;
51
+ if (live === undefined)
52
+ return [];
53
+ if (!open)
54
+ return live.lines.slice(-6);
55
+ return [
56
+ ...(live.droppedBytes === 0
57
+ ? []
58
+ : [`… ${String(Math.ceil(live.droppedBytes / 1024))} KiB earlier`]),
59
+ ...live.lines,
60
+ ];
61
+ }
30
62
  /** Items shown before "more" for excerpts, matches, and output. */
31
63
  export const toolPreviewLineLimit = 12;
32
64
  /** Diff lines shown before "more". */
@@ -600,6 +632,7 @@ export function toolCallView(item) {
600
632
  kind,
601
633
  label: target.label,
602
634
  name: item.name,
635
+ live: item.status === "running" ? item.live : undefined,
603
636
  paths: target.paths,
604
637
  get sections() {
605
638
  return getDetails().sections;
@@ -1,8 +1,7 @@
1
1
  import type { TimelineItem } from "../store/types.js";
2
2
  /**
3
- * The virtualizer's row-key callback. Text-only progress replaces tail items
4
- * but not their keys, so the callback keeps its identity, and the virtualizer
5
- * its measurements, until the settled history or the order or identity of the
6
- * tail rows changes. Settled rows are keyed from the settled list alone.
3
+ * The virtualizer's row-key callback. Progress replaces row payloads, including
4
+ * live overlays on settled calls, but not row keys. Keep the callback and its
5
+ * measurements until the order or identity of the combined rows changes.
7
6
  */
8
7
  export declare function useTimelineItemKey(settled: readonly TimelineItem[], tail: readonly TimelineItem[]): (index: number) => string | number;
@@ -9,21 +9,15 @@ function sameKeys(a, b) {
9
9
  return a.length === b.length && a.every((key, index) => key === b[index]);
10
10
  }
11
11
  /**
12
- * The virtualizer's row-key callback. Text-only progress replaces tail items
13
- * but not their keys, so the callback keeps its identity, and the virtualizer
14
- * its measurements, until the settled history or the order or identity of the
15
- * tail rows changes. Settled rows are keyed from the settled list alone.
12
+ * The virtualizer's row-key callback. Progress replaces row payloads, including
13
+ * live overlays on settled calls, but not row keys. Keep the callback and its
14
+ * measurements until the order or identity of the combined rows changes.
16
15
  */
17
16
  export function useTimelineItemKey(settled, tail) {
18
- const next = tail.map(rowKey);
17
+ const next = [...settled, ...tail].map(rowKey);
19
18
  const [previous, setPrevious] = useState(next);
20
- const tailKeys = sameKeys(previous, next) ? previous : next;
21
- if (tailKeys !== previous)
22
- setPrevious(tailKeys);
23
- return useCallback((index) => {
24
- const item = settled[index];
25
- if (item !== undefined)
26
- return rowKey(item);
27
- return tailKeys[index - settled.length] ?? index;
28
- }, [settled, tailKeys]);
19
+ const keys = sameKeys(previous, next) ? previous : next;
20
+ if (keys !== previous)
21
+ setPrevious(keys);
22
+ return useCallback((index) => keys[index] ?? index, [keys]);
29
23
  }
@@ -1,12 +1,13 @@
1
1
  export { formatModelIdentity, formatModelWithLevel, } from "./core/model-identity.js";
2
2
  export type { QueuedRecallState, QueuedRecallOutcome, } from "./core/queued-recall.js";
3
3
  export type { ParakeetDraftIdentity, ParakeetDraftStorage, } from "./core/draft-storage.js";
4
+ export { sanitizeDisplayText } from "./core/display-text.js";
4
5
  export { formatContextUsage, formatTokenCount } from "./core/format-context.js";
5
6
  export { formatWhen, oneLine } from "./core/picker-text.js";
6
7
  export { providerChoices } from "./provider-choices.js";
7
- export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useStatus, useTimeline, type ComposerController, type ParakeetDraftCheckpoint, type ParakeetModel, type ParakeetObservation, type ParakeetReconnectOutcome, type ParakeetStatus, type ParakeetSessionViewState, type ParakeetUsage, type StatusController, type TimelineController, } from "./conversation.js";
8
- export { sessionViewState, queuedSessionLabel, sessionActivityStatus, type SessionActivityStatus, type SessionViewFacts, type SessionViewState as ParakeetOwnerViewState, } from "./core/session-view-state.js";
9
- export { mergeTerminals } from "./core/session-projection.js";
8
+ export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useLastSessionReplyAt, useStatus, useTimeline, type ComposerController, type ParakeetDraftCheckpoint, type ParakeetModel, type ParakeetObservation, type ParakeetReconnectOutcome, type ParakeetStatus, type ParakeetSessionViewState, type ParakeetUsage, type StatusController, type TimelineController, } from "./conversation.js";
9
+ export { sessionViewState, sessionStateLabel, sessionActivityStatus, type SessionActivityStatus, type SessionViewFacts, type SessionViewState as ParakeetOwnerViewState, } from "./core/session-view-state.js";
10
+ export { applySessionFrame, mergeTerminals, } from "./core/session-projection.js";
10
11
  export { type SessionActivity, seedSessionQuery, sessionQueryOptions, subscribeSessionActivity, } from "./core/session-query.js";
11
12
  export { accountCommandDefinitions, contributeAccountCommands, loginRegistry, type AccountCommandOptions, type AccountOwner, type ProviderAccess, type ModelAccountContext, type LoginHost, type LoginMethod, type LoginMethodKind, type LoginPolicy, type LoginProvider, type LoginScope, } from "./account-commands.js";
12
13
  export { contributeModelCommands, sessionModelCommandDefinitions, type SessionModelChoice, type SessionModelCommandOptions, } from "./model-commands.js";
@@ -15,7 +16,7 @@ export { contributeSessionCommands, type ParakeetSessionCommandOptions, } from "
15
16
  export { completeSlash, fuzzyMatch, keyBindings, keyLabel, parseSlashDraft, slashRegistry, type FuzzyMatch, type KeyBindingRow, type SlashCommand, type SlashCommandItem, type SlashCompletion, type SlashParse, type SlashWordItem, } from "./commands/slash.js";
16
17
  export { composerHints, fitHints, formatHints, type ComposerHintContext, type ComposerKeys, type KeyHint, } from "./commands/hints.js";
17
18
  export { isCancelledOutcome, runSlashDraft, type SlashRuntime, } from "./commands/run.js";
18
- export { createParakeetInteractions, type ParakeetInteractions, type ParakeetInteractionState, type ParakeetNotice, type ParakeetOpenInteraction, type ParakeetToast, } from "./commands/interactions.js";
19
+ export { createParakeetInteractions, type ParakeetInteractions, type ParakeetAwaiting, type ParakeetInteractionState, type ParakeetNotice, type ParakeetOpenInteraction, type ParakeetToast, } from "./commands/interactions.js";
19
20
  export { parakeetBuiltinCommands, type ParakeetBuiltinOptions, } from "./commands/builtins.js";
20
21
  export { advertisedCommands, type AdvertisedCatalogError, type AdvertisedCommands, } from "./commands/advertised.js";
21
22
  export { useSlashCommands, type ParakeetCommandRuntime, type SlashCommandController, type SlashCommandOptions, } from "./commands/use-slash-commands.js";
@@ -23,9 +24,13 @@ export type { ParakeetSession, ParakeetSessionCommand, ParakeetPromptResult, } f
23
24
  export type { AssistantMessageItem as ParakeetAssistantMessageItem, TimelineItem as ParakeetTimelineItem, ToolGroupItem as ParakeetToolGroupItem, ToolItem as ParakeetToolItem, UserMessageItem as ParakeetUserMessageItem, } from "./store/types.js";
24
25
  export type { ToolArguments as ParakeetToolArguments } from "./core/tool-contract.js";
25
26
  export { useMarkdown, type MarkdownBlock, type MarkdownInline, } from "./markdown.js";
26
- export { formatToolDuration, toolCallView, toolKindSigils, toolStatusTone, type ToolCallSection as ParakeetToolCallSection, type ToolCallView as ParakeetToolCallView, type ToolDiffFile as ParakeetToolDiffFile, type ToolDiffHunk as ParakeetToolDiffHunk, type ToolDiffLine as ParakeetToolDiffLine, type ToolDiffRow as ParakeetToolDiffRow, type ToolExcerptLine as ParakeetToolExcerptLine, type ToolKind as ParakeetToolKind, type ToolMatch as ParakeetToolMatch, type ToolOutputLine as ParakeetToolOutputLine, } from "./core/tool-view.js";
27
+ export { formatToolDuration, toolCallView, toolKindSigils, toolLiveLines, toolStatusTone, type ToolCallSection as ParakeetToolCallSection, type ToolCallView as ParakeetToolCallView, type ToolDiffFile as ParakeetToolDiffFile, type ToolDiffHunk as ParakeetToolDiffHunk, type ToolDiffLine as ParakeetToolDiffLine, type ToolDiffRow as ParakeetToolDiffRow, type ToolExcerptLine as ParakeetToolExcerptLine, type ToolKind as ParakeetToolKind, type ToolMatch as ParakeetToolMatch, type ToolOutputLine as ParakeetToolOutputLine, } from "./core/tool-view.js";
27
28
  export { assistantMessageVisible } from "./core/assistant-visibility.js";
28
29
  export { disclosureOpen, initialToolDisclosure, toggleAllDisclosure, toggleDisclosure, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, type ToolDisclosureState, } from "./core/tool-groups.js";
29
30
  export { useClientActions } from "./client-contributions.js";
31
+ export { awaitingQueue, awaitingTakeover, decisionView, decisionViewText, initialDecisionState, reduceDecision, type AwaitingItem, type AwaitingKind, type AwaitingQueue, type DecisionEffect, type DecisionIntent, type DecisionOutcome, type DecisionPhase, type DecisionPrompt, type DecisionRowView, type DecisionState, type DecisionView, } from "./core/awaiting-answer.js";
32
+ export { useDecision, type DecisionController } from "./use-decision.js";
33
+ export { useApprovalAwaiting } from "./use-approval-awaiting.js";
34
+ export { approvalCardCopy, approvalRefusal, askApproval, type ApprovalAbandoned, type ApprovalAnswered, type ApprovalAnswerInput, type ApprovalAnswerResult, type ApprovalCard, type ApprovalContent, type ApprovalDecision, type ApprovalEntry, type ApprovalFacts, type ApprovalOutcome, type ApprovalRefused, type ApprovalReview, type ApprovalSource, } from "./core/approval-decision.js";
30
35
  export { placeClientTimeline, type PlacedClientCell, } from "./core/client-timeline.js";
31
36
  export type { ClientContribution } from "./core/client-contributions.js";
package/dist/headless.js CHANGED
@@ -1,10 +1,11 @@
1
1
  export { formatModelIdentity, formatModelWithLevel, } from "./core/model-identity.js";
2
+ export { sanitizeDisplayText } from "./core/display-text.js";
2
3
  export { formatContextUsage, formatTokenCount } from "./core/format-context.js";
3
4
  export { formatWhen, oneLine } from "./core/picker-text.js";
4
5
  export { providerChoices } from "./provider-choices.js";
5
- export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useStatus, useTimeline, } from "./conversation.js";
6
- export { sessionViewState, queuedSessionLabel, sessionActivityStatus, } from "./core/session-view-state.js";
7
- export { mergeTerminals } from "./core/session-projection.js";
6
+ export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useLastSessionReplyAt, useStatus, useTimeline, } from "./conversation.js";
7
+ export { sessionViewState, sessionStateLabel, sessionActivityStatus, } from "./core/session-view-state.js";
8
+ export { applySessionFrame, mergeTerminals, } from "./core/session-projection.js";
8
9
  export { seedSessionQuery, sessionQueryOptions, subscribeSessionActivity, } from "./core/session-query.js";
9
10
  export { accountCommandDefinitions, contributeAccountCommands, loginRegistry, } from "./account-commands.js";
10
11
  export { contributeModelCommands, sessionModelCommandDefinitions, } from "./model-commands.js";
@@ -18,8 +19,12 @@ export { parakeetBuiltinCommands, } from "./commands/builtins.js";
18
19
  export { advertisedCommands, } from "./commands/advertised.js";
19
20
  export { useSlashCommands, } from "./commands/use-slash-commands.js";
20
21
  export { useMarkdown, } from "./markdown.js";
21
- export { formatToolDuration, toolCallView, toolKindSigils, toolStatusTone, } from "./core/tool-view.js";
22
+ export { formatToolDuration, toolCallView, toolKindSigils, toolLiveLines, toolStatusTone, } from "./core/tool-view.js";
22
23
  export { assistantMessageVisible } from "./core/assistant-visibility.js";
23
24
  export { disclosureOpen, initialToolDisclosure, toggleAllDisclosure, toggleDisclosure, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, } from "./core/tool-groups.js";
24
25
  export { useClientActions } from "./client-contributions.js";
26
+ export { awaitingQueue, awaitingTakeover, decisionView, decisionViewText, initialDecisionState, reduceDecision, } from "./core/awaiting-answer.js";
27
+ export { useDecision } from "./use-decision.js";
28
+ export { useApprovalAwaiting } from "./use-approval-awaiting.js";
29
+ export { approvalCardCopy, approvalRefusal, askApproval, } from "./core/approval-decision.js";
25
30
  export { placeClientTimeline, } from "./core/client-timeline.js";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { ParakeetProvider, useComposer, useStatus, useTimeline, useMarkdown, disclosureOpen, formatToolDuration, formatModelIdentity, formatModelWithLevel, formatContextUsage, formatTokenCount, initialToolDisclosure, toggleAllDisclosure, toggleDisclosure, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, } from "./headless.js";
2
2
  export { Parakeet, ParakeetView } from "./parakeet.js";
3
+ export { ComposerModelKey, type ComposerModelKeyProps, } from "./components/ComposerModelKey.js";
4
+ export { composerSendState } from "./components/ComposerShell.js";
5
+ export { ComposerShell, useComposerTextareaSize, type ComposerShellProps, type ComposerShellRow, } from "./components/ComposerShell.js";
3
6
  export type { ParakeetVisitor } from "./parakeet.js";
4
7
  export { InteractionPanel, type InteractionPanelProps, } from "./components/InteractionPanel.js";
5
8
  export type { QueuedRecallState, QueuedRecallOutcome, ParakeetAssistantMessageItem, ComposerController, ParakeetDraftCheckpoint, ParakeetDraftIdentity, ParakeetDraftStorage, ParakeetModel, ParakeetSession, ParakeetStatus, ParakeetTimelineItem, ParakeetToolArguments, ParakeetToolCallSection, ParakeetToolCallView, ParakeetToolDiffFile, ParakeetToolDiffHunk, ParakeetToolDiffLine, ParakeetToolDiffRow, ParakeetToolExcerptLine, ParakeetToolMatch, ParakeetToolOutputLine, ParakeetToolGroupItem, ParakeetToolItem, ParakeetToolKind, ToolDisclosureState, ParakeetUsage, ParakeetUserMessageItem, StatusController, TimelineController, MarkdownBlock, MarkdownInline, } from "./headless.js";
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export { ParakeetProvider, useComposer, useStatus, useTimeline, useMarkdown, disclosureOpen, formatToolDuration, formatModelIdentity, formatModelWithLevel, formatContextUsage, formatTokenCount, initialToolDisclosure, toggleAllDisclosure, toggleDisclosure, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, } from "./headless.js";
2
2
  export { Parakeet, ParakeetView } from "./parakeet.js";
3
+ export { ComposerModelKey, } from "./components/ComposerModelKey.js";
4
+ export { composerSendState } from "./components/ComposerShell.js";
5
+ export { ComposerShell, useComposerTextareaSize, } from "./components/ComposerShell.js";
3
6
  export { InteractionPanel, } from "./components/InteractionPanel.js";
4
7
  export * from "./agent-head.js";
@@ -31,6 +31,8 @@ export type ParakeetProps = Readonly<{
31
31
  composerActionVariant?: "primary" | "outline" | undefined;
32
32
  /** Optional caller-owned actions for a completed tool result. */
33
33
  renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
34
+ /** Optional host claim: true hides this item's image parts from the tool body because the host shows them. */
35
+ claimsToolMedia?: ((item: ToolItem) => boolean) | undefined;
34
36
  /** Optional caller-owned attachment below a settled assistant reply. */
35
37
  renderAssistantAttachment?: ((text: string) => ReactNode) | undefined;
36
38
  /** Optional display text; attachments still receive the original reply. */
@@ -54,6 +56,8 @@ export type ParakeetViewProps = Readonly<{
54
56
  composerActionVariant?: "primary" | "outline" | undefined;
55
57
  /** Optional caller-owned actions for a completed tool result. */
56
58
  renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
59
+ /** Optional host claim: true hides this item's image parts from the tool body because the host shows them. */
60
+ claimsToolMedia?: ((item: ToolItem) => boolean) | undefined;
57
61
  /** Optional caller-owned attachment below a settled assistant reply. */
58
62
  renderAssistantAttachment?: ((text: string) => ReactNode) | undefined;
59
63
  /** Optional display text; attachments still receive the original reply. */
@@ -72,6 +76,6 @@ export type ParakeetViewProps = Readonly<{
72
76
  commands?: ParakeetCommandRuntime | undefined;
73
77
  }>;
74
78
  /** Render the retained conversation surface within one Parakeet provider. */
75
- export declare function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }: ParakeetViewProps): ReactElement;
79
+ export declare function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }: ParakeetViewProps): ReactElement;
76
80
  /** Standalone typed composer and exact-message timeline. */
77
- export declare function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy, composerActionVariant, empty, session, clients, renderToolActions, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }: ParakeetProps): ReactElement;
81
+ export declare function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy, composerActionVariant, empty, session, clients, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }: ParakeetProps): ReactElement;
package/dist/parakeet.js CHANGED
@@ -6,6 +6,7 @@ import { createWhistle } from "@fungi.computer/whistle";
6
6
  import { useVirtualizer } from "@tanstack/react-virtual";
7
7
  import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
8
8
  import { ParakeetProvider, useComposer, useClientContributions, useDismissFailedPrompt, useStatus, useTimeline, } from "./conversation.js";
9
+ import { awaitingTakeover } from "./core/awaiting-answer.js";
9
10
  import { initialToolDisclosure, toggleDisclosure } from "./core/tool-groups.js";
10
11
  import { authorDisplayNames, } from "./core/author-labels.js";
11
12
  import { useTimelineItemKey } from "./core/use-timeline-item-key.js";
@@ -14,12 +15,11 @@ import { ClientRows } from "./components/ClientRows.js";
14
15
  import { useClientActions } from "./client-contributions.js";
15
16
  import { placeClientTimeline } from "./core/client-timeline.js";
16
17
  import { InteractionPanel } from "./components/InteractionPanel.js";
17
- import { WaitingNotice } from "./components/WaitingNotice.js";
18
18
  import { useSlashCommands, } from "./commands/use-slash-commands.js";
19
19
  import { TimelineItem } from "./components/TimelineItem.js";
20
20
  import { MarkdownPolicyContext } from "./components/MarkdownRenderer.js";
21
21
  /** Render the retained conversation surface within one Parakeet provider. */
22
- export function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }) {
22
+ export function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }) {
23
23
  const textareaRef = useRef(null);
24
24
  const [ownCommands] = useState(() => createWhistle());
25
25
  const slash = useSlashCommands({
@@ -31,6 +31,9 @@ export function ParakeetView({ disambiguateAuthors, visitor, composerActionVaria
31
31
  });
32
32
  const clientActions = useClientActions(commands ?? ownCommands);
33
33
  const contributions = useClientContributions();
34
+ const { draft } = useComposer();
35
+ // The decision in the composer is not also a footer row.
36
+ const answering = awaitingTakeover(slash.awaiting, draft)?.key;
34
37
  const timelineController = useTimeline();
35
38
  const placed = useMemo(() => placeClientTimeline(contributions, timelineController.commandAnchors, timelineController.positionAnchors), [
36
39
  contributions,
@@ -43,7 +46,11 @@ export function ParakeetView({ disambiguateAuthors, visitor, composerActionVaria
43
46
  : null;
44
47
  if (anchor instanceof HTMLAnchorElement && onLink?.(anchor.href))
45
48
  event.preventDefault();
46
- }, children: [_jsxs("div", { className: "flex h-full w-full flex-col border-r border-base-300", children: [_jsx(ConversationTimeline, { disambiguateAuthors: visitor === undefined && disambiguateAuthors === true, clientActions: clientActions, onOpen: onOpen, focusAfterJump: textareaRef, empty: empty, renderToolActions: renderToolActions, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText }), contributions.some(({ value }) => value.panel.length > 0) ? (_jsx("section", { "aria-label": "Session extension controls", className: "w-full shrink-0 border-t border-base-300 bg-base-100", children: contributions.map(({ entryId, value }) => {
49
+ }, children: [_jsxs("div", { className: "flex h-full w-full flex-col border-r border-base-300", children: [_jsx(ConversationTimeline, { disambiguateAuthors: visitor === undefined && disambiguateAuthors === true, clientActions: clientActions, onOpen: onOpen, focusAfterJump: textareaRef, empty: empty, renderToolActions: renderToolActions, claimsToolMedia: claimsToolMedia, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText }), contributions.some(({ entryId, value }) => value.panel.some((row) => JSON.stringify([entryId, row.id]) !== answering)) ? (_jsx("section", { "aria-label": "Session extension controls", className: "w-full shrink-0 border-t border-base-300 bg-base-100", children: contributions.map(({ entryId, value: contribution }) => {
50
+ const value = {
51
+ ...contribution,
52
+ panel: contribution.panel.filter((row) => JSON.stringify([entryId, row.id]) !== answering),
53
+ };
47
54
  // An extension's retained-event notice and null-anchor rows
48
55
  // join its own footer entry; one with no panel row here keeps
49
56
  // none of this, rather than growing a section just to show it.
@@ -92,7 +99,7 @@ function ConversationInteraction({ slash, }) {
92
99
  slash.store.close();
93
100
  }, children: _jsx(DialogContent, { className: "w-[min(32rem,calc(100vw-2rem))] rounded-box bg-base-100 p-md shadow-lg", keepMounted: false, children: open === undefined ? null : (_jsx(InteractionPanel, { host: open.host, onClose: slash.store.close }, open.key)) }) }));
94
101
  }
95
- function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focusAfterJump, empty, renderToolActions, renderAssistantAttachment, formatAssistantText, }) {
102
+ function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focusAfterJump, empty, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, }) {
96
103
  const dismissFailed = useDismissFailedPrompt();
97
104
  const timelineController = useTimeline();
98
105
  const cancelQueued = useCallback((commandId) => {
@@ -160,7 +167,7 @@ function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focu
160
167
  timelineController.jumpToLatest();
161
168
  timeline.scrollToEnd({ behavior: "smooth" });
162
169
  }, [timeline, timelineController]);
163
- return (_jsxs("div", { className: "prose relative min-h-0 flex-1", children: [_jsx("div", { "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: sessionReadout(sessionStatus.status, sessionStatus.viewState) }), sessionStatus.observation.status === "unavailable" ? (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-1 text-xs text-dim", role: "status", children: [_jsx("span", { children: sessionStatus.observation.reason }), _jsx("button", { className: "underline", onClick: () => void sessionStatus.reconnect(), type: "button", children: "Reconnect" })] })) : null, _jsx("div", { className: "h-full min-h-0 overflow-y-auto", "data-chat-scroller": true, ref: scrollerRef, onScroll: (event) => {
170
+ return (_jsxs("div", { className: "prose relative min-h-0 flex-1", children: [_jsx("div", { "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: sessionReadout(sessionStatus.status, sessionStatus.viewState) }), sessionStatus.observation.status === "unavailable" ? (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-1 text-xs text-dim", role: "status", children: [_jsx("span", { children: sessionStatus.observation.reason }), _jsx(Button, { variant: "outline", size: "xs", onClick: () => void sessionStatus.reconnect(), type: "button", children: "Reconnect" })] })) : null, _jsx("div", { className: "h-full min-h-0 overflow-y-auto", "data-chat-scroller": true, ref: scrollerRef, onScroll: (event) => {
164
171
  const top = event.currentTarget.scrollTop;
165
172
  if (previousScrollTop.current !== undefined &&
166
173
  top < previousScrollTop.current)
@@ -209,24 +216,23 @@ function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focu
209
216
  item.author.name,
210
217
  },
211
218
  }
212
- : item, onDismissFailed: dismissFailed, onCancelQueued: cancelQueued, cancellingQueued: timelineController.cancellingQueued, onToggleTool: toggleTool, renderToolActions: renderToolActions, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, toolDisclosure: toolDisclosure }), placed.after.get(item.id)?.map(({ entryId, cell }) => (_jsx(ClientRows, { entryId: entryId, rows: cell.rows, actions: clientActions, onOpen: onOpen }, `${entryId}:${cell.id}`)))] }, virtualItem.key));
219
+ : item, onDismissFailed: dismissFailed, onCancelQueued: cancelQueued, cancellingQueued: timelineController.cancellingQueued, onToggleTool: toggleTool, renderToolActions: renderToolActions, claimsToolMedia: claimsToolMedia, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, toolDisclosure: toolDisclosure }), placed.after.get(item.id)?.map(({ entryId, cell }) => (_jsx(ClientRows, { entryId: entryId, rows: cell.rows, actions: clientActions, onOpen: onOpen }, `${entryId}:${cell.id}`)))] }, virtualItem.key));
213
220
  }) })) : before.length > 0 ? (before) : hasMoreHistory ? null : ((empty ?? (_jsx("div", { className: "flex h-full items-center justify-center text-center", children: _jsxs("div", { children: [_jsx("p", { className: "mb-1 text-xs text-base-content", children: "Ready" }), _jsx("p", { className: "text-[10px] text-dim", children: "What would you like to build?" })] }) })))) }), count > 0 ? (_jsx(ScrollToBottomBlob, { direction: "forward", unread: unread, focusAfterJump: focusAfterJump, onJump: jumpToBottom, scrollerRef: scrollerRef })) : null] }));
214
221
  }
215
- /** The owner's queued state and independently contributed waiting controls. */
222
+ /** Independently contributed waiting controls; queued messages stay in the transcript. */
216
223
  function ConversationWaiting({ clientActions, }) {
217
224
  const { viewState } = useStatus();
218
225
  const contributions = useClientContributions();
219
226
  const extensionWaiting = viewState.kind === "idle"
220
227
  ? contributions.find(({ value }) => value.waiting !== null)
221
228
  : undefined;
222
- const waiting = viewState.kind === "queued" ? viewState : null;
223
- return (_jsxs(_Fragment, { children: [extensionWaiting?.value.waiting === undefined ||
224
- extensionWaiting.value.waiting === null ? null : (_jsxs("div", { className: "flex items-center gap-2 px-3 text-xs", children: [_jsx("span", { children: extensionWaiting.value.waiting.summary }), _jsx(Button, { size: "xs", variant: "outline", disabled: !clientActions.enabled ||
225
- clientActions.state.get(extensionWaiting.value.waiting.cancel)
226
- ?.pending === true, onClick: () => {
227
- if (extensionWaiting.value.waiting !== null)
228
- void clientActions.execute(extensionWaiting.entryId, extensionWaiting.value.waiting.cancel, true);
229
- }, children: extensionWaiting.value.waiting.cancel.label })] })), _jsx(WaitingNotice, { waiting: waiting })] }));
229
+ return (_jsx(_Fragment, { children: extensionWaiting?.value.waiting === undefined ||
230
+ extensionWaiting.value.waiting === null ? null : (_jsxs("div", { className: "flex items-center gap-2 px-3 text-xs", children: [_jsx("span", { children: extensionWaiting.value.waiting.summary }), _jsx(Button, { size: "xs", variant: "outline", disabled: !clientActions.enabled ||
231
+ clientActions.state.get(extensionWaiting.value.waiting.cancel)
232
+ ?.pending === true, onClick: () => {
233
+ if (extensionWaiting.value.waiting !== null)
234
+ void clientActions.execute(extensionWaiting.entryId, extensionWaiting.value.waiting.cancel, true);
235
+ }, children: extensionWaiting.value.waiting.cancel.label })] })) }));
230
236
  }
231
237
  function ConversationComposer({ visitor, textareaRef, composerActionVariant, slash, }) {
232
238
  const composer = useComposer();
@@ -242,6 +248,7 @@ function ConversationComposer({ visitor, textareaRef, composerActionVariant, sla
242
248
  }, [composer.cancel]);
243
249
  const composerProps = {
244
250
  queuedRecall: visitor === undefined ? composer : undefined,
251
+ onImagesChange: visitor === undefined ? composer.setImages : undefined,
245
252
  statusChips: contributions
246
253
  .flatMap(({ value }) => value.chips)
247
254
  .sort((left, right) => left.order - right.order)
@@ -270,6 +277,6 @@ function ConversationComposer({ visitor, textareaRef, composerActionVariant, sla
270
277
  return _jsx(ChatComposer, { ...composerProps });
271
278
  }
272
279
  /** Standalone typed composer and exact-message timeline. */
273
- export function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy = "session", composerActionVariant, empty, session, clients, renderToolActions, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }) {
274
- return (_jsx(MarkdownPolicyContext.Provider, { value: markdownPolicy, children: _jsx(ParakeetProvider, { session: session, clients: clients, authorOf: authorOf, children: _jsx(ParakeetView, { disambiguateAuthors: disambiguateAuthors, visitor: visitor, composerActionVariant: composerActionVariant, empty: empty, renderToolActions: renderToolActions, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, commands: commands, onLink: onLink, onOpen: onOpen }) }) }));
280
+ export function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy = "session", composerActionVariant, empty, session, clients, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }) {
281
+ return (_jsx(MarkdownPolicyContext.Provider, { value: markdownPolicy, children: _jsx(ParakeetProvider, { session: session, clients: clients, authorOf: authorOf, children: _jsx(ParakeetView, { disambiguateAuthors: disambiguateAuthors, visitor: visitor, composerActionVariant: composerActionVariant, empty: empty, renderToolActions: renderToolActions, claimsToolMedia: claimsToolMedia, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, commands: commands, onLink: onLink, onOpen: onOpen }) }) }));
275
282
  }
@@ -51,7 +51,7 @@ export type LocalPromptAdmission = Readonly<{
51
51
  /** Existing Session delivery intent. */
52
52
  delivery: PromptDelivery;
53
53
  /** Current local admission result. */
54
- status: "admitting" | "accepted" | "failed";
54
+ status: "queued-local" | "admitting" | "accepted" | "failed";
55
55
  /** User-presentable admission error when status is failed. */
56
56
  error?: string;
57
57
  }>;
@@ -73,6 +73,10 @@ export type AssistantMessageItem = Readonly<{
73
73
  text: string;
74
74
  /** Pi's private failed-turn error text; never a terminal or delegation diagnostic. */
75
75
  providerError?: string;
76
+ /** Short assistant output retained before a provider failure, not provider prose. */
77
+ errorDetail?: string;
78
+ /** Consecutive failures with the same visible headline and detail. */
79
+ repeatCount?: number;
76
80
  /** Assistant reasoning text, when supplied by the model. */
77
81
  thinking?: string;
78
82
  /** Usage carried by the exact assistant message. */
@@ -101,6 +105,15 @@ export type ToolItem = Readonly<{
101
105
  startedAt?: number;
102
106
  /** Owner-confirmed activity or result; unknown when neither is available. */
103
107
  status: "completed" | "error" | "running" | "unknown";
108
+ /** Replaceable display-only output, never part of the durable result. */
109
+ live?: Readonly<{
110
+ /** Neutral output lines. */
111
+ lines: readonly string[];
112
+ /** Raw producer bytes omitted before this tail. */
113
+ droppedBytes: number;
114
+ /** Owner-issued output revision. */
115
+ revision: number;
116
+ }>;
104
117
  }>;
105
118
  /**
106
119
  * One maximal run of consecutive tool calls between conversation rows. A lone