@tangle-network/agent-app 0.43.40 → 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 (53) hide show
  1. package/dist/assistant/index.d.ts +3 -2
  2. package/dist/assistant/index.js +4 -3
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chat-routes/index.d.ts +19 -5
  5. package/dist/chat-routes/index.js +100 -7
  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-2EO7CPL3.js +191 -0
  11. package/dist/chunk-2EO7CPL3.js.map +1 -0
  12. package/dist/{chunk-Y4QHNQ75.js → chunk-6KVH5SP5.js} +110 -6
  13. package/dist/chunk-6KVH5SP5.js.map +1 -0
  14. package/dist/{chunk-XYWCGFII.js → chunk-B5JD3DXD.js} +48 -2
  15. package/dist/chunk-B5JD3DXD.js.map +1 -0
  16. package/dist/{chunk-I2R2XT4M.js → chunk-I2ATYB7R.js} +18 -2
  17. package/dist/chunk-I2ATYB7R.js.map +1 -0
  18. package/dist/chunk-SIXYZ2FB.js +101 -0
  19. package/dist/chunk-SIXYZ2FB.js.map +1 -0
  20. package/dist/{chunk-4TXDD6P2.js → chunk-XAWFPMAR.js} +38 -3
  21. package/dist/chunk-XAWFPMAR.js.map +1 -0
  22. package/dist/{chunk-D4HB72W2.js → chunk-ZLHK25C3.js} +1890 -1213
  23. package/dist/chunk-ZLHK25C3.js.map +1 -0
  24. package/dist/chunk-ZU5GNSOJ.js +920 -0
  25. package/dist/chunk-ZU5GNSOJ.js.map +1 -0
  26. package/dist/{contract-DYbTzEDf.d.ts → contract-KfqJh_au.d.ts} +23 -2
  27. package/dist/design-canvas-react/index.js +4 -4
  28. package/dist/durable-chat/index.d.ts +419 -0
  29. package/dist/durable-chat/index.js +59 -0
  30. package/dist/durable-chat/index.js.map +1 -0
  31. package/dist/file-index-Bw_IQE_G.d.ts +194 -0
  32. package/dist/index.d.ts +5 -3
  33. package/dist/index.js +123 -45
  34. package/dist/interactions/index.d.ts +58 -3
  35. package/dist/interactions/index.js +7 -3
  36. package/dist/{parts-BcbitSNp.d.ts → parts-DjX0RRTS.d.ts} +9 -3
  37. package/dist/plans/index.d.ts +69 -0
  38. package/dist/plans/index.js +21 -0
  39. package/dist/plans/index.js.map +1 -0
  40. package/dist/stream/index.js +3 -1
  41. package/dist/teams/index.js +9 -9
  42. package/dist/teams/invitations-api.js +3 -3
  43. package/dist/web-react/index.d.ts +345 -94
  44. package/dist/web-react/index.js +50 -5
  45. package/package.json +11 -1
  46. package/dist/chunk-4TXDD6P2.js.map +0 -1
  47. package/dist/chunk-5SV5PSU7.js +0 -80
  48. package/dist/chunk-5SV5PSU7.js.map +0 -1
  49. package/dist/chunk-D4HB72W2.js.map +0 -1
  50. package/dist/chunk-I2R2XT4M.js.map +0 -1
  51. package/dist/chunk-XYWCGFII.js.map +0 -1
  52. package/dist/chunk-Y4QHNQ75.js.map +0 -1
  53. package/dist/wire-BaUF66AS.d.ts +0 -61
@@ -3,15 +3,6 @@ import {
3
3
  isInviteTokenShape,
4
4
  validateInviteToken
5
5
  } from "../chunk-3SVAA3MA.js";
6
- import {
7
- INVITATION_EXPIRY_DAYS,
8
- generateInvitationToken,
9
- getInvitationExpiresAt,
10
- inviteUrlForToken,
11
- normalizeInvitationEmail,
12
- parseInvitationPermission,
13
- renderInvitationEmail
14
- } from "../chunk-WEBBJBDH.js";
15
6
  import {
16
7
  ASSIGNABLE_WORKSPACE_ROLES,
17
8
  ORGANIZATION_ROLES,
@@ -27,6 +18,15 @@ import {
27
18
  workspaceRoleToCollaborationAccess,
28
19
  workspaceRoleToSandboxRole
29
20
  } from "../chunk-63CE7FEZ.js";
21
+ import {
22
+ INVITATION_EXPIRY_DAYS,
23
+ generateInvitationToken,
24
+ getInvitationExpiresAt,
25
+ inviteUrlForToken,
26
+ normalizeInvitationEmail,
27
+ parseInvitationPermission,
28
+ renderInvitationEmail
29
+ } from "../chunk-WEBBJBDH.js";
30
30
  export {
31
31
  ASSIGNABLE_WORKSPACE_ROLES,
32
32
  INVITATION_EXPIRY_DAYS,
@@ -2,6 +2,9 @@ import {
2
2
  SeatLimitError
3
3
  } from "../chunk-SWUVTGMR.js";
4
4
  import "../chunk-3SVAA3MA.js";
5
+ import {
6
+ hasWorkspaceRole
7
+ } from "../chunk-63CE7FEZ.js";
5
8
  import {
6
9
  generateInvitationToken,
7
10
  getInvitationExpiresAt,
@@ -9,9 +12,6 @@ import {
9
12
  normalizeInvitationEmail,
10
13
  parseInvitationPermission
11
14
  } from "../chunk-WEBBJBDH.js";
12
- import {
13
- hasWorkspaceRole
14
- } from "../chunk-63CE7FEZ.js";
15
15
 
16
16
  // src/teams/invitations-api.ts
17
17
  import { and, eq, lte, sql } from "drizzle-orm";
@@ -1,15 +1,200 @@
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';
8
+ import { j as FileMention } from '../file-index-Bw_IQE_G.js';
9
+ export { b as ChatTurnFilePartInput, a as ChatTurnPartInput, C as ChatTurnRequestPayload, g as FileIndexReadyResponse, h as FileIndexResponse, i as FileIndexWarmingResponse, o as buildMentionPromptBlock, p as chatTurnRequestInit, r as fileMentionsToParts, s as mediaTypeForMentionPath } from '../file-index-Bw_IQE_G.js';
7
10
  import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
8
11
  import { a as FlowTrace } from '../flow-types-Cb_AblZs.js';
9
12
  export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-BIIC__CP.js';
10
13
  import { CatalogModel } from '../catalog/index.js';
11
14
  import { Harness } from '../harness/index.js';
12
- export { b as ChatTurnFilePartInput, a as ChatTurnPartInput, C as ChatTurnRequestPayload, f as chatTurnRequestInit } from '../wire-BaUF66AS.js';
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;
13
198
 
14
199
  /**
15
200
  * Client-side chat-stream consumption — the NDJSON parse loop every agent
@@ -63,6 +248,10 @@ interface ChatStreamCallbacks {
63
248
  * user", not "model working". Optional — a consumer that doesn't wire it
64
249
  * parses the same stream unchanged. */
65
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;
66
255
  }
67
256
  interface ConsumeChatStreamResult {
68
257
  turnId: string | null;
@@ -165,86 +354,6 @@ interface ChatComposerProps {
165
354
  }
166
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;
167
356
 
168
- /**
169
- * Shared answer-building + submit plumbing for the interaction cards
170
- * (question, plan). Client-safe, no React: cards own their state, this owns
171
- * the wire. Lifted from the gtm-agent fork (the most fix-absorbed of the three
172
- * product copies), including the 30s submit timeout that keeps a dead route
173
- * from wedging a card in "Submitting…".
174
- */
175
-
176
- /** Status-badge labels for an interaction card. `cancelled`/`expired` read the
177
- * same across cards; each card supplies its own verbs for the other states
178
- * (a question is answered/declined; a plan is approved/rejected). */
179
- declare function interactionStatusLabels(labels: {
180
- pending: string;
181
- answered: string;
182
- declined: string;
183
- }): Record<ChatInteractionStatus, string>;
184
- /** Terminal-state notes for an interaction card. The expiry/withdrawal lines
185
- * share one shape around the card's noun ("question"/"plan"); any extra notes
186
- * (e.g. a plan's `declined` revision line) merge on top. */
187
- declare function interactionTerminalNotes(noun: string, extra?: Partial<Record<ChatInteractionStatus, string>>): Partial<Record<ChatInteractionStatus, string>>;
188
- type FieldValues = Record<string, {
189
- selected?: string[];
190
- text?: string;
191
- custom?: string;
192
- }>;
193
- /** The submitted value for one field, or null when it has no answer yet. */
194
- declare function fieldAnswer(field: ChatInteractionField, values: FieldValues): InteractionData[string] | null;
195
- /** All required fields answered → the respond payload; else null (not
196
- * submittable yet). Optional unanswered fields are omitted. */
197
- declare function buildAnswerData(fields: ChatInteractionField[], values: FieldValues): InteractionData | null;
198
- declare function isLateAnswerableStatus(status: ChatInteractionStatus): boolean;
199
- /** Secrets must never leave the sidecar answer channel for the visible chat
200
- * transcript, so a secret-bearing ask cannot be late-answered. */
201
- declare function hasSecretField(fields: ChatInteractionField[]): boolean;
202
- /** Renders the late answer as a self-contained chat message: the original
203
- * question, its context, and the user's answer(s). */
204
- declare function lateAnswerMessage(interaction: ChatInteraction, data: InteractionData): string;
205
- declare const INTERACTION_SUBMIT_TIMEOUT_MS = 30000;
206
- declare const INTERACTION_SUBMIT_TIMEOUT_MESSAGE = "Could not reach the agent. Try again.";
207
- /** One card submission: which ask, resolved how, with what answers. */
208
- interface InteractionAnswerSubmission {
209
- id: string;
210
- outcome: 'accepted' | 'declined';
211
- data?: InteractionData;
212
- }
213
- type InteractionSubmitResult = {
214
- ok: true;
215
- } | {
216
- ok: false;
217
- expired: boolean;
218
- message: string;
219
- };
220
- /** The cards' only side-effect seam: POST one resolution, report the normalized
221
- * outcome. Products bind their route URL + routing fields (workspaceId,
222
- * threadId, session path param) via `createInteractionAnswerSubmitter` or a
223
- * hand-rolled implementation. */
224
- type SubmitInteractionAnswer = (submission: InteractionAnswerSubmission) => Promise<InteractionSubmitResult>;
225
- /** Extracts the most specific error message a route returned. */
226
- declare function responseErrorMessage(res: Response): Promise<{
227
- code?: string;
228
- message: string;
229
- }>;
230
- interface InteractionAnswerSubmitterOptions {
231
- /** The product's answer route (the POST half of `createInteractionAnswerRoute`).
232
- * A function when the URL carries the session (e.g. `/api/sessions/${id}/interactions`). */
233
- url: string | ((submission: InteractionAnswerSubmission) => string);
234
- /** Extra routing fields merged into the POST body (e.g. workspaceId, threadId). */
235
- body?: Record<string, unknown> | ((submission: InteractionAnswerSubmission) => Record<string, unknown>);
236
- timeoutMs?: number;
237
- fetchImpl?: typeof fetch;
238
- }
239
- /**
240
- * Builds the `SubmitInteractionAnswer` the cards consume: POSTs
241
- * `{ ...routingFields, id, outcome, data? }` with an abortable timeout and
242
- * normalizes the outcome. `expired` is the 410 path — the ask is gone
243
- * (answered elsewhere, timed out, or the session moved on) and the card must
244
- * flip to the same dead state a cancel event produces.
245
- */
246
- declare function createInteractionAnswerSubmitter(options: InteractionAnswerSubmitterOptions): SubmitInteractionAnswer;
247
-
248
357
  type InteractionBadgeVariant = 'outline' | 'default' | 'destructive';
249
358
  declare function InteractionBadge({ variant, children }: {
250
359
  variant: InteractionBadgeVariant;
@@ -281,7 +390,7 @@ interface InteractionQuestionCardProps {
281
390
  submitAnswer: SubmitInteractionAnswer;
282
391
  /** Fired when this card resolves locally (answered, or discovered expired
283
392
  * via a 410) so the stream/route state stays in sync. */
284
- onResolved?: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>) => void;
393
+ onResolved?: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>, answers?: ChatInteraction['answers']) => void;
285
394
  /** Delivers a late answer (the ask expired/was withdrawn) as a fresh chat
286
395
  * turn. Return/resolve `false` when the send was rejected so the card stays
287
396
  * retryable. Omit to hide the late-answer affordance entirely. */
@@ -306,6 +415,34 @@ interface InteractionPlanCardProps {
306
415
  }
307
416
  declare function InteractionPlanCard({ interaction, canWrite, submitAnswer, onResolved, renderMarkdown, className, }: InteractionPlanCardProps): react.JSX.Element;
308
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
+
309
446
  /**
310
447
  * useChatInteractions — the interaction-state wiring every consumer of
311
448
  * `ChatStreamCallbacks.onInteraction` re-implements: an id-keyed,
@@ -335,14 +472,28 @@ declare function upsertChatInteraction(list: ChatInteraction[], interaction: Cha
335
472
  * `expired` (reason:"timeout") or `cancelled`. */
336
473
  declare function cancelChatInteraction(list: ChatInteraction[], cancel: InteractionCancelData): ChatInteraction[];
337
474
  /** Marks one ask resolved locally (the card's `onResolved`). Forward-only. */
338
- 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[];
339
476
  /** Settles every still-pending ask when the turn ends: `answered` for a turn
340
477
  * that completed cleanly, `expired` for one that failed. */
341
478
  declare function terminalizePendingChatInteractions(list: ChatInteraction[], status: Extract<ChatInteractionStatus, 'answered' | 'expired'>): ChatInteraction[];
342
- /** Reload restore from the answer route's GET list: every listed ask is
343
- * outstanding (upserted pending), and every previously-pending ask the sidecar
344
- * no longer lists was resolved while we were away (settled as answered). */
345
- 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[];
346
497
  interface UseChatInteractionsResult {
347
498
  /** All known interactions, insertion-ordered. */
348
499
  interactions: ChatInteraction[];
@@ -353,15 +504,109 @@ interface UseChatInteractionsResult {
353
504
  /** Wire to `interaction.cancel` events. */
354
505
  applyCancel: (cancel: InteractionCancelData) => void;
355
506
  /** Wire to the cards' `onResolved`. */
356
- markResolved: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>) => void;
507
+ markResolved: (id: string, status: Exclude<ChatInteractionStatus, 'pending'>, answers?: InteractionAnswers) => void;
357
508
  /** Wire to the answer route's GET list after a reload/reconnect. */
358
- 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;
359
512
  /** Settle still-pending asks when the turn ends. */
360
513
  terminalizePending: (status: Extract<ChatInteractionStatus, 'answered' | 'expired'>) => void;
361
514
  /** Drop everything (thread switch). */
362
515
  reset: () => void;
363
516
  }
364
- declare function useChatInteractions(): UseChatInteractionsResult;
517
+ type UseChatInteractionsOptions = RestoreChatInteractionsOptions;
518
+ declare function useChatInteractions(options?: UseChatInteractionsOptions): UseChatInteractionsResult;
519
+
520
+ /**
521
+ * `useFileMentions` — the glue a host passes straight into `AgentComposer`'s
522
+ * `mention` prop (`@tangle-network/sandbox-ui#184`) to wire up `@`-file
523
+ * mentions against `createSandboxFileIndexRoute` (`/chat-routes`).
524
+ *
525
+ * Fetches the index once per session from `indexUrl`, refreshes it in the
526
+ * background whenever the popover opens (a `fetchItems` call) if the cached
527
+ * copy has aged past `refreshAfterMs`, and answers every keystroke from an
528
+ * in-memory fuzzy filter — no per-keystroke network round trip. The returned
529
+ * `refresh()` lets a caller force a re-fetch immediately instead of waiting
530
+ * on `refreshAfterMs` — e.g. right after the agent creates a file mid-session.
531
+ *
532
+ * `MentionItem`/the `mention` prop shape mirror the FROZEN contract from
533
+ * sandbox-ui#184 structurally (no import: `/web-react` stays dependency-free
534
+ * beyond React, and `@tangle-network/sandbox-ui` is an optional peer).
535
+ */
536
+
537
+ /** Mirrors sandbox-ui#184's `MentionItem` — the atomic pill's payload. For a
538
+ * file mention, `id` is the workspace-relative path (the pill's stable
539
+ * identity and the `@<id>` serialization sandbox-ui uses to round-trip
540
+ * `value`), `label` is the display name, and `detail` carries the full path
541
+ * for the popover row's secondary line. */
542
+ interface MentionItem {
543
+ id: string;
544
+ label: string;
545
+ detail?: string;
546
+ kind?: string;
547
+ }
548
+ /** Mirrors sandbox-ui#184's `AgentComposerProps['mention']` shape — plug the
549
+ * hook's `mention` return value straight into that prop. */
550
+ interface ComposerMentionProp {
551
+ trigger?: string;
552
+ fetchItems(query: string): Promise<MentionItem[]>;
553
+ onMentionsChange?(mentions: MentionItem[]): void;
554
+ renderItem?(item: MentionItem): ReactNode;
555
+ emptyText?: string;
556
+ }
557
+ /**
558
+ * Ranks `files` against `query` (case-insensitive), capped to `limit`:
559
+ * name-prefix matches first, then name-substring, then path-substring.
560
+ * Within a tier, shorter names sort first (the more specific match), then
561
+ * alphabetically by path for a stable order. An empty query returns the
562
+ * first `limit` entries unranked — the popover's default list before typing.
563
+ * Pure and dependency-free (no fuzzy-match library) so it's cheap enough to
564
+ * re-run on every keystroke against a 10k-entry index.
565
+ */
566
+ declare function rankFileMentions(files: readonly FileMention[], query: string, limit: number): FileMention[];
567
+ /** Max popover results per query — enough to show a useful spread of matches
568
+ * without pushing the fuzzy-filtered list past what a popover can usefully
569
+ * render in one screen. */
570
+ declare const DEFAULT_MENTION_LIMIT = 20;
571
+ /** How long a `ready` index is served before a background refetch — long
572
+ * enough that a full session's worth of popover opens don't repeatedly hit
573
+ * the index endpoint, short enough that a stale listing doesn't linger too
574
+ * far past workspace file changes. Callers who need the index current right
575
+ * now (e.g. just after the agent creates a file) call `refresh()` instead of
576
+ * waiting on this window. */
577
+ declare const INDEX_REFRESH_AFTER_MS: number;
578
+ /** Popover empty-state copy for a `ready` index whose query matched nothing.
579
+ * Loading/warming/error states have their own copy — see `emptyTextFor`. */
580
+ declare const DEFAULT_MENTION_EMPTY_TEXT = "No matching files";
581
+ interface UseFileMentionsOptions {
582
+ /** GET endpoint returning `FileIndexResponse` (a `createSandboxFileIndexRoute`). */
583
+ indexUrl: string;
584
+ /** Max popover results per query. Default {@link DEFAULT_MENTION_LIMIT}. */
585
+ limit?: number;
586
+ /** How long a `ready` index is served without a background refetch.
587
+ * Default {@link INDEX_REFRESH_AFTER_MS}. */
588
+ refreshAfterMs?: number;
589
+ /** `fetch` override for tests / non-global-fetch hosts. Default `fetch`. */
590
+ fetchImpl?: typeof fetch;
591
+ /** Text shown in the popover's empty state once the index is loaded and
592
+ * the query matched nothing. Default {@link DEFAULT_MENTION_EMPTY_TEXT}. */
593
+ emptyText?: string;
594
+ }
595
+ interface UseFileMentionsResult {
596
+ /** Spread straight into `AgentComposer`'s `mention` prop. */
597
+ mention: ComposerMentionProp;
598
+ /** The files currently referenced by mentions in the composer's value —
599
+ * the send-body list (map through `fileMentionsToParts`). */
600
+ mentions: FileMention[];
601
+ /** Drop all currently-referenced mentions (e.g. after a successful send). */
602
+ clearMentions: () => void;
603
+ /** Force a re-fetch of the index right now, ignoring `refreshAfterMs` — for
604
+ * example right after the agent creates a file mid-session, so the next
605
+ * popover open sees it. Dedupes against an already-in-flight load rather
606
+ * than firing a second request. */
607
+ refresh: () => Promise<void>;
608
+ }
609
+ declare function useFileMentions(options: UseFileMentionsOptions): UseFileMentionsResult;
365
610
 
366
611
  /**
367
612
  * Provider brand marks — real logo path data (simple-icons / SVG Logos, both
@@ -709,6 +954,9 @@ interface ChatUiMessage extends ChatMessageMetrics {
709
954
  * present and non-empty it is rendered in place of `content` + `toolCalls`;
710
955
  * both remain the fallback for producers that don't segment a turn. */
711
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>>;
712
960
  }
713
961
  interface ChatMessagesProps {
714
962
  messages: ChatUiMessage[];
@@ -718,6 +966,9 @@ interface ChatMessagesProps {
718
966
  renderMarkdown?: (content: string) => ReactNode;
719
967
  /** Extra per-message content (artifacts, custom panels) appended after the body. */
720
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'>;
721
972
  userLabel?: string;
722
973
  agentLabel?: string;
723
974
  /** Render the trailing "agent is thinking" row. */
@@ -790,6 +1041,6 @@ declare function useThinkingSeconds(active: boolean): number;
790
1041
  * model id, tokens/sec, and cost, plus a collapsible thinking section and
791
1042
  * tool-call chips.
792
1043
  */
793
- 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;
794
1045
 
795
- 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 ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, type EffortLevel, EffortPicker, type EffortPickerProps, type FieldValues, FlowWaterfall, type FlowWaterfallProps, 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, 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 WaterfallRow, activityTone, buildAnswerData, cancelChatInteraction, consumeChatStream, createInteractionAnswerSubmitter, dispatchChatStreamLine, fieldAnswer, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, hasSecretField, interactionStatusLabels, interactionTerminalNotes, isLateAnswerableStatus, lateAnswerMessage, mergeActivityPages, nextRevealCount, pendingApprovalOf, resolveChatInteraction, responseErrorMessage, restoreChatInteractions, streamChatTurn, terminalizePendingChatInteractions, upsertChatInteraction, useChatInteractions, 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 };