@tangle-network/agent-app 0.43.41 → 0.43.42

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 (47) hide show
  1. package/dist/assistant/index.d.ts +2 -1
  2. package/dist/assistant/index.js +3 -2
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chat-routes/index.d.ts +17 -3
  5. package/dist/chat-routes/index.js +91 -6
  6. package/dist/chat-routes/index.js.map +1 -1
  7. package/dist/chat-store/index.d.ts +4 -3
  8. package/dist/chat-store/index.js +5 -1
  9. package/dist/chat-store/index.js.map +1 -1
  10. package/dist/{chunk-Y4QHNQ75.js → chunk-6KVH5SP5.js} +110 -6
  11. package/dist/chunk-6KVH5SP5.js.map +1 -0
  12. package/dist/{chunk-XYWCGFII.js → chunk-B5JD3DXD.js} +48 -2
  13. package/dist/chunk-B5JD3DXD.js.map +1 -0
  14. package/dist/{chunk-I2R2XT4M.js → chunk-I2ATYB7R.js} +18 -2
  15. package/dist/chunk-I2ATYB7R.js.map +1 -0
  16. package/dist/chunk-SIXYZ2FB.js +101 -0
  17. package/dist/chunk-SIXYZ2FB.js.map +1 -0
  18. package/dist/{chunk-4TXDD6P2.js → chunk-XAWFPMAR.js} +38 -3
  19. package/dist/chunk-XAWFPMAR.js.map +1 -0
  20. package/dist/{chunk-KM766NN3.js → chunk-ZLHK25C3.js} +1766 -1223
  21. package/dist/chunk-ZLHK25C3.js.map +1 -0
  22. package/dist/chunk-ZU5GNSOJ.js +920 -0
  23. package/dist/chunk-ZU5GNSOJ.js.map +1 -0
  24. package/dist/{contract-DYbTzEDf.d.ts → contract-KfqJh_au.d.ts} +23 -2
  25. package/dist/design-canvas-react/index.js +4 -4
  26. package/dist/durable-chat/index.d.ts +419 -0
  27. package/dist/durable-chat/index.js +59 -0
  28. package/dist/durable-chat/index.js.map +1 -0
  29. package/dist/index.d.ts +5 -3
  30. package/dist/index.js +123 -45
  31. package/dist/interactions/index.d.ts +58 -3
  32. package/dist/interactions/index.js +7 -3
  33. package/dist/{parts-BcbitSNp.d.ts → parts-DjX0RRTS.d.ts} +9 -3
  34. package/dist/plans/index.d.ts +69 -0
  35. package/dist/plans/index.js +21 -0
  36. package/dist/plans/index.js.map +1 -0
  37. package/dist/stream/index.js +3 -1
  38. package/dist/teams/index.js +9 -9
  39. package/dist/teams/invitations-api.js +3 -3
  40. package/dist/web-react/index.d.ts +252 -93
  41. package/dist/web-react/index.js +32 -3
  42. package/package.json +11 -1
  43. package/dist/chunk-4TXDD6P2.js.map +0 -1
  44. package/dist/chunk-I2R2XT4M.js.map +0 -1
  45. package/dist/chunk-KM766NN3.js.map +0 -1
  46. package/dist/chunk-XYWCGFII.js.map +0 -1
  47. package/dist/chunk-Y4QHNQ75.js.map +0 -1
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { C as ChatInteraction, a as ChatInteractionField, b as ChatInteractionStatus, c as ChatSelectField, g as InteractionCancelData, i as InteractionRequestWire } from '../contract-DYbTzEDf.js';
4
- export { d as ComposerAnswerDelivery, I as INTERACTION_CANCEL_EVENT, e as INTERACTION_EVENT, f as INTERACTION_RESOLVED_EVENT, h as InteractionPersistedPart, N as NoticeKind, j as NoticePersistedPart, P as ParseInteractionResult, k as canTransitionInteractionStatus, l as cancelStatusFor, m as composerAnswerData, n as composerAnswerDeliveries, o as dedupeQuestionInteractionsByContent, p as fieldAcceptsFreeText, q as interactionFromWireRequest, r as interactionPartKey, s as interactionToPersistedPart, t as isRenderableInteractionKind, u as isSafeInteractionFieldKey, v as isTerminalInteractionStatus, w as noticePart, x as noticePartKey, y as parseInteractionCancel, z as parseInteractionRequest, A as persistedPartToInteraction, B as questionInteractionContentSignature } from '../contract-DYbTzEDf.js';
3
+ import { a as ChatInteractionField, h as InteractionAnswers, b as ChatInteractionStatus, C as ChatInteraction, i as InteractionCancelData, c as ChatSelectField, k as InteractionRequestWire } from '../contract-KfqJh_au.js';
4
+ export { d as ComposerAnswerDelivery, I as INTERACTION_CANCEL_EVENT, e as INTERACTION_EVENT, f as INTERACTION_RESOLVED_EVENT, g as InteractionAnswerValue, j as InteractionPersistedPart, N as NoticeKind, l as NoticePersistedPart, P as ParseInteractionAnswersResult, m as ParseInteractionResult, n as canTransitionInteractionStatus, o as cancelStatusFor, p as composerAnswerData, q as composerAnswerDeliveries, r as dedupeQuestionInteractionsByContent, s as fieldAcceptsFreeText, t as interactionFromWireRequest, u as interactionPartKey, v as interactionToPersistedPart, w as isRenderableInteractionKind, x as isSafeInteractionFieldKey, y as isTerminalInteractionStatus, z as noticePart, A as noticePartKey, B as parseInteractionAnswers, D as parseInteractionCancel, E as parseInteractionRequest, F as persistedPartToInteraction, G as questionInteractionContentSignature, H as stampInteractionAnswers } from '../contract-KfqJh_au.js';
5
+ import { ChatPlan } from '../plans/index.js';
5
6
  import { InteractionData } from '@tangle-network/agent-interface';
6
7
  export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
7
8
  import { j as FileMention } from '../file-index-Bw_IQE_G.js';
@@ -12,6 +13,189 @@ export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse,
12
13
  import { CatalogModel } from '../catalog/index.js';
13
14
  import { Harness } from '../harness/index.js';
14
15
 
16
+ type DurablePlanDecision = 'approved' | 'rejected';
17
+ /** Stable authority receipt for the follow-up turn dispatched by a plan
18
+ * decision. Consumers must make `attachFollowUp` idempotent by `receiptId`;
19
+ * reload and retry deliberately invoke it again. */
20
+ interface DurablePlanFollowUpReceipt {
21
+ receiptId: string;
22
+ planId: string;
23
+ revision: number;
24
+ turnId: string;
25
+ state: string;
26
+ }
27
+ interface DurablePlanDecisionResult {
28
+ plan: ChatPlan;
29
+ followUp?: DurablePlanFollowUpReceipt;
30
+ idempotent: boolean;
31
+ projectionPending?: boolean;
32
+ effectPending?: boolean;
33
+ }
34
+ interface DurablePlanDecisionInput {
35
+ planId: string;
36
+ revision: number;
37
+ decision: DurablePlanDecision;
38
+ feedback?: string;
39
+ }
40
+ interface DurablePlanCurrentInput {
41
+ planId: string;
42
+ revision?: number;
43
+ }
44
+ interface DurablePlanDecisionClient {
45
+ current: (input: DurablePlanCurrentInput) => Promise<DurablePlanDecisionResult>;
46
+ decide: (input: DurablePlanDecisionInput) => Promise<DurablePlanDecisionResult>;
47
+ }
48
+ declare class DurablePlanClientError extends Error {
49
+ readonly status: number;
50
+ readonly code?: string | undefined;
51
+ readonly currentPlan?: ChatPlan | undefined;
52
+ constructor(message: string, status: number, code?: string | undefined, currentPlan?: ChatPlan | undefined);
53
+ }
54
+ interface DurablePlanDecisionClientOptions {
55
+ url: string | ((input: DurablePlanCurrentInput | DurablePlanDecisionInput) => string);
56
+ body?: Record<string, unknown> | ((input: DurablePlanDecisionInput) => Record<string, unknown>);
57
+ fetchImpl?: typeof fetch;
58
+ }
59
+ /** Browser client for the shared durable-plan route. The route URL and all
60
+ * product routing fields are injected; workspace/session identity is still
61
+ * resolved and authorized on the server. */
62
+ declare function createDurablePlanDecisionClient(options: DurablePlanDecisionClientOptions): DurablePlanDecisionClient;
63
+ interface UseDurablePlanFlowOptions {
64
+ plan: ChatPlan;
65
+ client: DurablePlanDecisionClient;
66
+ /** Must be idempotent by receipt.receiptId. */
67
+ attachFollowUp?: (receipt: DurablePlanFollowUpReceipt) => Promise<void> | void;
68
+ onUpdated?: (plan: ChatPlan) => void;
69
+ }
70
+ interface UseDurablePlanFlowResult {
71
+ plan: ChatPlan;
72
+ deciding: DurablePlanDecision | null;
73
+ restoring: boolean;
74
+ error: string | null;
75
+ decide: (decision: DurablePlanDecision, feedback?: string) => Promise<DurablePlanDecisionResult | null>;
76
+ restore: () => Promise<DurablePlanDecisionResult | null>;
77
+ clearError: () => void;
78
+ }
79
+ /** Shared plan decision controller. It coalesces only concurrent attachment
80
+ * attempts; a later retry/restore calls the consumer's idempotent transport
81
+ * again so a lost response cannot strand an already-dispatched follow-up. */
82
+ declare function useDurablePlanFlow(options: UseDurablePlanFlowOptions): UseDurablePlanFlowResult;
83
+
84
+ /**
85
+ * Shared answer-building + submit plumbing for the interaction cards
86
+ * (question, plan). Client-safe, no React: cards own their state, this owns
87
+ * the wire. Lifted from the gtm-agent fork (the most fix-absorbed of the three
88
+ * product copies), including the 30s submit timeout that keeps a dead route
89
+ * from wedging a card in "Submitting…".
90
+ */
91
+
92
+ /** Status-badge labels for an interaction card. `cancelled`/`expired` read the
93
+ * same across cards; each card supplies its own verbs for the other states
94
+ * (a question is answered/declined; a plan is approved/rejected). */
95
+ declare function interactionStatusLabels(labels: {
96
+ pending: string;
97
+ answered: string;
98
+ declined: string;
99
+ }): Record<ChatInteractionStatus, string>;
100
+ /** Terminal-state notes for an interaction card. The expiry/withdrawal lines
101
+ * share one shape around the card's noun ("question"/"plan"); any extra notes
102
+ * (e.g. a plan's `declined` revision line) merge on top. */
103
+ declare function interactionTerminalNotes(noun: string, extra?: Partial<Record<ChatInteractionStatus, string>>): Partial<Record<ChatInteractionStatus, string>>;
104
+ type FieldValues = Record<string, {
105
+ selected?: string[];
106
+ text?: string;
107
+ custom?: string;
108
+ }>;
109
+ /** Converts acknowledged, persisted answers back into the local field state
110
+ * consumed by the shared cards. Persisted values are authoritative: this is
111
+ * intentionally used only when an interaction carries `answers`, never to
112
+ * guess an answer from the absence of an outstanding sidecar ask. */
113
+ declare function fieldValuesFromAnswers(fields: ChatInteractionField[], answers: InteractionAnswers | undefined): FieldValues;
114
+ /** The submitted value for one field, or null when it has no answer yet. */
115
+ declare function fieldAnswer(field: ChatInteractionField, values: FieldValues): InteractionData[string] | null;
116
+ /** All required fields answered → the respond payload; else null (not
117
+ * submittable yet). Optional unanswered fields are omitted. */
118
+ declare function buildAnswerData(fields: ChatInteractionField[], values: FieldValues): InteractionData | null;
119
+ declare function isLateAnswerableStatus(status: ChatInteractionStatus): boolean;
120
+ /** Secrets must never leave the sidecar answer channel for the visible chat
121
+ * transcript, so a secret-bearing ask cannot be late-answered. */
122
+ declare function hasSecretField(fields: ChatInteractionField[]): boolean;
123
+ /** Renders the late answer as a self-contained chat message: the original
124
+ * question, its context, and the user's answer(s). */
125
+ declare function lateAnswerMessage(interaction: ChatInteraction, data: InteractionData): string;
126
+ declare const INTERACTION_SUBMIT_TIMEOUT_MS = 30000;
127
+ declare const INTERACTION_SUBMIT_TIMEOUT_MESSAGE = "Could not reach the agent. Try again.";
128
+ /** One card submission: which ask, resolved how, with what answers. */
129
+ interface InteractionAnswerSubmission {
130
+ id: string;
131
+ outcome: 'accepted' | 'declined';
132
+ data?: InteractionData;
133
+ }
134
+ type InteractionSubmitResult = {
135
+ ok: true;
136
+ } | {
137
+ ok: false;
138
+ expired: boolean;
139
+ message: string;
140
+ };
141
+ /** The cards' only side-effect seam: POST one resolution, report the normalized
142
+ * outcome. Products bind their route URL + routing fields (workspaceId,
143
+ * threadId, session path param) via `createInteractionAnswerSubmitter` or a
144
+ * hand-rolled implementation. */
145
+ type SubmitInteractionAnswer = (submission: InteractionAnswerSubmission) => Promise<InteractionSubmitResult>;
146
+ /** Extracts the most specific error message a route returned. */
147
+ declare function responseErrorMessage(res: Response): Promise<{
148
+ code?: string;
149
+ message: string;
150
+ }>;
151
+ interface InteractionAnswerSubmitterOptions {
152
+ /** The product's answer route (the POST half of `createInteractionAnswerRoute`).
153
+ * A function when the URL carries the session (e.g. `/api/sessions/${id}/interactions`). */
154
+ url: string | ((submission: InteractionAnswerSubmission) => string);
155
+ /** Extra routing fields merged into the POST body (e.g. workspaceId, threadId). */
156
+ body?: Record<string, unknown> | ((submission: InteractionAnswerSubmission) => Record<string, unknown>);
157
+ timeoutMs?: number;
158
+ fetchImpl?: typeof fetch;
159
+ }
160
+ /**
161
+ * Builds the `SubmitInteractionAnswer` the cards consume: POSTs
162
+ * `{ ...routingFields, id, outcome, data? }` with an abortable timeout and
163
+ * normalizes the outcome. `expired` is the 410 path — the ask is gone
164
+ * (answered elsewhere, timed out, or the session moved on) and the card must
165
+ * flip to the same dead state a cancel event produces.
166
+ */
167
+ declare function createInteractionAnswerSubmitter(options: InteractionAnswerSubmitterOptions): SubmitInteractionAnswer;
168
+
169
+ type DurableChatCard = {
170
+ kind: 'plan';
171
+ key: string;
172
+ plan: ChatPlan;
173
+ } | {
174
+ kind: 'interaction';
175
+ key: string;
176
+ interaction: ChatInteraction;
177
+ };
178
+ /** Converts persisted/live parts to canonical cards. Legacy interaction-plan
179
+ * cards are suppressed only when their raw part carries an explicit planId and
180
+ * revision matching a durable plan. Identical markdown alone is never proof. */
181
+ declare function durableChatCardsFromParts(parts: Array<Record<string, unknown>>): DurableChatCard[];
182
+ interface DurableChatCardsProps {
183
+ parts: Array<Record<string, unknown>>;
184
+ canWrite: boolean;
185
+ submitInteraction: SubmitInteractionAnswer;
186
+ decidePlan: (plan: ChatPlan, decision: DurablePlanDecision, feedback?: string) => Promise<DurablePlanDecisionResult | null>;
187
+ decidingPlan?: (plan: ChatPlan) => DurablePlanDecision | null;
188
+ planError?: (plan: ChatPlan) => string | null;
189
+ onInteractionResolved?: (id: string, status: Exclude<ChatInteraction['status'], 'pending'>, answers?: InteractionAnswers) => void;
190
+ onLateAnswer?: (message: string) => boolean | void | Promise<boolean | void>;
191
+ renderMarkdown?: (markdown: string) => ReactNode;
192
+ className?: string;
193
+ }
194
+ /** Ready-to-embed canonical question/plan card lane for persisted assistant
195
+ * parts. Apps inject transport and styling callbacks instead of rebuilding the
196
+ * lifecycle/render switch. */
197
+ declare function DurableChatCards({ parts, canWrite, submitInteraction, decidePlan, decidingPlan, planError, onInteractionResolved, onLateAnswer, renderMarkdown, className, }: DurableChatCardsProps): react.JSX.Element | null;
198
+
15
199
  /**
16
200
  * Client-side chat-stream consumption — the NDJSON parse loop every agent
17
201
  * app's chat UI hand-rolls (and breaks). Normalizes the three line shapes the
@@ -64,6 +248,10 @@ interface ChatStreamCallbacks {
64
248
  * user", not "model working". Optional — a consumer that doesn't wire it
65
249
  * parses the same stream unchanged. */
66
250
  onInteraction?: (interaction: ChatInteraction) => void;
251
+ /** A terminal withdrawal/timeout for a previously emitted interaction. */
252
+ onInteractionCancel?: (cancel: InteractionCancelData) => void;
253
+ /** A durable-plan snapshot from any plan lifecycle event. */
254
+ onPlan?: (plan: ChatPlan) => void;
67
255
  }
68
256
  interface ConsumeChatStreamResult {
69
257
  turnId: string | null;
@@ -166,86 +354,6 @@ interface ChatComposerProps {
166
354
  }
167
355
  declare function ChatComposer({ onSend, onSendParts, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, seed, onSeedApplied, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, accept, dropTitle, dropDescription, focusShortcut, sendLabel, className, }: ChatComposerProps): react.JSX.Element;
168
356
 
169
- /**
170
- * Shared answer-building + submit plumbing for the interaction cards
171
- * (question, plan). Client-safe, no React: cards own their state, this owns
172
- * the wire. Lifted from the gtm-agent fork (the most fix-absorbed of the three
173
- * product copies), including the 30s submit timeout that keeps a dead route
174
- * from wedging a card in "Submitting…".
175
- */
176
-
177
- /** Status-badge labels for an interaction card. `cancelled`/`expired` read the
178
- * same across cards; each card supplies its own verbs for the other states
179
- * (a question is answered/declined; a plan is approved/rejected). */
180
- declare function interactionStatusLabels(labels: {
181
- pending: string;
182
- answered: string;
183
- declined: string;
184
- }): Record<ChatInteractionStatus, string>;
185
- /** Terminal-state notes for an interaction card. The expiry/withdrawal lines
186
- * share one shape around the card's noun ("question"/"plan"); any extra notes
187
- * (e.g. a plan's `declined` revision line) merge on top. */
188
- declare function interactionTerminalNotes(noun: string, extra?: Partial<Record<ChatInteractionStatus, string>>): Partial<Record<ChatInteractionStatus, string>>;
189
- type FieldValues = Record<string, {
190
- selected?: string[];
191
- text?: string;
192
- custom?: string;
193
- }>;
194
- /** The submitted value for one field, or null when it has no answer yet. */
195
- declare function fieldAnswer(field: ChatInteractionField, values: FieldValues): InteractionData[string] | null;
196
- /** All required fields answered → the respond payload; else null (not
197
- * submittable yet). Optional unanswered fields are omitted. */
198
- declare function buildAnswerData(fields: ChatInteractionField[], values: FieldValues): InteractionData | null;
199
- declare function isLateAnswerableStatus(status: ChatInteractionStatus): boolean;
200
- /** Secrets must never leave the sidecar answer channel for the visible chat
201
- * transcript, so a secret-bearing ask cannot be late-answered. */
202
- declare function hasSecretField(fields: ChatInteractionField[]): boolean;
203
- /** Renders the late answer as a self-contained chat message: the original
204
- * question, its context, and the user's answer(s). */
205
- declare function lateAnswerMessage(interaction: ChatInteraction, data: InteractionData): string;
206
- declare const INTERACTION_SUBMIT_TIMEOUT_MS = 30000;
207
- declare const INTERACTION_SUBMIT_TIMEOUT_MESSAGE = "Could not reach the agent. Try again.";
208
- /** One card submission: which ask, resolved how, with what answers. */
209
- interface InteractionAnswerSubmission {
210
- id: string;
211
- outcome: 'accepted' | 'declined';
212
- data?: InteractionData;
213
- }
214
- type InteractionSubmitResult = {
215
- ok: true;
216
- } | {
217
- ok: false;
218
- expired: boolean;
219
- message: string;
220
- };
221
- /** The cards' only side-effect seam: POST one resolution, report the normalized
222
- * outcome. Products bind their route URL + routing fields (workspaceId,
223
- * threadId, session path param) via `createInteractionAnswerSubmitter` or a
224
- * hand-rolled implementation. */
225
- type SubmitInteractionAnswer = (submission: InteractionAnswerSubmission) => Promise<InteractionSubmitResult>;
226
- /** Extracts the most specific error message a route returned. */
227
- declare function responseErrorMessage(res: Response): Promise<{
228
- code?: string;
229
- message: string;
230
- }>;
231
- interface InteractionAnswerSubmitterOptions {
232
- /** The product's answer route (the POST half of `createInteractionAnswerRoute`).
233
- * A function when the URL carries the session (e.g. `/api/sessions/${id}/interactions`). */
234
- url: string | ((submission: InteractionAnswerSubmission) => string);
235
- /** Extra routing fields merged into the POST body (e.g. workspaceId, threadId). */
236
- body?: Record<string, unknown> | ((submission: InteractionAnswerSubmission) => Record<string, unknown>);
237
- timeoutMs?: number;
238
- fetchImpl?: typeof fetch;
239
- }
240
- /**
241
- * Builds the `SubmitInteractionAnswer` the cards consume: POSTs
242
- * `{ ...routingFields, id, outcome, data? }` with an abortable timeout and
243
- * normalizes the outcome. `expired` is the 410 path — the ask is gone
244
- * (answered elsewhere, timed out, or the session moved on) and the card must
245
- * flip to the same dead state a cancel event produces.
246
- */
247
- declare function createInteractionAnswerSubmitter(options: InteractionAnswerSubmitterOptions): SubmitInteractionAnswer;
248
-
249
357
  type InteractionBadgeVariant = 'outline' | 'default' | 'destructive';
250
358
  declare function InteractionBadge({ variant, children }: {
251
359
  variant: InteractionBadgeVariant;
@@ -282,7 +390,7 @@ interface InteractionQuestionCardProps {
282
390
  submitAnswer: SubmitInteractionAnswer;
283
391
  /** Fired when this card resolves locally (answered, or discovered expired
284
392
  * via a 410) so the stream/route state stays in sync. */
285
- onResolved?: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>) => void;
393
+ onResolved?: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>, answers?: ChatInteraction['answers']) => void;
286
394
  /** Delivers a late answer (the ask expired/was withdrawn) as a fresh chat
287
395
  * turn. Return/resolve `false` when the send was rejected so the card stays
288
396
  * retryable. Omit to hide the late-answer affordance entirely. */
@@ -307,6 +415,34 @@ interface InteractionPlanCardProps {
307
415
  }
308
416
  declare function InteractionPlanCard({ interaction, canWrite, submitAnswer, onResolved, renderMarkdown, className, }: InteractionPlanCardProps): react.JSX.Element;
309
417
 
418
+ interface DurablePlanCardProps {
419
+ plan: ChatPlan;
420
+ canWrite: boolean;
421
+ decide: (decision: DurablePlanDecision, feedback?: string) => Promise<DurablePlanDecisionResult | null>;
422
+ deciding?: DurablePlanDecision | null;
423
+ error?: string | null;
424
+ renderMarkdown?: (markdown: string) => ReactNode;
425
+ className?: string;
426
+ }
427
+ declare function DurablePlanCard({ plan, canWrite, decide, deciding, error, renderMarkdown, className, }: DurablePlanCardProps): react.JSX.Element;
428
+
429
+ interface InteractionAttemptStore {
430
+ get(interactionId: string, submissionSignature: string): string | null;
431
+ set(interactionId: string, submissionSignature: string, attemptKey: string): void;
432
+ delete(interactionId: string, submissionSignature: string): void;
433
+ }
434
+ declare function createSessionInteractionAttemptStore(storage: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>, namespace?: string): InteractionAttemptStore;
435
+ declare function createMemoryInteractionAttemptStore(): InteractionAttemptStore;
436
+ declare function interactionSubmissionSignature(submission: InteractionAnswerSubmission): string;
437
+ interface DurableInteractionAnswerSubmitterOptions extends InteractionAnswerSubmitterOptions {
438
+ attempts: InteractionAttemptStore;
439
+ createAttemptKey?: () => string;
440
+ }
441
+ /** Answer submitter for a durable interaction route. One opaque attempt key is
442
+ * retained for an ambiguous transport/5xx result and reused after reload. A
443
+ * changed answer has a different signature and therefore a new attempt. */
444
+ declare function createDurableInteractionAnswerSubmitter(options: DurableInteractionAnswerSubmitterOptions): SubmitInteractionAnswer;
445
+
310
446
  /**
311
447
  * useChatInteractions — the interaction-state wiring every consumer of
312
448
  * `ChatStreamCallbacks.onInteraction` re-implements: an id-keyed,
@@ -336,14 +472,28 @@ declare function upsertChatInteraction(list: ChatInteraction[], interaction: Cha
336
472
  * `expired` (reason:"timeout") or `cancelled`. */
337
473
  declare function cancelChatInteraction(list: ChatInteraction[], cancel: InteractionCancelData): ChatInteraction[];
338
474
  /** Marks one ask resolved locally (the card's `onResolved`). Forward-only. */
339
- declare function resolveChatInteraction(list: ChatInteraction[], id: string, status: Exclude<ChatInteractionStatus, 'pending'>): ChatInteraction[];
475
+ declare function resolveChatInteraction(list: ChatInteraction[], id: string, status: Exclude<ChatInteractionStatus, 'pending'>, answers?: InteractionAnswers): ChatInteraction[];
340
476
  /** Settles every still-pending ask when the turn ends: `answered` for a turn
341
477
  * that completed cleanly, `expired` for one that failed. */
342
478
  declare function terminalizePendingChatInteractions(list: ChatInteraction[], status: Extract<ChatInteractionStatus, 'answered' | 'expired'>): ChatInteraction[];
343
- /** Reload restore from the answer route's GET list: every listed ask is
344
- * outstanding (upserted pending), and every previously-pending ask the sidecar
345
- * no longer lists was resolved while we were away (settled as answered). */
346
- declare function restoreChatInteractions(list: ChatInteraction[], outstanding: InteractionRequestWire[]): ChatInteraction[];
479
+ type ChatInteractionRestoreMode = 'legacy' | 'durable';
480
+ interface RestoreChatInteractionsOptions {
481
+ /**
482
+ * `legacy` settles pending asks absent from the sidecar list as answered,
483
+ * preserving the pre-durable restore contract. `durable` leaves them
484
+ * pending because absence is ambiguous until `hydrateChatInteractions`
485
+ * applies the durable projection.
486
+ */
487
+ mode?: ChatInteractionRestoreMode;
488
+ }
489
+ /** Reload restore from the answer route's GET list. Legacy consumers retain
490
+ * the historical absence→answered behavior; durable consumers opt into the
491
+ * ambiguity-preserving mode and apply terminal parts through `hydrate`. */
492
+ declare function restoreChatInteractions(list: ChatInteraction[], outstanding: InteractionRequestWire[], options?: RestoreChatInteractionsOptions): ChatInteraction[];
493
+ /** Applies transcript/state-store projections after reload. Terminal state and
494
+ * acknowledged answer values enrich an existing pending card without relying
495
+ * on the sidecar's outstanding-list absence. */
496
+ declare function hydrateChatInteractions(list: ChatInteraction[], persisted: ChatInteraction[]): ChatInteraction[];
347
497
  interface UseChatInteractionsResult {
348
498
  /** All known interactions, insertion-ordered. */
349
499
  interactions: ChatInteraction[];
@@ -354,15 +504,18 @@ interface UseChatInteractionsResult {
354
504
  /** Wire to `interaction.cancel` events. */
355
505
  applyCancel: (cancel: InteractionCancelData) => void;
356
506
  /** Wire to the cards' `onResolved`. */
357
- markResolved: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>) => void;
507
+ markResolved: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>, answers?: InteractionAnswers) => void;
358
508
  /** Wire to the answer route's GET list after a reload/reconnect. */
359
- restore: (outstanding: InteractionRequestWire[]) => void;
509
+ restore: (outstanding: InteractionRequestWire[], options?: RestoreChatInteractionsOptions) => void;
510
+ /** Apply durable transcript/state projections after a reload. */
511
+ hydrate: (persisted: ChatInteraction[]) => void;
360
512
  /** Settle still-pending asks when the turn ends. */
361
513
  terminalizePending: (status: Extract<ChatInteractionStatus, 'answered' | 'expired'>) => void;
362
514
  /** Drop everything (thread switch). */
363
515
  reset: () => void;
364
516
  }
365
- declare function useChatInteractions(): UseChatInteractionsResult;
517
+ type UseChatInteractionsOptions = RestoreChatInteractionsOptions;
518
+ declare function useChatInteractions(options?: UseChatInteractionsOptions): UseChatInteractionsResult;
366
519
 
367
520
  /**
368
521
  * `useFileMentions` — the glue a host passes straight into `AgentComposer`'s
@@ -801,6 +954,9 @@ interface ChatUiMessage extends ChatMessageMetrics {
801
954
  * present and non-empty it is rendered in place of `content` + `toolCalls`;
802
955
  * both remain the fallback for producers that don't segment a turn. */
803
956
  segments?: ChatMessageSegment[];
957
+ /** Persisted assistant parts. When `ChatMessages.durableCards` is supplied,
958
+ * shared plan/question cards render directly from these projections. */
959
+ parts?: Array<Record<string, unknown>>;
804
960
  }
805
961
  interface ChatMessagesProps {
806
962
  messages: ChatUiMessage[];
@@ -810,6 +966,9 @@ interface ChatMessagesProps {
810
966
  renderMarkdown?: (content: string) => ReactNode;
811
967
  /** Extra per-message content (artifacts, custom panels) appended after the body. */
812
968
  renderExtras?: (message: ChatUiMessage) => ReactNode;
969
+ /** Canonical durable plan/question card wiring. Apps inject only transport,
970
+ * access, and optional visual callbacks; card selection/dedupe stays shared. */
971
+ durableCards?: Omit<DurableChatCardsProps, 'parts' | 'renderMarkdown'>;
813
972
  userLabel?: string;
814
973
  agentLabel?: string;
815
974
  /** Render the trailing "agent is thinking" row. */
@@ -882,6 +1041,6 @@ declare function useThinkingSeconds(active: boolean): number;
882
1041
  * model id, tokens/sec, and cost, plus a collapsible thinking section and
883
1042
  * tool-call chips.
884
1043
  */
885
- declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, emptyState, header, }: ChatMessagesProps): react.JSX.Element;
1044
+ declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, durableCards, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, emptyState, header, }: ChatMessagesProps): react.JSX.Element;
886
1045
 
887
- export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, CatalogModel, ChatComposer, type ChatComposerProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, ChatInteraction, ChatInteractionField, ChatInteractionStatus, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, ChatSelectField, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ComposerFile, type ComposerFilePart, type ComposerMentionProp, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, DEFAULT_MENTION_EMPTY_TEXT, DEFAULT_MENTION_LIMIT, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FileMention, FlowWaterfall, type FlowWaterfallProps, INDEX_REFRESH_AFTER_MS, INTERACTION_SUBMIT_TIMEOUT_MESSAGE, INTERACTION_SUBMIT_TIMEOUT_MS, InteractionActionButton, type InteractionAnswerSubmission, type InteractionAnswerSubmitterOptions, InteractionBadge, type InteractionBadgeVariant, InteractionCancelData, InteractionPlanCard, type InteractionPlanCardProps, InteractionQuestionCard, type InteractionQuestionCardProps, InteractionRequestWire, type InteractionSubmitResult, type MentionItem, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, QuestionOptionList, type QuestionOptionListProps, RunDrillIn, type RunDrillInProps, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type SubmitInteractionAnswer, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseChatInteractionsResult, type UseFileMentionsOptions, type UseFileMentionsResult, type WaterfallRow, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createInteractionAnswerSubmitter, dispatchChatStreamLine, fieldAnswer, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, interactionStatusLabels, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, mergeActivityPages, nextRevealCount, pendingApprovalOf, rankFileMentions, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, streamChatTurn, terminalizePendingChatInteractions, upsertChatInteraction, useChatInteractions, useFileMentions, usePending, usePopover, useSmoothText, useThinkingSeconds, waterfallLayout };
1046
+ export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, CatalogModel, ChatComposer, type ChatComposerProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, ChatInteraction, ChatInteractionField, type ChatInteractionRestoreMode, ChatInteractionStatus, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, ChatSelectField, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ComposerFile, type ComposerFilePart, type ComposerMentionProp, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, DEFAULT_MENTION_EMPTY_TEXT, DEFAULT_MENTION_LIMIT, type DurableChatCard, DurableChatCards, type DurableChatCardsProps, type DurableInteractionAnswerSubmitterOptions, DurablePlanCard, type DurablePlanCardProps, DurablePlanClientError, type DurablePlanCurrentInput, type DurablePlanDecision, type DurablePlanDecisionClient, type DurablePlanDecisionClientOptions, type DurablePlanDecisionInput, type DurablePlanDecisionResult, type DurablePlanFollowUpReceipt, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FileMention, FlowWaterfall, type FlowWaterfallProps, INDEX_REFRESH_AFTER_MS, INTERACTION_SUBMIT_TIMEOUT_MESSAGE, INTERACTION_SUBMIT_TIMEOUT_MS, InteractionActionButton, type InteractionAnswerSubmission, type InteractionAnswerSubmitterOptions, InteractionAnswers, type InteractionAttemptStore, InteractionBadge, type InteractionBadgeVariant, InteractionCancelData, InteractionPlanCard, type InteractionPlanCardProps, InteractionQuestionCard, type InteractionQuestionCardProps, InteractionRequestWire, type InteractionSubmitResult, type MentionItem, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, QuestionOptionList, type QuestionOptionListProps, type RestoreChatInteractionsOptions, RunDrillIn, type RunDrillInProps, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type SubmitInteractionAnswer, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseChatInteractionsOptions, type UseChatInteractionsResult, type UseDurablePlanFlowOptions, type UseDurablePlanFlowResult, type UseFileMentionsOptions, type UseFileMentionsResult, type WaterfallRow, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createDurableInteractionAnswerSubmitter, createDurablePlanDecisionClient, createInteractionAnswerSubmitter, createMemoryInteractionAttemptStore, createSessionInteractionAttemptStore, dispatchChatStreamLine, durableChatCardsFromParts, fieldAnswer, fieldValuesFromAnswers, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, hydrateChatInteractions, interactionStatusLabels, interactionSubmissionSignature, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, mergeActivityPages, nextRevealCount, pendingApprovalOf, rankFileMentions, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, streamChatTurn, terminalizePendingChatInteractions, upsertChatInteraction, useChatInteractions, useDurablePlanFlow, useFileMentions, usePending, usePopover, useSmoothText, useThinkingSeconds, waterfallLayout };
@@ -7,6 +7,9 @@ import {
7
7
  DEFAULT_EFFORT_LEVELS,
8
8
  DEFAULT_MENTION_EMPTY_TEXT,
9
9
  DEFAULT_MENTION_LIMIT,
10
+ DurableChatCards,
11
+ DurablePlanCard,
12
+ DurablePlanClientError,
10
13
  EffortPicker,
11
14
  FlowWaterfall,
12
15
  INDEX_REFRESH_AFTER_MS,
@@ -26,15 +29,23 @@ import {
26
29
  buildAnswerData,
27
30
  cancelChatInteraction,
28
31
  consumeChatStream,
32
+ createDurableInteractionAnswerSubmitter,
33
+ createDurablePlanDecisionClient,
29
34
  createInteractionAnswerSubmitter,
35
+ createMemoryInteractionAttemptStore,
36
+ createSessionInteractionAttemptStore,
30
37
  dispatchChatStreamLine,
38
+ durableChatCardsFromParts,
31
39
  fieldAnswer,
40
+ fieldValuesFromAnswers,
32
41
  formatActivityCost,
33
42
  formatActivityDuration,
34
43
  formatModelCost,
35
44
  formatTokensPerSecond,
36
45
  hasSecretField,
46
+ hydrateChatInteractions,
37
47
  interactionStatusLabels,
48
+ interactionSubmissionSignature,
38
49
  interactionTerminalNotes,
39
50
  isLateAnswerableStatus,
40
51
  lateAnswerMessage,
@@ -49,13 +60,14 @@ import {
49
60
  terminalizePendingChatInteractions,
50
61
  upsertChatInteraction,
51
62
  useChatInteractions,
63
+ useDurablePlanFlow,
52
64
  useFileMentions,
53
65
  usePending,
54
66
  usePopover,
55
67
  useSmoothText,
56
68
  useThinkingSeconds,
57
69
  waterfallLayout
58
- } from "../chunk-KM766NN3.js";
70
+ } from "../chunk-ZLHK25C3.js";
59
71
  import {
60
72
  tabTerminalConnectionId,
61
73
  useSandboxTerminalConnection
@@ -85,11 +97,14 @@ import {
85
97
  isTerminalInteractionStatus,
86
98
  noticePart,
87
99
  noticePartKey,
100
+ parseInteractionAnswers,
88
101
  parseInteractionCancel,
89
102
  parseInteractionRequest,
90
103
  persistedPartToInteraction,
91
- questionInteractionContentSignature
92
- } from "../chunk-4TXDD6P2.js";
104
+ questionInteractionContentSignature,
105
+ stampInteractionAnswers
106
+ } from "../chunk-XAWFPMAR.js";
107
+ import "../chunk-SIXYZ2FB.js";
93
108
  import "../chunk-E7QYOOON.js";
94
109
  export {
95
110
  AgentActivityPanel,
@@ -100,6 +115,9 @@ export {
100
115
  DEFAULT_EFFORT_LEVELS,
101
116
  DEFAULT_MENTION_EMPTY_TEXT,
102
117
  DEFAULT_MENTION_LIMIT,
118
+ DurableChatCards,
119
+ DurablePlanCard,
120
+ DurablePlanClientError,
103
121
  EffortPicker,
104
122
  FlowWaterfall,
105
123
  INDEX_REFRESH_AFTER_MS,
@@ -128,20 +146,28 @@ export {
128
146
  composerAnswerData,
129
147
  composerAnswerDeliveries,
130
148
  consumeChatStream,
149
+ createDurableInteractionAnswerSubmitter,
150
+ createDurablePlanDecisionClient,
131
151
  createInteractionAnswerSubmitter,
152
+ createMemoryInteractionAttemptStore,
153
+ createSessionInteractionAttemptStore,
132
154
  dedupeQuestionInteractionsByContent,
133
155
  dispatchChatStreamLine,
156
+ durableChatCardsFromParts,
134
157
  fieldAcceptsFreeText,
135
158
  fieldAnswer,
159
+ fieldValuesFromAnswers,
136
160
  fileMentionsToParts,
137
161
  formatActivityCost,
138
162
  formatActivityDuration,
139
163
  formatModelCost,
140
164
  formatTokensPerSecond,
141
165
  hasSecretField,
166
+ hydrateChatInteractions,
142
167
  interactionFromWireRequest,
143
168
  interactionPartKey,
144
169
  interactionStatusLabels,
170
+ interactionSubmissionSignature,
145
171
  interactionTerminalNotes,
146
172
  interactionToPersistedPart,
147
173
  isLateAnswerableStatus,
@@ -154,6 +180,7 @@ export {
154
180
  nextRevealCount,
155
181
  noticePart,
156
182
  noticePartKey,
183
+ parseInteractionAnswers,
157
184
  parseInteractionCancel,
158
185
  parseInteractionRequest,
159
186
  pendingApprovalOf,
@@ -163,11 +190,13 @@ export {
163
190
  resolveChatInteraction,
164
191
  responseErrorMessage,
165
192
  restoreChatInteractions,
193
+ stampInteractionAnswers,
166
194
  streamChatTurn,
167
195
  tabTerminalConnectionId,
168
196
  terminalizePendingChatInteractions,
169
197
  upsertChatInteraction,
170
198
  useChatInteractions,
199
+ useDurablePlanFlow,
171
200
  useFileMentions,
172
201
  usePending,
173
202
  usePopover,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.43.41",
3
+ "version": "0.43.42",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -182,6 +182,16 @@
182
182
  "import": "./dist/interactions/index.js",
183
183
  "default": "./dist/interactions/index.js"
184
184
  },
185
+ "./plans": {
186
+ "types": "./dist/plans/index.d.ts",
187
+ "import": "./dist/plans/index.js",
188
+ "default": "./dist/plans/index.js"
189
+ },
190
+ "./durable-chat": {
191
+ "types": "./dist/durable-chat/index.d.ts",
192
+ "import": "./dist/durable-chat/index.js",
193
+ "default": "./dist/durable-chat/index.js"
194
+ },
185
195
  "./missions": {
186
196
  "types": "./dist/missions/index.d.ts",
187
197
  "import": "./dist/missions/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/interactions/contract.ts"],"sourcesContent":["// Shared contract for agent interaction events (kind: \"question\" et al) — the\n// generalized human-in-the-loop primitive on the chat stream. Framework-agnostic\n// (no server- or React-only imports) so a producer (chat route) and a consumer\n// (stream parser, transcript, question card) agree on one wire shape and one\n// persisted-part shape.\n//\n// An `interaction` event means the run is BLOCKED inside the sidecar's\n// InteractionBroker until the user answers, the agent withdraws the ask\n// (`interaction.cancel`), or the broker times out. A pending interaction is\n// \"waiting on the user\", not \"model working\".\n\nimport {\n InteractionRequestSchema,\n type InteractionData,\n type InteractionField,\n type InteractionOutcome,\n type InteractionRequest,\n} from '@tangle-network/agent-interface'\n\nexport type { InteractionData, InteractionOutcome, InteractionRequest }\n\n// ---------------------------------------------------------------------------\n// Event names\n\n/** Sidecar → client: the agent raised an ask; data = `{ request }`. */\nexport const INTERACTION_EVENT = 'interaction' as const\n/** Sidecar → client: the ask was withdrawn; data = `{ id, reason? }`. */\nexport const INTERACTION_CANCEL_EVENT = 'interaction.cancel' as const\n/** An ask was answered; data = `{ id, status }`. In the wire contract so a\n * server broadcast and a client-local mark share one event name. */\nexport const INTERACTION_RESOLVED_EVENT = 'interaction.resolved' as const\n\n/** Interaction kinds a product typically renders a card for. Anything else is\n * auto-declined by the chat producer's safety net and never reaches a client.\n * A pure default; a product may substitute its own renderable set. */\nconst RENDERABLE_INTERACTION_KINDS: ReadonlySet<string> = new Set(['question', 'plan'])\n\nexport function isRenderableInteractionKind(kind: string): boolean {\n return RENDERABLE_INTERACTION_KINDS.has(kind)\n}\n\n/** Answer/field keys the sidecar will accept: identifier-safe and never a\n * prototype-pollution vector. */\nexport function isSafeInteractionFieldKey(key: string): boolean {\n return /^[A-Za-z0-9_-]+$/.test(key) && key !== '__proto__' && key !== 'constructor' && key !== 'prototype'\n}\n\n// ---------------------------------------------------------------------------\n// Field types\n//\n// `allowCustom` (a select that also accepts a write-in value) is defined by\n// newer agent-interface schemas; older pinned schemas strip unknown keys on\n// parse. The wire/persisted field types below carry the flag so a card can gate\n// its write-in input, and `parseInteractionRequest` returns the RAW payload\n// (schema-validated, not schema-parsed) so the flag survives.\n\nexport type ChatSelectField = Extract<InteractionField, { type: 'select' }> & {\n allowCustom?: boolean\n}\nexport type ChatInteractionField = Exclude<InteractionField, { type: 'select' }> | ChatSelectField\n\n/** `InteractionRequest` whose select fields may carry `allowCustom`. */\nexport type InteractionRequestWire = Omit<InteractionRequest, 'answerSpec'> & {\n answerSpec: { fields: ChatInteractionField[] }\n}\n\n// ---------------------------------------------------------------------------\n// Interaction lifecycle\n\nexport type ChatInteractionStatus = 'pending' | 'answered' | 'declined' | 'cancelled' | 'expired'\n\n/** The client/persisted view of one ask. `fields` come verbatim off the wire. */\nexport interface ChatInteraction {\n id: string\n kind: string\n title: string\n body?: string\n fields: ChatInteractionField[]\n status: ChatInteractionStatus\n /** Set when status came from an `interaction.cancel` (e.g. \"timeout\"). */\n cancelReason?: string\n}\n\nexport function isTerminalInteractionStatus(status: ChatInteractionStatus): boolean {\n return status !== 'pending'\n}\n\n/** Statuses only move forward (pending → terminal); a replayed/stale `pending`\n * must never resurrect a resolved card. */\nexport function canTransitionInteractionStatus(\n from: ChatInteractionStatus,\n to: ChatInteractionStatus,\n): boolean {\n return from === 'pending' && to !== from\n}\n\n/** Maps an `interaction.cancel` reason to the card's terminal status. */\nexport function cancelStatusFor(reason: string | undefined): ChatInteractionStatus {\n return reason === 'timeout' ? 'expired' : 'cancelled'\n}\n\nfunction stableValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(stableValue)\n if (!value || typeof value !== 'object') return value\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, stableValue(nested)]),\n )\n}\n\nfunction normalizedInteractionText(value: string | undefined): string {\n return (value ?? '').replace(/\\s+/g, ' ').trim()\n}\n\n/** Content identity for duplicate safety nets. Excludes volatile ids/statuses. */\nexport function questionInteractionContentSignature(interaction: ChatInteraction): string | null {\n if (interaction.kind !== 'question') return null\n return JSON.stringify(stableValue({\n kind: interaction.kind,\n title: normalizedInteractionText(interaction.title),\n body: normalizedInteractionText(interaction.body),\n fields: interaction.fields,\n }))\n}\n\nexport function dedupeQuestionInteractionsByContent(interactions: ChatInteraction[]): ChatInteraction[] {\n const seen = new Set<string>()\n return interactions.filter((interaction) => {\n const signature = questionInteractionContentSignature(interaction)\n if (!signature) return true\n if (seen.has(signature)) return false\n seen.add(signature)\n return true\n })\n}\n\n// ---------------------------------------------------------------------------\n// Wire parsing — typed outcomes, fail loud at the caller (log + skip; never a\n// half-rendered card).\n\nexport type ParseInteractionResult =\n | { succeeded: true; value: InteractionRequestWire }\n | { succeeded: false; error: string }\n\n/** Parses an `interaction` event's data (`{ request }`). Validates the shape\n * with the agent-interface schema but returns the raw request so a field a\n * pinned schema predates (`allowCustom`) survives. */\nexport function parseInteractionRequest(data: Record<string, unknown> | undefined): ParseInteractionResult {\n const request = data?.request\n if (!request || typeof request !== 'object') {\n return { succeeded: false, error: 'interaction event carried no request object' }\n }\n const validation = InteractionRequestSchema.safeParse(request)\n if (!validation.success) {\n return { succeeded: false, error: `malformed interaction request: ${validation.error.message}` }\n }\n return { succeeded: true, value: request as InteractionRequestWire }\n}\n\nexport interface InteractionCancelData {\n id: string\n reason?: string\n}\n\nexport function parseInteractionCancel(\n data: Record<string, unknown> | undefined,\n): { succeeded: true; value: InteractionCancelData } | { succeeded: false; error: string } {\n const id = typeof data?.id === 'string' && data.id ? data.id : null\n if (!id) return { succeeded: false, error: 'interaction.cancel event carried no id' }\n const reason = typeof data?.reason === 'string' && data.reason ? data.reason : undefined\n return { succeeded: true, value: { id, ...(reason ? { reason } : {}) } }\n}\n\n// ---------------------------------------------------------------------------\n// Composer-as-answer delivery: while asks are pending the composer never\n// blocks — typed text is delivered verbatim to every open ask and the agent\n// decides what it means. No interpretation here; the sidecar validates answers\n// fail-closed (invalid free text on an option-only ask → 400).\n\nexport function fieldAcceptsFreeText(field: ChatInteractionField): boolean {\n if (field.type === 'text') return true\n if (field.type === 'select') return (field as ChatSelectField).allowCustom === true\n return false\n}\n\nexport interface ComposerAnswerDelivery {\n interactionId: string\n field: ChatInteractionField\n}\n\n/** One delivery per pending ask: the first free-text-capable field, else the\n * first field. Zero-field asks are skipped (nothing to carry the text). */\nexport function composerAnswerDeliveries(pending: ChatInteraction[]): ComposerAnswerDelivery[] {\n const deliveries: ComposerAnswerDelivery[] = []\n for (const interaction of pending) {\n // Only questions take a composer-routed answer. A non-question ask (a plan)\n // is POSTed as outcome:\"accepted\" when answered — routing composer text to\n // it would silently APPROVE it. Approval is an explicit card click, so the\n // composer skips it and the plan card stays the only path.\n if (interaction.kind !== 'question') continue\n const field = interaction.fields.find(fieldAcceptsFreeText) ?? interaction.fields[0]\n if (!field) continue\n deliveries.push({ interactionId: interaction.id, field })\n }\n return deliveries\n}\n\n/** Shapes composer text into the respond payload for the routed field\n * (select answers are string arrays on the wire; text answers are strings). */\nexport function composerAnswerData(field: ChatInteractionField, text: string): InteractionData {\n return { [field.name]: field.type === 'select' ? [text] : text }\n}\n\n// ---------------------------------------------------------------------------\n// Part keys + codecs (persisted `messages.parts` entries and live stream parts\n// share these shapes).\n\nexport function interactionPartKey(id: string): string {\n return `interaction:${id}`\n}\n\nexport function noticePartKey(id: string): string {\n return `notice:${id}`\n}\n\nexport type NoticeKind = 'warning' | 'auto-declined'\n\n/**\n * Persisted-part shapes the codecs below produce — the SAME rows\n * `/chat-store`'s `ChatInteractionPart`/`ChatNoticePart` store, typed at the\n * source so a product pushing them into a `ChatMessagePart[]` transcript needs\n * no cast. Type aliases (not interfaces) on purpose: the implicit index\n * signature keeps them assignable to the `Record<string, unknown>` these\n * codecs previously returned, so existing consumers stay source-compatible.\n */\nexport type InteractionPersistedPart = {\n type: 'interaction'\n id: string\n kind: string\n title: string\n body?: string\n answerSpec: { fields: ChatInteractionField[] }\n status: ChatInteractionStatus\n cancelReason?: string\n}\n\nexport type NoticePersistedPart = {\n type: 'notice'\n id: string\n noticeKind: NoticeKind\n text: string\n}\n\n/** Builds the persisted/streamed `notice` part — a one-line transcript notice\n * explaining an out-of-band event (warning, auto-declined interaction). */\nexport function noticePart(noticeKind: NoticeKind, id: string, text: string): NoticePersistedPart {\n return { type: 'notice', id, noticeKind, text }\n}\n\n/** Reads a wire request into the client's pending `ChatInteraction`. */\nexport function interactionFromWireRequest(request: InteractionRequestWire): ChatInteraction {\n return {\n id: request.id,\n kind: request.kind,\n title: request.title,\n ...(request.body ? { body: request.body } : {}),\n fields: request.answerSpec.fields,\n status: 'pending',\n }\n}\n\n/** Builds the persisted/streamed `interaction` part from a wire request. */\nexport function interactionToPersistedPart(\n request: InteractionRequestWire,\n status: ChatInteractionStatus,\n cancelReason?: string,\n): InteractionPersistedPart {\n return {\n type: 'interaction',\n id: request.id,\n kind: request.kind,\n title: request.title,\n ...(request.body ? { body: request.body } : {}),\n answerSpec: { fields: request.answerSpec.fields },\n status,\n ...(cancelReason ? { cancelReason } : {}),\n }\n}\n\n/** Reads a persisted/streamed `interaction` part back into a `ChatInteraction`.\n * Returns null (caller logs) when the part is not one of ours. */\nexport function persistedPartToInteraction(part: Record<string, unknown>): ChatInteraction | null {\n if (String(part.type ?? '') !== 'interaction') return null\n const id = typeof part.id === 'string' && part.id ? part.id : null\n const kind = typeof part.kind === 'string' && part.kind ? part.kind : null\n const title = typeof part.title === 'string' ? part.title : ''\n const answerSpec = part.answerSpec as { fields?: unknown } | undefined\n const fields = Array.isArray(answerSpec?.fields) ? (answerSpec.fields as ChatInteractionField[]) : null\n const status = part.status as ChatInteractionStatus | undefined\n const validStatus = status && ['pending', 'answered', 'declined', 'cancelled', 'expired'].includes(status)\n if (!id || !kind || !fields || !validStatus) return null\n return {\n id,\n kind,\n title,\n ...(typeof part.body === 'string' && part.body ? { body: part.body } : {}),\n fields,\n status,\n ...(typeof part.cancelReason === 'string' && part.cancelReason ? { cancelReason: part.cancelReason } : {}),\n }\n}\n"],"mappings":"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","names":[]}