@dudousxd/nestjs-agent-core 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -53,6 +53,8 @@ __export(src_exports, {
53
53
  APPROVAL_EXPIRED_REASON: () => APPROVAL_EXPIRED_REASON,
54
54
  ASK_TOOL_DESCRIPTION: () => ASK_TOOL_DESCRIPTION,
55
55
  ASK_TOOL_NAME: () => ASK_TOOL_NAME,
56
+ ActionProposalExecutor: () => ActionProposalExecutor,
57
+ ActionProposalWorker: () => ActionProposalWorker,
56
58
  AgentRegistry: () => AgentRegistry,
57
59
  AgentStreamError: () => AgentStreamError,
58
60
  CONFIRM_JSON_SCHEMA_PROPERTIES: () => CONFIRM_JSON_SCHEMA_PROPERTIES,
@@ -73,6 +75,7 @@ __export(src_exports, {
73
75
  DefaultRolesPolicy: () => DefaultRolesPolicy,
74
76
  ELICITATION_INPUT_TYPES: () => ELICITATION_INPUT_TYPES,
75
77
  GLOBAL_SCOPE: () => GLOBAL_SCOPE,
78
+ InMemoryActionProposalStore: () => InMemoryActionProposalStore,
76
79
  InMemoryAgentStore: () => InMemoryAgentStore,
77
80
  InMemoryConfirmTokenStore: () => InMemoryConfirmTokenStore,
78
81
  MAX_ASK_QUESTIONS: () => MAX_ASK_QUESTIONS,
@@ -96,15 +99,25 @@ __export(src_exports, {
96
99
  THREAD_DETAIL_CONTENT_CHARS: () => THREAD_DETAIL_CONTENT_CHARS,
97
100
  ToolDisabledError: () => ToolDisabledError,
98
101
  ToolForbiddenError: () => ToolForbiddenError,
102
+ ToolInputDriftError: () => ToolInputDriftError,
99
103
  ToolInputInvalidError: () => ToolInputInvalidError,
100
104
  ToolNotFoundError: () => ToolNotFoundError,
105
+ ToolPreflightDeniedError: () => ToolPreflightDeniedError,
101
106
  ToolRegistry: () => ToolRegistry,
102
107
  UNFINISHED_TOOL_CALL: () => UNFINISHED_TOOL_CALL,
108
+ actionProposalCreationMatches: () => actionProposalCreationMatches,
109
+ actionProposalOutcomeFenceValid: () => actionProposalOutcomeFenceValid,
110
+ actionProposalOutcomeScope: () => actionProposalOutcomeScope,
111
+ actionProposalOutcomeText: () => actionProposalOutcomeText,
112
+ actionProposalReceipt: () => actionProposalReceipt,
113
+ actionProposalScopeMatches: () => actionProposalScopeMatches,
103
114
  actorScope: () => actorScope,
104
115
  agentDiagnosticKey: () => agentDiagnosticKey,
105
116
  agentFailureCode: () => agentFailureCode,
106
117
  askInputSchema: () => askInputSchema,
107
118
  askToolDefinition: () => askToolDefinition,
119
+ assertIndependentActionCapabilities: () => assertIndependentActionCapabilities,
120
+ attachActionProposalOutcome: () => attachActionProposalOutcome,
108
121
  bucketByActor: () => bucketByActor,
109
122
  bucketByModel: () => bucketByModel,
110
123
  bucketByThread: () => bucketByThread,
@@ -112,12 +125,15 @@ __export(src_exports, {
112
125
  buildMemoryBlock: () => buildMemoryBlock,
113
126
  buildSkillsBlock: () => buildSkillsBlock,
114
127
  canActorUseTool: () => canActorUseTool,
128
+ canonicalActionProposalJson: () => canonicalActionProposalJson,
115
129
  canonicalJson: () => canonicalJson,
130
+ claimActionProposalOutcome: () => claimActionProposalOutcome,
116
131
  closeEmptyRoles: () => closeEmptyRoles,
117
132
  compositeSkillProvider: () => compositeSkillProvider,
118
133
  confirmTokenExpiry: () => confirmTokenExpiry,
119
134
  createFrameBuffer: () => createFrameBuffer,
120
135
  createIncrementalGate: () => createIncrementalGate,
136
+ createNegotiatedUiCollector: () => createNegotiatedUiCollector,
121
137
  createNoopEmitUi: () => createNoopEmitUi,
122
138
  createUiCollector: () => createUiCollector,
123
139
  danglingToolCallIds: () => danglingToolCallIds,
@@ -130,6 +146,7 @@ __export(src_exports, {
130
146
  detachedStarted: () => detachedStarted,
131
147
  detachedUnsettled: () => detachedUnsettled,
132
148
  encodeStreamEvent: () => encodeStreamEvent,
149
+ escapeUnsafeToolUiText: () => escapeUnsafeToolUiText,
133
150
  estimateCost: () => estimateCost,
134
151
  estimateMessageTokens: () => estimateMessageTokens,
135
152
  exhaustedWindow: () => exhaustedWindow,
@@ -144,6 +161,7 @@ __export(src_exports, {
144
161
  gateFollowUps: () => gateFollowUps,
145
162
  gateTail: () => gateTail,
146
163
  hashConfirmToken: () => hashConfirmToken,
164
+ initialActionProposal: () => initialActionProposal,
147
165
  intersectAllowLists: () => intersectAllowLists,
148
166
  invokeWithTransientRetry: () => invokeWithTransientRetry,
149
167
  isChatQueueStore: () => isChatQueueStore,
@@ -162,7 +180,9 @@ __export(src_exports, {
162
180
  observeTurnFrames: () => observeTurnFrames,
163
181
  offerMemories: () => offerMemories,
164
182
  offerSkills: () => offerSkills,
183
+ parseTextActionProposalCommand: () => parseTextActionProposalCommand,
165
184
  personaCatalogEntry: () => personaCatalogEntry,
185
+ prepareActionProposal: () => prepareActionProposal,
166
186
  publishAgentDelegated: () => publishAgentDelegated,
167
187
  publishAgentMemoryResolved: () => publishAgentMemoryResolved,
168
188
  publishAgentMemoryWritten: () => publishAgentMemoryWritten,
@@ -188,12 +208,14 @@ __export(src_exports, {
188
208
  rememberToolDefinition: () => rememberToolDefinition,
189
209
  renderElicitationAnswers: () => renderElicitationAnswers,
190
210
  repairInstruction: () => repairInstruction,
211
+ resolveActionProposalApproval: () => resolveActionProposalApproval,
191
212
  resolveElicitation: () => resolveElicitation,
192
213
  resolveGateLookback: () => resolveGateLookback,
193
214
  resolveMemoryDigest: () => resolveMemoryDigest,
194
215
  resolveOutputGateMode: () => resolveOutputGateMode,
195
216
  resolvePersonaAlias: () => resolvePersonaAlias,
196
217
  resolveSkillCatalog: () => resolveSkillCatalog,
218
+ resolveTextActionProposalDecision: () => resolveTextActionProposalDecision,
197
219
  resolveToolTransientRetryNumbers: () => resolveToolTransientRetryNumbers,
198
220
  rollupThreadUsage: () => rollupThreadUsage,
199
221
  runAgentLoop: () => runAgentLoop,
@@ -210,6 +232,7 @@ __export(src_exports, {
210
232
  skillInputSchema: () => skillInputSchema,
211
233
  skillToolDefinition: () => skillToolDefinition,
212
234
  skillWriteVerdict: () => skillWriteVerdict,
235
+ snapshotActionProposal: () => snapshotActionProposal,
213
236
  stampToolKinds: () => stampToolKinds,
214
237
  staticModelCatalog: () => staticModelCatalog,
215
238
  staticSkillProvider: () => staticSkillProvider,
@@ -220,8 +243,18 @@ __export(src_exports, {
220
243
  toolCallContext: () => toolCallContext,
221
244
  traceLlmTurn: () => traceLlmTurn,
222
245
  traceToolExecution: () => traceToolExecution,
246
+ transitionActionProposalClaim: () => transitionActionProposalClaim,
247
+ transitionActionProposalDecision: () => transitionActionProposalDecision,
248
+ transitionActionProposalLease: () => transitionActionProposalLease,
249
+ transitionActionProposalSettlement: () => transitionActionProposalSettlement,
250
+ transitionActionProposalSupersession: () => transitionActionProposalSupersession,
223
251
  truncateDetailContent: () => truncateDetailContent,
224
252
  unwrapToolStepOutput: () => unwrapToolStepOutput,
253
+ validateActionProposalCreation: () => validateActionProposalCreation,
254
+ validateActionProposalDiscoveryIndexBatch: () => validateActionProposalDiscoveryIndexBatch,
255
+ validateActionProposalExpiryBatch: () => validateActionProposalExpiryBatch,
256
+ validateActionProposalListQuery: () => validateActionProposalListQuery,
257
+ validateActionProposalWorkerClaim: () => validateActionProposalWorkerClaim,
225
258
  validateElicitationAnswer: () => validateElicitationAnswer,
226
259
  validateElicitationValue: () => validateElicitationValue,
227
260
  validateStructured: () => validateStructured,
@@ -234,6 +267,7 @@ __export(src_exports, {
234
267
  withSkillTool: () => withSkillTool,
235
268
  withToolTimeout: () => withToolTimeout,
236
269
  withTurnFrames: () => withTurnFrames,
270
+ wrapToolPreflightDenied: () => wrapToolPreflightDenied,
237
271
  wrapToolStepOutput: () => wrapToolStepOutput,
238
272
  writeMemory: () => writeMemory
239
273
  });
@@ -407,16 +441,16 @@ function observeTurnFrames(writer, now = Date.now) {
407
441
  };
408
442
  }
409
443
  __name(observeTurnFrames, "observeTurnFrames");
410
- function withTurnFrames(result, frames) {
444
+ function withTurnFrames(result2, frames) {
411
445
  return {
412
- ...result,
413
- ...result.reasoning === void 0 && frames.reasoning !== void 0 ? {
446
+ ...result2,
447
+ ...result2.reasoning === void 0 && frames.reasoning !== void 0 ? {
414
448
  reasoning: frames.reasoning
415
449
  } : {},
416
- ...result.reasoningMs === void 0 && frames.reasoningMs !== void 0 ? {
450
+ ...result2.reasoningMs === void 0 && frames.reasoningMs !== void 0 ? {
417
451
  reasoningMs: frames.reasoningMs
418
452
  } : {},
419
- ...result.ui === void 0 && frames.ui !== void 0 ? {
453
+ ...result2.ui === void 0 && frames.ui !== void 0 ? {
420
454
  ui: frames.ui
421
455
  } : {}
422
456
  };
@@ -425,8 +459,9 @@ __name(withTurnFrames, "withTurnFrames");
425
459
 
426
460
  // src/tool-ui.ts
427
461
  var TOOL_STEP_UI = "@@nestjs-agent/tool-step-ui";
428
- function wrapToolStepOutput(output, ui) {
429
- if (ui.length === 0) {
462
+ function wrapToolStepOutput(output, ui, text) {
463
+ const reserved = typeof output === "object" && output !== null && TOOL_STEP_UI in output;
464
+ if (ui.length === 0 && !reserved && !text) {
430
465
  return output;
431
466
  }
432
467
  const wrapped = {
@@ -434,17 +469,36 @@ function wrapToolStepOutput(output, ui) {
434
469
  output,
435
470
  ui: [
436
471
  ...ui
437
- ]
472
+ ],
473
+ ...text ? {
474
+ text
475
+ } : {}
438
476
  };
439
477
  return wrapped;
440
478
  }
441
479
  __name(wrapToolStepOutput, "wrapToolStepOutput");
480
+ function wrapToolPreflightDenied(reason) {
481
+ const wrapped = {
482
+ [TOOL_STEP_UI]: 1,
483
+ output: null,
484
+ ui: [],
485
+ preflightDenied: reason
486
+ };
487
+ return wrapped;
488
+ }
489
+ __name(wrapToolPreflightDenied, "wrapToolPreflightDenied");
442
490
  function unwrapToolStepOutput(raw) {
443
491
  if (typeof raw === "object" && raw !== null && raw[TOOL_STEP_UI] === 1) {
444
492
  const wrapped = raw;
445
493
  return {
446
494
  output: wrapped.output,
447
- ui: Array.isArray(wrapped.ui) ? wrapped.ui : []
495
+ ui: Array.isArray(wrapped.ui) ? wrapped.ui : [],
496
+ ...typeof wrapped.text === "string" ? {
497
+ text: wrapped.text
498
+ } : {},
499
+ ...typeof wrapped.preflightDenied === "string" ? {
500
+ preflightDenied: wrapped.preflightDenied
501
+ } : {}
448
502
  };
449
503
  }
450
504
  return {
@@ -493,6 +547,14 @@ function createUiCollector(toolCallId, write) {
493
547
  ...options.version !== void 0 ? {
494
548
  version: options.version
495
549
  } : {},
550
+ ...options.fallbackText !== void 0 ? {
551
+ fallbackText: options.fallbackText
552
+ } : {},
553
+ ...options.componentVersions !== void 0 ? {
554
+ componentVersions: {
555
+ ...options.componentVersions
556
+ }
557
+ } : {},
496
558
  toolCallId
497
559
  };
498
560
  pushed.set(id, entry);
@@ -527,6 +589,10 @@ function mergeUi(...lists) {
527
589
  ];
528
590
  }
529
591
  __name(mergeUi, "mergeUi");
592
+ function escapeUnsafeToolUiText(text) {
593
+ return text.replaceAll("\0", "\\u0000").replace(/[\uD800-\uDFFF]/gu, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
594
+ }
595
+ __name(escapeUnsafeToolUiText, "escapeUnsafeToolUiText");
530
596
 
531
597
  // src/tool-presentation.ts
532
598
  var ALL_AGENTS = "*";
@@ -738,12 +804,18 @@ function toolCallApprovalFromRow(row) {
738
804
  if (row.approver === null || row.approver === void 0) {
739
805
  return null;
740
806
  }
741
- const status = row.status === "pending_approval" ? "pending" : row.status === "rejected" ? "rejected" : row.status === "expired" ? "expired" : "approved";
807
+ const status = row.status === "pending_approval" || row.status === "proposed" ? "pending" : row.status === "rejected" ? "rejected" : row.status === "expired" ? "expired" : "approved";
742
808
  const expiresAt = row.expiresAt instanceof Date ? row.expiresAt.toISOString() : typeof row.expiresAt === "string" ? row.expiresAt : void 0;
743
809
  const decided = status === "approved" || status === "rejected";
744
810
  return {
745
811
  toolCallId: row.toolCallId,
812
+ ...row.proposalId != null ? {
813
+ proposalId: row.proposalId
814
+ } : {},
746
815
  approver: row.approver,
816
+ ...row.confirmation != null ? {
817
+ confirmation: row.confirmation
818
+ } : {},
747
819
  status,
748
820
  ...expiresAt !== void 0 ? {
749
821
  expiresAt
@@ -794,9 +866,9 @@ function bucketByModel(rows, prices) {
794
866
  bucket.costUsd += rowCost(row, prices);
795
867
  byModel.set(row.modelId, bucket);
796
868
  }
797
- const result = [];
869
+ const result2 = [];
798
870
  for (const [modelId, bucket] of byModel) {
799
- result.push({
871
+ result2.push({
800
872
  modelId,
801
873
  requests: bucket.requests,
802
874
  inputTokens: bucket.inputTokens,
@@ -804,8 +876,8 @@ function bucketByModel(rows, prices) {
804
876
  costUsd: bucket.costUsd
805
877
  });
806
878
  }
807
- result.sort((left, right) => right.costUsd - left.costUsd || left.modelId.localeCompare(right.modelId));
808
- return result;
879
+ result2.sort((left, right) => right.costUsd - left.costUsd || left.modelId.localeCompare(right.modelId));
880
+ return result2;
809
881
  }
810
882
  __name(bucketByModel, "bucketByModel");
811
883
  function bucketByActor(rows, prices) {
@@ -823,9 +895,9 @@ function bucketByActor(rows, prices) {
823
895
  bucket.threadIds.add(row.threadId);
824
896
  byActor.set(row.actorRef, bucket);
825
897
  }
826
- const result = [];
898
+ const result2 = [];
827
899
  for (const [actorRef, bucket] of byActor) {
828
- result.push({
900
+ result2.push({
829
901
  actorRef,
830
902
  requests: bucket.requests,
831
903
  totalTokens: bucket.totalTokens,
@@ -833,8 +905,8 @@ function bucketByActor(rows, prices) {
833
905
  threadCount: bucket.threadIds.size
834
906
  });
835
907
  }
836
- result.sort((left, right) => right.costUsd - left.costUsd || left.actorRef.localeCompare(right.actorRef));
837
- return result;
908
+ result2.sort((left, right) => right.costUsd - left.costUsd || left.actorRef.localeCompare(right.actorRef));
909
+ return result2;
838
910
  }
839
911
  __name(bucketByActor, "bucketByActor");
840
912
  function bucketByThread(rows, prices, threads, options) {
@@ -850,13 +922,13 @@ function bucketByThread(rows, prices, threads, options) {
850
922
  bucket.costUsd += rowCost(row, prices);
851
923
  byThread.set(row.threadId, bucket);
852
924
  }
853
- const result = [];
925
+ const result2 = [];
854
926
  for (const [threadId, bucket] of byThread) {
855
927
  const thread = threads.get(threadId);
856
928
  if (thread === void 0 && !options.includeUnknownThreads) {
857
929
  continue;
858
930
  }
859
- result.push({
931
+ result2.push({
860
932
  threadId,
861
933
  title: thread?.title ?? "",
862
934
  actorRef: thread?.actorRef ?? "",
@@ -865,8 +937,8 @@ function bucketByThread(rows, prices, threads, options) {
865
937
  costUsd: bucket.costUsd
866
938
  });
867
939
  }
868
- result.sort((left, right) => right.costUsd - left.costUsd || left.threadId.localeCompare(right.threadId));
869
- return result.slice(0, options.limit);
940
+ result2.sort((left, right) => right.costUsd - left.costUsd || left.threadId.localeCompare(right.threadId));
941
+ return result2.slice(0, options.limit);
870
942
  }
871
943
  __name(bucketByThread, "bucketByThread");
872
944
  function rollupThreadUsage(rows, prices) {
@@ -898,16 +970,16 @@ function bucketUsageTrend(rows, prices) {
898
970
  bucket.costUsd += rowCost(row, prices);
899
971
  byDay.set(row.day, bucket);
900
972
  }
901
- const result = [];
973
+ const result2 = [];
902
974
  for (const [day, bucket] of byDay) {
903
- result.push({
975
+ result2.push({
904
976
  day,
905
977
  totalTokens: bucket.totalTokens,
906
978
  costUsd: bucket.costUsd
907
979
  });
908
980
  }
909
- result.sort((left, right) => left.day.localeCompare(right.day));
910
- return result;
981
+ result2.sort((left, right) => left.day.localeCompare(right.day));
982
+ return result2;
911
983
  }
912
984
  __name(bucketUsageTrend, "bucketUsageTrend");
913
985
  function dayBoundsUtc(range) {
@@ -1351,13 +1423,13 @@ async function validateStructured(schema, text, reported) {
1351
1423
  issues: []
1352
1424
  };
1353
1425
  }
1354
- const result = await schema["~standard"].validate(candidate);
1355
- return result.issues !== void 0 ? {
1426
+ const result2 = await schema["~standard"].validate(candidate);
1427
+ return result2.issues !== void 0 ? {
1356
1428
  ok: false,
1357
- issues: result.issues
1429
+ issues: result2.issues
1358
1430
  } : {
1359
1431
  ok: true,
1360
- value: result.value
1432
+ value: result2.value
1361
1433
  };
1362
1434
  }
1363
1435
  __name(validateStructured, "validateStructured");
@@ -2536,12 +2608,12 @@ function memoryWriteVerdict(request) {
2536
2608
  };
2537
2609
  }
2538
2610
  __name(memoryWriteVerdict, "memoryWriteVerdict");
2539
- function memoryForgetVerdict({ record, actor }) {
2540
- return record.scope === actorScope(actor) ? {
2611
+ function memoryForgetVerdict({ record: record2, actor }) {
2612
+ return record2.scope === actorScope(actor) ? {
2541
2613
  allowed: true
2542
2614
  } : {
2543
2615
  allowed: false,
2544
- reason: `"${record.scope}" is not this actor's own scope; deleting it is an administrative action`
2616
+ reason: `"${record2.scope}" is not this actor's own scope; deleting it is an administrative action`
2545
2617
  };
2546
2618
  }
2547
2619
  __name(memoryForgetVerdict, "memoryForgetVerdict");
@@ -2584,7 +2656,7 @@ async function writeMemory({ config, digest, call, ctx, runId }) {
2584
2656
  error: verdict.reason
2585
2657
  };
2586
2658
  }
2587
- const record = await write({
2659
+ const record2 = await write({
2588
2660
  key,
2589
2661
  text,
2590
2662
  scope,
@@ -2598,7 +2670,7 @@ async function writeMemory({ config, digest, call, ctx, runId }) {
2598
2670
  });
2599
2671
  return {
2600
2672
  ok: true,
2601
- record
2673
+ record: record2
2602
2674
  };
2603
2675
  }
2604
2676
  __name(writeMemory, "writeMemory");
@@ -2650,7 +2722,7 @@ function danglingToolCallIds(messages) {
2650
2722
  if (message.role !== "assistant" || calls.length === 0) {
2651
2723
  continue;
2652
2724
  }
2653
- const settled = new Set((message.toolResults ?? []).map((result) => result.id));
2725
+ const settled = new Set((message.toolResults ?? []).map((result2) => result2.id));
2654
2726
  for (const call of calls) {
2655
2727
  if (!settled.has(call.id)) {
2656
2728
  ids.push(call.id);
@@ -2718,7 +2790,7 @@ function settleDanglingToolCalls(messages, outcomes = []) {
2718
2790
  return message;
2719
2791
  }
2720
2792
  const results = message.toolResults ?? [];
2721
- const settled = new Set(results.map((result) => result.id));
2793
+ const settled = new Set(results.map((result2) => result2.id));
2722
2794
  const missing = calls.filter((call) => !settled.has(call.id));
2723
2795
  if (missing.length === 0) {
2724
2796
  return message;
@@ -2826,6 +2898,1111 @@ async function settleUnsettledDelegation(args) {
2826
2898
  }
2827
2899
  __name(settleUnsettledDelegation, "settleUnsettledDelegation");
2828
2900
 
2901
+ // src/action-proposal-discovery.ts
2902
+ function validateActionProposalWorkerClaim(command, now) {
2903
+ if (!Number.isSafeInteger(now)) throw new RangeError("Invalid action proposal clock");
2904
+ if (typeof command.workerId !== "string" || command.workerId.length === 0) throw new TypeError("workerId must be a nonempty string");
2905
+ if (!Number.isSafeInteger(command.leaseMs) || command.leaseMs <= 0 || !Number.isSafeInteger(now + command.leaseMs)) throw new RangeError("leaseMs must be positive safe integer milliseconds");
2906
+ }
2907
+ __name(validateActionProposalWorkerClaim, "validateActionProposalWorkerClaim");
2908
+ function validateActionProposalExpiryBatch(command, now) {
2909
+ if (!Number.isSafeInteger(now)) throw new RangeError("Invalid action proposal clock");
2910
+ validateActionProposalDiscoveryIndexBatch(command);
2911
+ }
2912
+ __name(validateActionProposalExpiryBatch, "validateActionProposalExpiryBatch");
2913
+ function validateActionProposalDiscoveryIndexBatch(command) {
2914
+ if (!Number.isInteger(command.limit) || command.limit < 1 || command.limit > 1e3) throw new RangeError("limit must be an integer from 1 to 1000");
2915
+ }
2916
+ __name(validateActionProposalDiscoveryIndexBatch, "validateActionProposalDiscoveryIndexBatch");
2917
+
2918
+ // src/action-proposal-outcome.ts
2919
+ function attachActionProposalOutcome(row, now, ui = [], text) {
2920
+ if (row.outcome) return row;
2921
+ const execution = row.execution;
2922
+ const outcome = {
2923
+ id: `${row.id}:outcome:1`,
2924
+ proposalId: row.id,
2925
+ outcomeVersion: 1,
2926
+ tenantRef: row.tenantRef,
2927
+ actorRef: row.actorRef,
2928
+ threadId: row.threadId,
2929
+ originRunId: row.originRunId,
2930
+ originToolCallId: row.originToolCallId,
2931
+ toolName: row.toolName,
2932
+ decision: row.decision,
2933
+ ui,
2934
+ createdAt: now,
2935
+ ...text !== void 0 ? {
2936
+ text
2937
+ } : {},
2938
+ ...execution?.status === "succeeded" || execution?.status === "failed" ? {
2939
+ executionStatus: execution.status
2940
+ } : {},
2941
+ ...execution && Object.hasOwn(execution, "result") ? {
2942
+ result: execution.result
2943
+ } : {},
2944
+ ...execution?.error !== void 0 ? {
2945
+ error: execution.error
2946
+ } : row.decisionAudit?.reason !== void 0 ? {
2947
+ error: row.decisionAudit.reason
2948
+ } : {}
2949
+ };
2950
+ return snapshotActionProposal({
2951
+ ...row,
2952
+ outcome,
2953
+ outcomeDelivery: {
2954
+ status: "pending",
2955
+ generation: 0,
2956
+ lease: null
2957
+ }
2958
+ });
2959
+ }
2960
+ __name(attachActionProposalOutcome, "attachActionProposalOutcome");
2961
+ function claimActionProposalOutcome(row, command, now, token) {
2962
+ validateActionProposalWorkerClaim(command, now);
2963
+ const delivery = row.outcomeDelivery;
2964
+ if (!row.outcome || !delivery || delivery.status !== "pending" || delivery.lease && now < delivery.lease.expiresAt) return null;
2965
+ const generation = delivery.generation + 1;
2966
+ return snapshotActionProposal({
2967
+ ...row,
2968
+ outcomeDelivery: {
2969
+ ...delivery,
2970
+ generation,
2971
+ lease: {
2972
+ token,
2973
+ generation,
2974
+ workerId: command.workerId,
2975
+ expiresAt: now + command.leaseMs
2976
+ }
2977
+ }
2978
+ });
2979
+ }
2980
+ __name(claimActionProposalOutcome, "claimActionProposalOutcome");
2981
+ function actionProposalOutcomeFenceValid(row, command, now) {
2982
+ if (!Number.isSafeInteger(now)) throw new RangeError("Invalid server clock");
2983
+ if (typeof command.token !== "string" || !command.token || !Number.isSafeInteger(command.generation) || command.generation < 1 || typeof command.outcomeId !== "string" || !command.outcomeId) throw new TypeError("Invalid outcome fence");
2984
+ const delivery = row.outcomeDelivery;
2985
+ return row.outcome?.id === command.outcomeId && delivery?.status === "pending" && delivery.generation === command.generation && delivery.lease?.token === command.token && delivery.lease.generation === command.generation && now < delivery.lease.expiresAt;
2986
+ }
2987
+ __name(actionProposalOutcomeFenceValid, "actionProposalOutcomeFenceValid");
2988
+ function outcomeSummary(outcome) {
2989
+ if (outcome.executionStatus === "succeeded") return `Action ${JSON.stringify(outcome.toolName)} completed (proposal ${JSON.stringify(outcome.proposalId)}).${Object.hasOwn(outcome, "result") ? ` Result: ${JSON.stringify(outcome.result)}` : ""}`;
2990
+ if (outcome.executionStatus === "failed") return `Action ${JSON.stringify(outcome.toolName)} failed (proposal ${JSON.stringify(outcome.proposalId)}): ${JSON.stringify(outcome.error ?? "Execution failed")}`;
2991
+ return `Action ${JSON.stringify(outcome.toolName)} was ${outcome.decision} (proposal ${JSON.stringify(outcome.proposalId)}) and was not executed.${outcome.error ? ` Reason: ${JSON.stringify(outcome.error)}` : ""}`;
2992
+ }
2993
+ __name(outcomeSummary, "outcomeSummary");
2994
+ function actionProposalOutcomeScope(outcome) {
2995
+ return {
2996
+ tenantRef: outcome.tenantRef,
2997
+ actorRef: outcome.actorRef,
2998
+ threadId: outcome.threadId
2999
+ };
3000
+ }
3001
+ __name(actionProposalOutcomeScope, "actionProposalOutcomeScope");
3002
+ function actionProposalOutcomeText(outcome) {
3003
+ return outcomeSummary(outcome) + (outcome.text ? ` UI: ${JSON.stringify(outcome.text)}` : "");
3004
+ }
3005
+ __name(actionProposalOutcomeText, "actionProposalOutcomeText");
3006
+
3007
+ // src/genui/schema.ts
3008
+ function isStandardSchema(schema) {
3009
+ return typeof schema === "object" && schema !== null && "~standard" in schema && typeof schema["~standard"]?.validate === "function";
3010
+ }
3011
+ __name(isStandardSchema, "isStandardSchema");
3012
+ function toJsonSchema(schema) {
3013
+ if (!isStandardSchema(schema)) {
3014
+ return schema;
3015
+ }
3016
+ const standard = schema["~standard"];
3017
+ const converter = standard.jsonSchema;
3018
+ if (converter === void 0 || typeof converter.input !== "function") {
3019
+ return void 0;
3020
+ }
3021
+ try {
3022
+ return converter.input({
3023
+ target: "draft-07"
3024
+ });
3025
+ } catch {
3026
+ return void 0;
3027
+ }
3028
+ }
3029
+ __name(toJsonSchema, "toJsonSchema");
3030
+ async function validateProps(schema, value, validator) {
3031
+ if (isStandardSchema(schema)) {
3032
+ const result2 = await schema["~standard"].validate(value);
3033
+ if (result2.issues !== void 0) {
3034
+ return {
3035
+ ok: false,
3036
+ issues: toGenuiIssues(result2.issues)
3037
+ };
3038
+ }
3039
+ return {
3040
+ ok: true,
3041
+ value: result2.value
3042
+ };
3043
+ }
3044
+ const issues = validator.validate(schema, value);
3045
+ return issues.length > 0 ? {
3046
+ ok: false,
3047
+ issues
3048
+ } : {
3049
+ ok: true,
3050
+ value
3051
+ };
3052
+ }
3053
+ __name(validateProps, "validateProps");
3054
+ function validatePropsSync(schema, value, validator) {
3055
+ if (!isStandardSchema(schema)) {
3056
+ const issues = validator.validate(schema, value);
3057
+ return issues.length > 0 ? {
3058
+ ok: false,
3059
+ issues
3060
+ } : {
3061
+ ok: true,
3062
+ value
3063
+ };
3064
+ }
3065
+ const result2 = schema["~standard"].validate(value);
3066
+ if (result2 instanceof Promise) {
3067
+ result2.catch(() => void 0);
3068
+ return void 0;
3069
+ }
3070
+ return result2.issues !== void 0 ? {
3071
+ ok: false,
3072
+ issues: toGenuiIssues(result2.issues)
3073
+ } : {
3074
+ ok: true,
3075
+ value: result2.value
3076
+ };
3077
+ }
3078
+ __name(validatePropsSync, "validatePropsSync");
3079
+ function toGenuiIssues(issues) {
3080
+ return issues.map((issue4) => ({
3081
+ path: (issue4.path ?? []).map((segment) => typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment),
3082
+ message: issue4.message
3083
+ }));
3084
+ }
3085
+ __name(toGenuiIssues, "toGenuiIssues");
3086
+ function formatIssues(issues) {
3087
+ return issues.map((issue4) => `${issue4.path.length > 0 ? formatPath(issue4.path) : "(root)"}: ${issue4.message}`).join("; ");
3088
+ }
3089
+ __name(formatIssues, "formatIssues");
3090
+ function formatPath(path) {
3091
+ return path.map((segment, index) => typeof segment === "number" ? `[${segment}]` : index === 0 ? segment : `.${segment}`).join("");
3092
+ }
3093
+ __name(formatPath, "formatPath");
3094
+ var builtinJsonSchemaValidator = {
3095
+ validate(schema, value) {
3096
+ const issues = [];
3097
+ check(schema, value, [], schema, issues, 0);
3098
+ return issues;
3099
+ }
3100
+ };
3101
+ var MAX_DEPTH = 64;
3102
+ function typeOf(value) {
3103
+ if (value === null) return "null";
3104
+ if (Array.isArray(value)) return "array";
3105
+ return typeof value;
3106
+ }
3107
+ __name(typeOf, "typeOf");
3108
+ function matchesType(type, value) {
3109
+ switch (type) {
3110
+ case "integer":
3111
+ return typeof value === "number" && Number.isInteger(value);
3112
+ case "number":
3113
+ return typeof value === "number" && Number.isFinite(value);
3114
+ case "object":
3115
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3116
+ default:
3117
+ return typeOf(value) === type;
3118
+ }
3119
+ }
3120
+ __name(matchesType, "matchesType");
3121
+ function resolveRef(root, ref) {
3122
+ if (ref === "#") return root;
3123
+ if (!ref.startsWith("#/")) return void 0;
3124
+ let current = root;
3125
+ for (const raw of ref.slice(2).split("/")) {
3126
+ const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
3127
+ if (typeof current !== "object" || current === null) return void 0;
3128
+ current = current[key];
3129
+ }
3130
+ return typeof current === "object" && current !== null ? current : void 0;
3131
+ }
3132
+ __name(resolveRef, "resolveRef");
3133
+ function deepEqual(a, b) {
3134
+ if (a === b) return true;
3135
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
3136
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
3137
+ const keysA = Object.keys(a);
3138
+ const keysB = Object.keys(b);
3139
+ if (keysA.length !== keysB.length) return false;
3140
+ return keysA.every((key) => deepEqual(a[key], b[key]));
3141
+ }
3142
+ __name(deepEqual, "deepEqual");
3143
+ function check(schema, value, path, root, issues, depth) {
3144
+ if (schema === true || schema === void 0) return;
3145
+ if (schema === false) {
3146
+ issues.push({
3147
+ path,
3148
+ message: "is not allowed"
3149
+ });
3150
+ return;
3151
+ }
3152
+ if (typeof schema !== "object" || schema === null) return;
3153
+ if (depth > MAX_DEPTH) {
3154
+ issues.push({
3155
+ path,
3156
+ message: "is nested too deeply"
3157
+ });
3158
+ return;
3159
+ }
3160
+ const s = schema;
3161
+ if (typeof s.$ref === "string") {
3162
+ const target = resolveRef(root, s.$ref);
3163
+ if (target === void 0) {
3164
+ issues.push({
3165
+ path,
3166
+ message: `references unknown schema ${s.$ref}`
3167
+ });
3168
+ return;
3169
+ }
3170
+ check(target, value, path, root, issues, depth + 1);
3171
+ }
3172
+ if (s.type !== void 0) {
3173
+ const types = Array.isArray(s.type) ? s.type : [
3174
+ s.type
3175
+ ];
3176
+ if (!types.some((type) => matchesType(type, value))) {
3177
+ issues.push({
3178
+ path,
3179
+ message: `must be ${types.join(" or ")}`
3180
+ });
3181
+ return;
3182
+ }
3183
+ }
3184
+ if (Array.isArray(s.enum) && !s.enum.some((option) => deepEqual(option, value))) {
3185
+ issues.push({
3186
+ path,
3187
+ message: `must be one of ${s.enum.map((option) => JSON.stringify(option)).join(", ")}`
3188
+ });
3189
+ }
3190
+ if ("const" in s && !deepEqual(s.const, value)) {
3191
+ issues.push({
3192
+ path,
3193
+ message: `must be ${JSON.stringify(s.const)}`
3194
+ });
3195
+ }
3196
+ if (typeof value === "string") {
3197
+ if (typeof s.minLength === "number" && value.length < s.minLength) {
3198
+ issues.push({
3199
+ path,
3200
+ message: `must be at least ${s.minLength} characters`
3201
+ });
3202
+ }
3203
+ if (typeof s.maxLength === "number" && value.length > s.maxLength) {
3204
+ issues.push({
3205
+ path,
3206
+ message: `must be at most ${s.maxLength} characters`
3207
+ });
3208
+ }
3209
+ if (typeof s.pattern === "string") {
3210
+ let pattern;
3211
+ try {
3212
+ pattern = new RegExp(s.pattern, "u");
3213
+ } catch {
3214
+ pattern = void 0;
3215
+ }
3216
+ if (pattern !== void 0 && !pattern.test(value)) {
3217
+ issues.push({
3218
+ path,
3219
+ message: `must match ${s.pattern}`
3220
+ });
3221
+ }
3222
+ }
3223
+ }
3224
+ if (typeof value === "number") {
3225
+ if (typeof s.minimum === "number" && value < s.minimum) {
3226
+ issues.push({
3227
+ path,
3228
+ message: `must be >= ${s.minimum}`
3229
+ });
3230
+ }
3231
+ if (typeof s.maximum === "number" && value > s.maximum) {
3232
+ issues.push({
3233
+ path,
3234
+ message: `must be <= ${s.maximum}`
3235
+ });
3236
+ }
3237
+ }
3238
+ if (Array.isArray(value)) {
3239
+ if (typeof s.minItems === "number" && value.length < s.minItems) {
3240
+ issues.push({
3241
+ path,
3242
+ message: `must have at least ${s.minItems} items`
3243
+ });
3244
+ }
3245
+ if (typeof s.maxItems === "number" && value.length > s.maxItems) {
3246
+ issues.push({
3247
+ path,
3248
+ message: `must have at most ${s.maxItems} items`
3249
+ });
3250
+ }
3251
+ if (s.items !== void 0 && !Array.isArray(s.items)) {
3252
+ value.forEach((item, index) => {
3253
+ check(s.items, item, [
3254
+ ...path,
3255
+ index
3256
+ ], root, issues, depth + 1);
3257
+ });
3258
+ }
3259
+ }
3260
+ if (matchesType("object", value)) {
3261
+ const record2 = value;
3262
+ const properties = typeof s.properties === "object" && s.properties !== null ? s.properties : {};
3263
+ if (Array.isArray(s.required)) {
3264
+ for (const key of s.required) {
3265
+ if (record2[key] === void 0) {
3266
+ issues.push({
3267
+ path: [
3268
+ ...path,
3269
+ key
3270
+ ],
3271
+ message: "is required"
3272
+ });
3273
+ }
3274
+ }
3275
+ }
3276
+ for (const [key, entry] of Object.entries(record2)) {
3277
+ if (key in properties) {
3278
+ if (entry !== void 0) {
3279
+ check(properties[key], entry, [
3280
+ ...path,
3281
+ key
3282
+ ], root, issues, depth + 1);
3283
+ }
3284
+ } else if (s.additionalProperties === false) {
3285
+ issues.push({
3286
+ path: [
3287
+ ...path,
3288
+ key
3289
+ ],
3290
+ message: "is not a known property"
3291
+ });
3292
+ } else if (typeof s.additionalProperties === "object" && s.additionalProperties !== null) {
3293
+ check(s.additionalProperties, entry, [
3294
+ ...path,
3295
+ key
3296
+ ], root, issues, depth + 1);
3297
+ }
3298
+ }
3299
+ }
3300
+ if (Array.isArray(s.allOf)) {
3301
+ for (const sub of s.allOf) check(sub, value, path, root, issues, depth + 1);
3302
+ }
3303
+ if (Array.isArray(s.anyOf) || Array.isArray(s.oneOf)) {
3304
+ const options = Array.isArray(s.anyOf) ? s.anyOf : s.oneOf;
3305
+ const passing = options.filter((sub) => {
3306
+ const nested = [];
3307
+ check(sub, value, path, root, nested, depth + 1);
3308
+ return nested.length === 0;
3309
+ }).length;
3310
+ if (Array.isArray(s.anyOf) ? passing === 0 : passing !== 1) {
3311
+ issues.push({
3312
+ path,
3313
+ message: Array.isArray(s.anyOf) ? "does not match any allowed shape" : "must match exactly one allowed shape"
3314
+ });
3315
+ }
3316
+ }
3317
+ if (s.not !== void 0) {
3318
+ const nested = [];
3319
+ check(s.not, value, path, root, nested, depth + 1);
3320
+ if (nested.length === 0) {
3321
+ issues.push({
3322
+ path,
3323
+ message: "matches a shape that is not allowed"
3324
+ });
3325
+ }
3326
+ }
3327
+ }
3328
+ __name(check, "check");
3329
+
3330
+ // src/genui/catalog.ts
3331
+ var COMPONENT_NAME = /^[A-Za-z][A-Za-z0-9]{0,63}$/;
3332
+ function defineComponent(definition) {
3333
+ if (!COMPONENT_NAME.test(definition.name)) {
3334
+ throw new Error(`genui: component name "${definition.name}" must be letters and digits, starting with a letter (e.g. DataTable)`);
3335
+ }
3336
+ if (definition.version !== void 0 && (!Number.isSafeInteger(definition.version) || definition.version <= 0)) throw new RangeError("genui: component version must be a positive safe integer");
3337
+ if (definition.fallbackText !== void 0 && typeof definition.fallbackText !== "function") throw new TypeError("genui: fallbackText must be a function");
3338
+ return definition;
3339
+ }
3340
+ __name(defineComponent, "defineComponent");
3341
+ function defineCatalog(components, options = {}) {
3342
+ const byName = /* @__PURE__ */ new Map();
3343
+ for (const component of components) {
3344
+ defineComponent(component);
3345
+ if (byName.has(component.name)) {
3346
+ throw new Error(`genui: component "${component.name}" is defined twice in one catalog`);
3347
+ }
3348
+ byName.set(component.name, component);
3349
+ }
3350
+ return buildCatalog(byName, options.jsonSchemaValidator ?? builtinJsonSchemaValidator);
3351
+ }
3352
+ __name(defineCatalog, "defineCatalog");
3353
+ function buildCatalog(byName, validator) {
3354
+ const list = Object.freeze([
3355
+ ...byName.values()
3356
+ ]);
3357
+ return {
3358
+ components: list,
3359
+ validator,
3360
+ get: /* @__PURE__ */ __name((name) => byName.get(name), "get"),
3361
+ has: /* @__PURE__ */ __name((name) => byName.has(name), "has"),
3362
+ modelComponents: /* @__PURE__ */ __name(() => list.filter((component) => component.internal !== true), "modelComponents"),
3363
+ async validate(component, props) {
3364
+ const definition = byName.get(component);
3365
+ if (definition === void 0) {
3366
+ return {
3367
+ ok: false,
3368
+ issues: [
3369
+ {
3370
+ path: [],
3371
+ message: `unknown component "${component}"`
3372
+ }
3373
+ ]
3374
+ };
3375
+ }
3376
+ const result2 = await validateProps(definition.props, props, validator);
3377
+ return result2;
3378
+ },
3379
+ validateSync(component, props) {
3380
+ const definition = byName.get(component);
3381
+ if (definition === void 0) {
3382
+ return {
3383
+ ok: false,
3384
+ issues: [
3385
+ {
3386
+ path: [],
3387
+ message: `unknown component "${component}"`
3388
+ }
3389
+ ]
3390
+ };
3391
+ }
3392
+ return validatePropsSync(definition.props, props, validator);
3393
+ },
3394
+ jsonSchemaFor(component) {
3395
+ const definition = byName.get(component);
3396
+ return definition === void 0 ? void 0 : toJsonSchema(definition.props);
3397
+ },
3398
+ extend(more) {
3399
+ const next = new Map(byName);
3400
+ for (const component of more) {
3401
+ defineComponent(component);
3402
+ next.set(component.name, component);
3403
+ }
3404
+ return buildCatalog(next, validator);
3405
+ }
3406
+ };
3407
+ }
3408
+ __name(buildCatalog, "buildCatalog");
3409
+
3410
+ // src/genui/tree.ts
3411
+ var GENUI_TREE_COMPONENT = "genui:tree";
3412
+ async function validateTree(catalog, tree, limits = {}) {
3413
+ const maxDepth = limits.maxDepth ?? 12;
3414
+ const maxNodes = limits.maxNodes ?? 200;
3415
+ const issues = [];
3416
+ let nodes = 0;
3417
+ const visit = /* @__PURE__ */ __name(async (node, path, depth) => {
3418
+ nodes += 1;
3419
+ if (nodes > maxNodes) {
3420
+ if (nodes === maxNodes + 1) {
3421
+ issues.push({
3422
+ path,
3423
+ message: `the tree has more than ${maxNodes} elements`
3424
+ });
3425
+ }
3426
+ return void 0;
3427
+ }
3428
+ if (depth > maxDepth) {
3429
+ issues.push({
3430
+ path,
3431
+ message: `the tree is nested deeper than ${maxDepth} levels`
3432
+ });
3433
+ return void 0;
3434
+ }
3435
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
3436
+ issues.push({
3437
+ path,
3438
+ message: "must be an element object { type, props, children? }"
3439
+ });
3440
+ return void 0;
3441
+ }
3442
+ const element = node;
3443
+ if (typeof element.type !== "string") {
3444
+ issues.push({
3445
+ path: [
3446
+ ...path,
3447
+ "type"
3448
+ ],
3449
+ message: "must be a component name"
3450
+ });
3451
+ return void 0;
3452
+ }
3453
+ const definition = catalog.get(element.type);
3454
+ if (definition === void 0 || definition.internal === true) {
3455
+ issues.push({
3456
+ path: [
3457
+ ...path,
3458
+ "type"
3459
+ ],
3460
+ message: `unknown component "${element.type}" (allowed: ${catalog.modelComponents().map((component) => component.name).join(", ")})`
3461
+ });
3462
+ return void 0;
3463
+ }
3464
+ const validated = await catalog.validate(element.type, element.props ?? {});
3465
+ let props = {};
3466
+ if (!validated.ok) {
3467
+ for (const issue4 of validated.issues) {
3468
+ issues.push({
3469
+ path: [
3470
+ ...path,
3471
+ "props",
3472
+ ...issue4.path
3473
+ ],
3474
+ message: issue4.message
3475
+ });
3476
+ }
3477
+ } else {
3478
+ props = validated.value;
3479
+ }
3480
+ const rawChildren = parseChildrenIfStringified(element.children);
3481
+ if (rawChildren === void 0 || rawChildren === null) {
3482
+ return {
3483
+ type: element.type,
3484
+ props
3485
+ };
3486
+ }
3487
+ if (!Array.isArray(rawChildren)) {
3488
+ issues.push({
3489
+ path: [
3490
+ ...path,
3491
+ "children"
3492
+ ],
3493
+ message: "must be an array of elements (a literal array, not a string)"
3494
+ });
3495
+ return void 0;
3496
+ }
3497
+ if (rawChildren.length > 0 && definition.children !== true) {
3498
+ issues.push({
3499
+ path: [
3500
+ ...path,
3501
+ "children"
3502
+ ],
3503
+ message: `${element.type} does not take children`
3504
+ });
3505
+ return void 0;
3506
+ }
3507
+ const children = [];
3508
+ for (const [index, child] of rawChildren.entries()) {
3509
+ const next = await visit(child, [
3510
+ ...path,
3511
+ "children",
3512
+ index
3513
+ ], depth + 1);
3514
+ if (next !== void 0) {
3515
+ children.push(next);
3516
+ }
3517
+ }
3518
+ return children.length > 0 ? {
3519
+ type: element.type,
3520
+ props,
3521
+ children
3522
+ } : {
3523
+ type: element.type,
3524
+ props
3525
+ };
3526
+ }, "visit");
3527
+ const root = await visit(tree, [], 1);
3528
+ return issues.length > 0 || root === void 0 ? {
3529
+ ok: false,
3530
+ issues: issues.length > 0 ? issues : [
3531
+ {
3532
+ path: [],
3533
+ message: "is invalid"
3534
+ }
3535
+ ]
3536
+ } : {
3537
+ ok: true,
3538
+ value: root
3539
+ };
3540
+ }
3541
+ __name(validateTree, "validateTree");
3542
+ function parseChildrenIfStringified(value) {
3543
+ if (typeof value !== "string") return value;
3544
+ const trimmed = value.trim();
3545
+ if (!trimmed.startsWith("[")) return value;
3546
+ return leadingJsonArray(trimmed) ?? value;
3547
+ }
3548
+ __name(parseChildrenIfStringified, "parseChildrenIfStringified");
3549
+ function leadingJsonArray(text) {
3550
+ let depth = 0;
3551
+ let inString = false;
3552
+ let escaped = false;
3553
+ for (let index = 0; index < text.length; index += 1) {
3554
+ const char = text[index];
3555
+ if (inString) {
3556
+ if (escaped) escaped = false;
3557
+ else if (char === "\\") escaped = true;
3558
+ else if (char === '"') inString = false;
3559
+ continue;
3560
+ }
3561
+ if (char === '"') inString = true;
3562
+ else if (char === "[" || char === "{") depth += 1;
3563
+ else if (char === "]" || char === "}") {
3564
+ depth -= 1;
3565
+ if (depth === 0) {
3566
+ try {
3567
+ const parsed = JSON.parse(text.slice(0, index + 1));
3568
+ return Array.isArray(parsed) ? parsed : void 0;
3569
+ } catch {
3570
+ return void 0;
3571
+ }
3572
+ }
3573
+ }
3574
+ }
3575
+ return void 0;
3576
+ }
3577
+ __name(leadingJsonArray, "leadingJsonArray");
3578
+
3579
+ // src/genui/text.ts
3580
+ function componentToText(catalog, component, props) {
3581
+ if (component === GENUI_TREE_COMPONENT) {
3582
+ const root = props.root;
3583
+ return root === void 0 ? "" : treeToText(catalog, root);
3584
+ }
3585
+ const fallback = catalog.get(component)?.fallbackText;
3586
+ if (fallback !== void 0) {
3587
+ try {
3588
+ const text = fallback(props);
3589
+ if (typeof text === "string" && text.trim().length > 0) return text;
3590
+ } catch {
3591
+ }
3592
+ }
3593
+ return jsonBlock(props);
3594
+ }
3595
+ __name(componentToText, "componentToText");
3596
+ function treeToText(catalog, root) {
3597
+ const out = [];
3598
+ const visit = /* @__PURE__ */ __name((node, depth) => {
3599
+ if (depth > 32) return;
3600
+ const definition = catalog.get(node.type);
3601
+ if (definition?.fallbackText !== void 0) {
3602
+ try {
3603
+ const text = definition.fallbackText(node.props);
3604
+ out.push(typeof text === "string" && text.trim().length > 0 ? text : jsonBlock(node.props));
3605
+ } catch {
3606
+ out.push(jsonBlock(node.props));
3607
+ }
3608
+ } else if (definition?.children !== true) {
3609
+ out.push(jsonBlock(node.props));
3610
+ }
3611
+ for (const child of node.children ?? []) visit(child, depth + 1);
3612
+ }, "visit");
3613
+ visit(root, 0);
3614
+ return out.filter((line) => line.trim().length > 0).join("\n");
3615
+ }
3616
+ __name(treeToText, "treeToText");
3617
+ function jsonBlock(props) {
3618
+ const title = typeof props.title === "string" && props.title.length > 0 ? `*${props.title}*
3619
+ ` : "";
3620
+ return `${title}\`\`\`
3621
+ ${JSON.stringify(props, null, 2)}
3622
+ \`\`\``;
3623
+ }
3624
+ __name(jsonBlock, "jsonBlock");
3625
+
3626
+ // src/genui/capabilities.ts
3627
+ function validateUiCapabilities(value) {
3628
+ if (!record(value) || Object.keys(value).some((key) => key !== "components") || !Array.isArray(value.components)) throw new TypeError("Invalid UI capabilities");
3629
+ const seen = /* @__PURE__ */ new Set();
3630
+ const components = value.components.map((component) => {
3631
+ if (!record(component) || Object.keys(component).some((key) => key !== "name" && key !== "version") || typeof component.name !== "string" || !COMPONENT_NAME.test(component.name) || !Number.isSafeInteger(component.version) || Number(component.version) <= 0 || seen.has(component.name)) throw new TypeError("Invalid UI component capability");
3632
+ seen.add(component.name);
3633
+ return {
3634
+ name: component.name,
3635
+ version: Number(component.version)
3636
+ };
3637
+ });
3638
+ return {
3639
+ components
3640
+ };
3641
+ }
3642
+ __name(validateUiCapabilities, "validateUiCapabilities");
3643
+ function record(value) {
3644
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3645
+ }
3646
+ __name(record, "record");
3647
+ function negotiateCatalog(catalog, capabilities) {
3648
+ if (capabilities === void 0) return catalog;
3649
+ const supported = validateUiCapabilities(capabilities).components;
3650
+ return defineCatalog(catalog.components.filter((component) => supported.some((entry) => entry.name === component.name && entry.version === (component.version ?? 1))), {
3651
+ jsonSchemaValidator: catalog.validator
3652
+ });
3653
+ }
3654
+ __name(negotiateCatalog, "negotiateCatalog");
3655
+ async function prepareUiEmission(catalog, capabilities, component, props, version) {
3656
+ const drawable = negotiateCatalog(catalog, capabilities);
3657
+ if (component === GENUI_TREE_COMPONENT) {
3658
+ if (version !== void 0 && version !== 1) throw new Error("Invalid UI tree version");
3659
+ const validated2 = await validateTree(catalog, props.root);
3660
+ if (!validated2.ok) throw new Error(`Invalid UI tree: ${formatIssues(validated2.issues)}`);
3661
+ const supported = /* @__PURE__ */ __name((node) => drawable.has(node.type) && (node.children ?? []).every(supported), "supported");
3662
+ const versions = /* @__PURE__ */ new Map();
3663
+ const collectVersions = /* @__PURE__ */ __name((node) => {
3664
+ versions.set(node.type, catalog.get(node.type)?.version ?? 1);
3665
+ for (const child of node.children ?? []) collectVersions(child);
3666
+ }, "collectVersions");
3667
+ collectVersions(validated2.value);
3668
+ return supported(validated2.value) ? {
3669
+ kind: "ui",
3670
+ component,
3671
+ props: {
3672
+ root: validated2.value
3673
+ },
3674
+ version: 1,
3675
+ fallbackText: treeToText(catalog, validated2.value),
3676
+ componentVersions: Object.fromEntries(versions)
3677
+ } : {
3678
+ kind: "text",
3679
+ text: treeToText(catalog, validated2.value)
3680
+ };
3681
+ }
3682
+ const definition = catalog.get(component);
3683
+ if (definition === void 0) throw new Error(`Unknown UI component "${component}"`);
3684
+ const currentVersion = definition.version ?? 1;
3685
+ if (version !== void 0 && version !== currentVersion) throw new Error(`Invalid UI component version for "${component}"`);
3686
+ const validated = await catalog.validate(component, props);
3687
+ if (!validated.ok) throw new Error(`Invalid UI props: ${formatIssues(validated.issues)}`);
3688
+ return drawable.has(component) ? {
3689
+ kind: "ui",
3690
+ component,
3691
+ props: validated.value,
3692
+ version: currentVersion,
3693
+ fallbackText: componentToText(catalog, component, validated.value)
3694
+ } : {
3695
+ kind: "text",
3696
+ text: componentToText(catalog, component, validated.value)
3697
+ };
3698
+ }
3699
+ __name(prepareUiEmission, "prepareUiEmission");
3700
+
3701
+ // src/action-proposal-transitions.ts
3702
+ function checkTime(value) {
3703
+ if (!Number.isSafeInteger(value)) throw new RangeError("Action proposal timestamps must be safe integer milliseconds");
3704
+ }
3705
+ __name(checkTime, "checkTime");
3706
+ function checkString(value) {
3707
+ if (typeof value !== "string" || value.length === 0) throw new TypeError("Action proposal identifiers must be nonempty strings");
3708
+ }
3709
+ __name(checkString, "checkString");
3710
+ function validateActionProposalCreation(input) {
3711
+ const allowed = [
3712
+ "id",
3713
+ "tenantRef",
3714
+ "actorRef",
3715
+ "threadId",
3716
+ "originRunId",
3717
+ "originMessageId",
3718
+ "originToolCallId",
3719
+ "toolName",
3720
+ "input",
3721
+ "preparationInput",
3722
+ "executionContext",
3723
+ "confirmation",
3724
+ "approver",
3725
+ "expiresAt",
3726
+ "idempotencyKey",
3727
+ "replacementKey"
3728
+ ];
3729
+ if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("Unknown action proposal creation field");
3730
+ for (const key of [
3731
+ "id",
3732
+ "actorRef",
3733
+ "threadId",
3734
+ "originRunId",
3735
+ "originMessageId",
3736
+ "originToolCallId",
3737
+ "toolName",
3738
+ "approver",
3739
+ "idempotencyKey"
3740
+ ]) checkString(input[key]);
3741
+ if (input.tenantRef !== null && typeof input.tenantRef !== "string") throw new TypeError("tenantRef must be an explicit string or null");
3742
+ for (const value of [
3743
+ input.id,
3744
+ input.actorRef,
3745
+ input.threadId,
3746
+ input.tenantRef
3747
+ ]) {
3748
+ if (value !== null && value.length > 255) throw new RangeError("Action proposal id and scope must fit 255 UTF-16 code units");
3749
+ }
3750
+ if (input.replacementKey !== void 0) {
3751
+ checkString(input.replacementKey);
3752
+ if (input.replacementKey.length > 255) throw new RangeError("replacementKey must fit 255 UTF-16 code units");
3753
+ }
3754
+ if (Object.hasOwn(input, "executionContext")) {
3755
+ const context = input.executionContext;
3756
+ if (context === null || typeof context !== "object" || Array.isArray(context)) throw new TypeError("executionContext must be a JSON object");
3757
+ const allowedContext = [
3758
+ "agentName",
3759
+ "persona",
3760
+ "requestId",
3761
+ "pageContext",
3762
+ "uiCapabilities"
3763
+ ];
3764
+ if (Object.keys(context).some((key) => !allowedContext.includes(key))) throw new TypeError("Unknown execution context field");
3765
+ checkString(context.requestId);
3766
+ if (Object.hasOwn(context, "uiCapabilities")) validateUiCapabilities(context.uiCapabilities);
3767
+ if (Object.hasOwn(context, "agentName")) checkString(context.agentName);
3768
+ if (Object.hasOwn(context, "persona")) checkString(context.persona);
3769
+ if (Object.hasOwn(context, "pageContext")) {
3770
+ const page = context.pageContext;
3771
+ if (page === null || typeof page !== "object" || Array.isArray(page)) throw new TypeError("pageContext must be a JSON object");
3772
+ if (Object.hasOwn(page, "kind") && typeof page.kind !== "string") throw new TypeError("pageContext.kind must be a string");
3773
+ }
3774
+ }
3775
+ if (input.expiresAt !== null) checkTime(input.expiresAt);
3776
+ checkString(input.confirmation?.title);
3777
+ checkString(input.confirmation?.verb);
3778
+ canonicalActionProposalJson(input);
3779
+ }
3780
+ __name(validateActionProposalCreation, "validateActionProposalCreation");
3781
+ function validateActionProposalListQuery(query = {}) {
3782
+ const limit = query.limit === void 0 ? 100 : query.limit;
3783
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) throw new RangeError("limit must be an integer from 1 to 1000");
3784
+ if (query.decision !== void 0 && ![
3785
+ "pending",
3786
+ "approved",
3787
+ "rejected",
3788
+ "expired",
3789
+ "superseded"
3790
+ ].includes(query.decision)) throw new TypeError("Unsupported action proposal list decision");
3791
+ if (query.after !== void 0) {
3792
+ const after = query.after;
3793
+ if (typeof after !== "object" || after === null || Array.isArray(after) || Object.keys(after).some((key) => key !== "createdAt" && key !== "id")) throw new TypeError("Invalid action proposal cursor");
3794
+ checkTime(after.createdAt);
3795
+ checkString(after.id);
3796
+ if (after.id.length > 255) throw new TypeError("Action proposal cursor id is too long");
3797
+ }
3798
+ return limit;
3799
+ }
3800
+ __name(validateActionProposalListQuery, "validateActionProposalListQuery");
3801
+ function canonicalActionProposalJson(value) {
3802
+ const ancestors = /* @__PURE__ */ new Set();
3803
+ const sort = /* @__PURE__ */ __name((item) => {
3804
+ if (item === null || typeof item === "string" || typeof item === "boolean") return item;
3805
+ if (typeof item === "number" && Number.isFinite(item)) return item;
3806
+ if (typeof item !== "object" || item === null || ancestors.has(item)) throw new TypeError("Action proposals require finite, acyclic JSON values");
3807
+ const prototype = Object.getPrototypeOf(item);
3808
+ if (!Array.isArray(item) && prototype !== Object.prototype && prototype !== null) throw new TypeError("Action proposals require plain JSON objects");
3809
+ ancestors.add(item);
3810
+ const sorted = Array.isArray(item) ? Array.from(item, sort) : Object.fromEntries(Object.keys(item).sort().map((key) => [
3811
+ key,
3812
+ sort(item[key])
3813
+ ]));
3814
+ ancestors.delete(item);
3815
+ return sorted;
3816
+ }, "sort");
3817
+ return JSON.stringify(sort(value));
3818
+ }
3819
+ __name(canonicalActionProposalJson, "canonicalActionProposalJson");
3820
+ function snapshotActionProposal(value) {
3821
+ return JSON.parse(canonicalActionProposalJson(value));
3822
+ }
3823
+ __name(snapshotActionProposal, "snapshotActionProposal");
3824
+ function actionProposalScopeMatches(row, scope) {
3825
+ return row.tenantRef === scope.tenantRef && row.actorRef === scope.actorRef && row.threadId === scope.threadId;
3826
+ }
3827
+ __name(actionProposalScopeMatches, "actionProposalScopeMatches");
3828
+ function initialActionProposal(input, now) {
3829
+ validateActionProposalCreation(input);
3830
+ checkTime(now);
3831
+ return snapshotActionProposal({
3832
+ ...input,
3833
+ decision: "pending",
3834
+ decisionAudit: null,
3835
+ execution: null,
3836
+ createdAt: now,
3837
+ updatedAt: now
3838
+ });
3839
+ }
3840
+ __name(initialActionProposal, "initialActionProposal");
3841
+ function actionProposalCreationMatches(row, input) {
3842
+ validateActionProposalCreation(input);
3843
+ const { decision: _decision, decisionAudit: _audit, execution: _execution, createdAt: _created, updatedAt: _updated, supersededBy: _supersededBy, outcome: _outcome, outcomeDelivery: _delivery, ...original } = row;
3844
+ return canonicalActionProposalJson(original) === canonicalActionProposalJson(input);
3845
+ }
3846
+ __name(actionProposalCreationMatches, "actionProposalCreationMatches");
3847
+ function result(status, proposal) {
3848
+ return {
3849
+ status,
3850
+ proposal: snapshotActionProposal(proposal)
3851
+ };
3852
+ }
3853
+ __name(result, "result");
3854
+ function transitionActionProposalDecision(row, command, now) {
3855
+ checkTime(now);
3856
+ if (![
3857
+ "approved",
3858
+ "rejected",
3859
+ "expired"
3860
+ ].includes(command.decision)) throw new TypeError("Unsupported action proposal decision");
3861
+ checkString(command.actorRef);
3862
+ checkString(command.via);
3863
+ if (command.reason !== void 0 && typeof command.reason !== "string") throw new TypeError("reason must be a string");
3864
+ if (command.remember !== void 0 && typeof command.remember !== "boolean") throw new TypeError("remember must be a boolean");
3865
+ if (row.decision !== "pending") {
3866
+ return result(row.decision === command.decision ? "unchanged" : row.decision === "expired" ? "expired" : "conflict", row);
3867
+ }
3868
+ const next = snapshotActionProposal(row);
3869
+ if (row.expiresAt !== null && now >= row.expiresAt) {
3870
+ next.decision = "expired";
3871
+ next.decisionAudit = {
3872
+ actorRef: "system",
3873
+ via: "expiry",
3874
+ at: now
3875
+ };
3876
+ next.updatedAt = now;
3877
+ return result(command.decision === "expired" ? "applied" : "expired", attachActionProposalOutcome(next, now));
3878
+ }
3879
+ if (command.decision === "expired") return result("conflict", row);
3880
+ const { decision, ...audit } = command;
3881
+ next.decision = decision;
3882
+ next.decisionAudit = {
3883
+ actorRef: audit.actorRef,
3884
+ via: audit.via,
3885
+ at: now,
3886
+ ...audit.reason === void 0 ? {} : {
3887
+ reason: audit.reason
3888
+ },
3889
+ ...audit.remember === void 0 ? {} : {
3890
+ remember: audit.remember
3891
+ }
3892
+ };
3893
+ next.updatedAt = now;
3894
+ if (decision === "approved") next.execution = {
3895
+ status: "queued",
3896
+ generation: 0,
3897
+ lease: null
3898
+ };
3899
+ return result("applied", decision === "approved" ? next : attachActionProposalOutcome(next, now));
3900
+ }
3901
+ __name(transitionActionProposalDecision, "transitionActionProposalDecision");
3902
+ function checkLeaseMs(leaseMs, now) {
3903
+ checkTime(now);
3904
+ if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0 || !Number.isSafeInteger(now + leaseMs)) throw new RangeError("leaseMs must be finite and positive");
3905
+ }
3906
+ __name(checkLeaseMs, "checkLeaseMs");
3907
+ function transitionActionProposalClaim(row, command, now, token) {
3908
+ checkLeaseMs(command.leaseMs, now);
3909
+ checkString(command.workerId);
3910
+ checkString(token);
3911
+ const execution = row.execution;
3912
+ if (row.decision !== "approved" || !execution || execution.status !== "queued" && !(execution.status === "executing" && execution.lease && now >= execution.lease.expiresAt)) return result("conflict", row);
3913
+ const next = snapshotActionProposal(row);
3914
+ const generation = execution.generation + 1;
3915
+ next.execution = {
3916
+ status: "executing",
3917
+ generation,
3918
+ lease: {
3919
+ token,
3920
+ generation,
3921
+ workerId: command.workerId,
3922
+ expiresAt: now + command.leaseMs
3923
+ }
3924
+ };
3925
+ next.updatedAt = now;
3926
+ return result("applied", next);
3927
+ }
3928
+ __name(transitionActionProposalClaim, "transitionActionProposalClaim");
3929
+ function leaseStatus(row, command, now) {
3930
+ checkTime(now);
3931
+ checkString(command.token);
3932
+ if (!Number.isSafeInteger(command.generation) || command.generation < 1) throw new RangeError("Invalid lease generation");
3933
+ const work = row.execution;
3934
+ if (row.decision !== "approved" || work?.status !== "executing" || !work.lease || work.generation !== command.generation || work.lease.generation !== command.generation || work.lease.token !== command.token) return "conflict";
3935
+ return now >= work.lease.expiresAt ? "expired" : "valid";
3936
+ }
3937
+ __name(leaseStatus, "leaseStatus");
3938
+ function transitionActionProposalLease(row, command, now) {
3939
+ checkLeaseMs(command.leaseMs, now);
3940
+ const status = leaseStatus(row, command, now);
3941
+ if (status !== "valid") return result(status, row);
3942
+ const next = snapshotActionProposal(row);
3943
+ if (next.execution?.lease) next.execution.lease.expiresAt = Math.max(next.execution.lease.expiresAt, now + command.leaseMs);
3944
+ next.updatedAt = now;
3945
+ return result("applied", next);
3946
+ }
3947
+ __name(transitionActionProposalLease, "transitionActionProposalLease");
3948
+ function transitionActionProposalSettlement(row, command, now) {
3949
+ checkTime(now);
3950
+ if (command.status !== "succeeded" && command.status !== "failed") throw new TypeError("Unsupported settlement status");
3951
+ if (command.status === "succeeded" && command.error !== void 0 || command.status === "failed" && (typeof command.error !== "string" || command.result !== void 0)) throw new TypeError("Ambiguous action proposal settlement");
3952
+ const status = leaseStatus(row, command, now);
3953
+ if (status !== "valid") return result(status, row);
3954
+ const next = snapshotActionProposal(row);
3955
+ next.execution = command.status === "succeeded" ? {
3956
+ status: "succeeded",
3957
+ generation: command.generation,
3958
+ lease: null,
3959
+ ...command.result === void 0 ? {} : {
3960
+ result: command.result
3961
+ }
3962
+ } : {
3963
+ status: "failed",
3964
+ generation: command.generation,
3965
+ lease: null,
3966
+ error: command.error
3967
+ };
3968
+ if (command.ui !== void 0) canonicalActionProposalJson(command.ui);
3969
+ if (command.text !== void 0 && typeof command.text !== "string") throw new TypeError("Outcome text must be a string");
3970
+ next.updatedAt = now;
3971
+ return result("applied", attachActionProposalOutcome(next, now, command.ui, command.text));
3972
+ }
3973
+ __name(transitionActionProposalSettlement, "transitionActionProposalSettlement");
3974
+ function transitionActionProposalSupersession(row, replacement, command, now) {
3975
+ checkTime(now);
3976
+ checkString(command.replacementProposalId);
3977
+ checkString(command.actorRef);
3978
+ checkString(command.via);
3979
+ if (replacement.id !== command.replacementProposalId || row.id === replacement.id || !actionProposalScopeMatches(row, replacement) || row.toolName !== replacement.toolName || !row.replacementKey || row.replacementKey !== replacement.replacementKey) return result("conflict", row);
3980
+ if (row.decision === "superseded") return result(row.supersededBy === replacement.id ? "unchanged" : "conflict", row);
3981
+ if (row.decision !== "pending") return result("conflict", row);
3982
+ if (row.expiresAt !== null && now >= row.expiresAt) return {
3983
+ ...transitionActionProposalDecision(row, {
3984
+ decision: "expired",
3985
+ actorRef: "system",
3986
+ via: "expiry"
3987
+ }, now),
3988
+ status: "expired"
3989
+ };
3990
+ const next = snapshotActionProposal({
3991
+ ...row,
3992
+ decision: "superseded",
3993
+ supersededBy: replacement.id,
3994
+ decisionAudit: {
3995
+ actorRef: command.actorRef,
3996
+ via: command.via,
3997
+ replacementProposalId: replacement.id,
3998
+ at: now
3999
+ },
4000
+ updatedAt: now
4001
+ });
4002
+ return result("applied", attachActionProposalOutcome(next, now));
4003
+ }
4004
+ __name(transitionActionProposalSupersession, "transitionActionProposalSupersession");
4005
+
2829
4006
  // src/tool-registry.ts
2830
4007
  var ToolForbiddenError = class extends Error {
2831
4008
  static {
@@ -2868,6 +4045,35 @@ var ToolInputInvalidError = class extends Error {
2868
4045
  this.name = "ToolInputInvalidError";
2869
4046
  }
2870
4047
  };
4048
+ var ToolPreflightDeniedError = class extends Error {
4049
+ static {
4050
+ __name(this, "ToolPreflightDeniedError");
4051
+ }
4052
+ toolName;
4053
+ reason;
4054
+ constructor(toolName, reason) {
4055
+ super(reason), this.toolName = toolName, this.reason = reason;
4056
+ this.name = "ToolPreflightDeniedError";
4057
+ }
4058
+ };
4059
+ var ToolInputDriftError = class extends Error {
4060
+ static {
4061
+ __name(this, "ToolInputDriftError");
4062
+ }
4063
+ toolName;
4064
+ constructor(toolName) {
4065
+ super(`Tool "${toolName}" input differs from its trusted snapshot`), this.toolName = toolName;
4066
+ this.name = "ToolInputDriftError";
4067
+ }
4068
+ };
4069
+ function assertTrustedInput(name, value, approved) {
4070
+ try {
4071
+ if (canonicalActionProposalJson(value) === canonicalActionProposalJson(approved)) return;
4072
+ } catch {
4073
+ }
4074
+ throw new ToolInputDriftError(name);
4075
+ }
4076
+ __name(assertTrustedInput, "assertTrustedInput");
2871
4077
  var ToolRegistry = class {
2872
4078
  static {
2873
4079
  __name(this, "ToolRegistry");
@@ -2915,11 +4121,12 @@ var ToolRegistry = class {
2915
4121
  */
2916
4122
  async definitionsFor(actor, policy, allowedTools, scope = {}) {
2917
4123
  const visible = await this.visibleEntries(actor, policy, allowedTools);
2918
- return Promise.all(visible.map(async ({ spec, handler }) => {
4124
+ const definitions = await Promise.all(visible.map(async ({ spec, handler }) => {
2919
4125
  const override = handler.describe === void 0 ? void 0 : await handler.describe({
2920
4126
  actor,
2921
4127
  ...scope
2922
4128
  });
4129
+ if (override?.available === false) return null;
2923
4130
  return {
2924
4131
  name: spec.name,
2925
4132
  kind: spec.kind,
@@ -2927,6 +4134,7 @@ var ToolRegistry = class {
2927
4134
  inputSchema: override?.inputSchema ?? spec.inputSchema
2928
4135
  };
2929
4136
  }));
4137
+ return definitions.filter((definition) => definition !== null);
2930
4138
  }
2931
4139
  /**
2932
4140
  * The specs {@link definitionsFor} offers the model, whole — the same four gates (allow-list,
@@ -2952,6 +4160,40 @@ var ToolRegistry = class {
2952
4160
  * moved, neither of which went through `definitionsFor` again) and re-parses the input via Zod.
2953
4161
  */
2954
4162
  async invoke(name, input, ctx, policy, options = {}) {
4163
+ const { entry, value, ctx: withEmit } = await this.validated(name, input, ctx, policy, options);
4164
+ const trusted = Object.hasOwn(options, "approvedInput");
4165
+ if (trusted) assertTrustedInput(name, value, options.approvedInput);
4166
+ const executionInput = trusted ? snapshotActionProposal(value) : value;
4167
+ const hookInput = trusted ? snapshotActionProposal(executionInput) : value;
4168
+ const result2 = entry.spec.kind === "action" ? await entry.handler.preflight?.(hookInput, withEmit, {
4169
+ phase: "execute"
4170
+ }) : void 0;
4171
+ if (trusted) assertTrustedInput(name, hookInput, executionInput);
4172
+ if (result2?.status === "denied") throw new ToolPreflightDeniedError(name, result2.reason);
4173
+ if (result2?.status === "completed") return result2.output;
4174
+ return entry.handler.execute(executionInput, withEmit);
4175
+ }
4176
+ /** Authorize and validate without executing. The caller journals this result before approval. */
4177
+ async prepare(name, input, ctx, policy, options = {}) {
4178
+ return (await this.prepareValidated(name, input, ctx, policy, options)).preflight;
4179
+ }
4180
+ /** Expose the single schema parse; snapshotInput opts proposal callers into immutable JSON. */
4181
+ async prepareValidated(name, input, ctx, policy, options = {}) {
4182
+ const { entry, value, ctx: withEmit } = await this.validated(name, input, ctx, policy, options);
4183
+ const normalized = options.snapshotInput === true ? snapshotActionProposal(value) : value;
4184
+ const hookInput = options.snapshotInput === true ? snapshotActionProposal(normalized) : value;
4185
+ const preflight = (entry.spec.kind === "action" ? await entry.handler.preflight?.(hookInput, withEmit, {
4186
+ phase: "prepare"
4187
+ }) : void 0) ?? {
4188
+ status: "ready"
4189
+ };
4190
+ if (options.snapshotInput === true) assertTrustedInput(name, hookInput, normalized);
4191
+ return {
4192
+ input: normalized,
4193
+ preflight
4194
+ };
4195
+ }
4196
+ async validated(name, input, ctx, policy, options) {
2955
4197
  const entry = this.entries.get(name);
2956
4198
  if (entry === void 0) {
2957
4199
  throw new ToolNotFoundError(name);
@@ -2976,7 +4218,11 @@ var ToolRegistry = class {
2976
4218
  ...ctx,
2977
4219
  emitUi: createNoopEmitUi(ctx.requestId)
2978
4220
  };
2979
- return entry.handler.execute(validation.value, withEmit);
4221
+ return {
4222
+ entry,
4223
+ value: validation.value,
4224
+ ctx: withEmit
4225
+ };
2980
4226
  }
2981
4227
  };
2982
4228
  var DefaultRolesPolicy = class {
@@ -3023,6 +4269,69 @@ __name(closeEmptyRoles, "closeEmptyRoles");
3023
4269
  var import_node_crypto = require("crypto");
3024
4270
  var import_nestjs_diagnostics2 = require("@dudousxd/nestjs-diagnostics");
3025
4271
 
4272
+ // src/action-proposal-approval.ts
4273
+ async function resolveActionProposalApproval(input) {
4274
+ const policy = input.policy ?? new DefaultApprovalPolicy();
4275
+ const requirement = await policy.requirementFor(input.tool, input.actor, input.thread);
4276
+ if (!requirement.required) return {
4277
+ mode: "auto"
4278
+ };
4279
+ const remembered = await input.store.rememberedApprovals?.(input.thread.threadId) ?? [];
4280
+ if (remembered.includes(input.tool.name)) return {
4281
+ mode: "remembered",
4282
+ approver: requirement.approver
4283
+ };
4284
+ const roundedTtl = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) ? Math.floor(requirement.ttlMs) : 0;
4285
+ if (roundedTtl <= 0) return {
4286
+ mode: "ask",
4287
+ approver: requirement.approver
4288
+ };
4289
+ const now = (input.clock ?? Date.now)();
4290
+ if (!Number.isSafeInteger(now) || !Number.isSafeInteger(roundedTtl) || !Number.isSafeInteger(now + roundedTtl)) throw new RangeError("Invalid action approval deadline");
4291
+ return {
4292
+ mode: "ask",
4293
+ approver: requirement.approver,
4294
+ ttlMs: roundedTtl,
4295
+ expiresAt: new Date(now + roundedTtl).toISOString()
4296
+ };
4297
+ }
4298
+ __name(resolveActionProposalApproval, "resolveActionProposalApproval");
4299
+
4300
+ // src/action-proposal-preparation.ts
4301
+ async function prepareActionProposal(registry, name, input, ctx, policy, options = {}) {
4302
+ const preparationInput = snapshotActionProposal(input);
4303
+ const prepared = await registry.prepareValidated(name, snapshotActionProposal(preparationInput), ctx, policy, {
4304
+ ...options,
4305
+ snapshotInput: true
4306
+ });
4307
+ let replacementKey;
4308
+ const replacement = registry.spec(name)?.replacementKey;
4309
+ if (prepared.preflight.status === "ready" && replacement !== void 0) {
4310
+ replacementKey = typeof replacement === "string" ? replacement : await replacement(snapshotActionProposal(prepared.input), ctx);
4311
+ if (replacementKey !== void 0 && (typeof replacementKey !== "string" || !replacementKey || replacementKey.length > 255)) throw new TypeError("replacementKey must be a nonempty string of at most 255 UTF-16 code units");
4312
+ }
4313
+ return {
4314
+ ...replacementKey !== void 0 ? {
4315
+ replacementKey
4316
+ } : {},
4317
+ preparationInput,
4318
+ input: prepared.input,
4319
+ // Completed results keep their normal handler contract; they never create a proposal.
4320
+ preflight: prepared.preflight.status === "ready" ? snapshotActionProposal(prepared.preflight) : prepared.preflight
4321
+ };
4322
+ }
4323
+ __name(prepareActionProposal, "prepareActionProposal");
4324
+
4325
+ // src/action-proposal-receipt.ts
4326
+ function actionProposalReceipt(proposalId) {
4327
+ return {
4328
+ proposalId,
4329
+ status: "pending",
4330
+ executed: false
4331
+ };
4332
+ }
4333
+ __name(actionProposalReceipt, "actionProposalReceipt");
4334
+
3026
4335
  // src/diagnostics.ts
3027
4336
  var import_nestjs_diagnostics = require("@dudousxd/nestjs-diagnostics");
3028
4337
  function publishAgentRunStarted(payload) {
@@ -3096,7 +4405,57 @@ var AGENT_DIAGNOSTIC_EVENTS = [
3096
4405
  function agentDiagnosticKey(event) {
3097
4406
  return `agent:${event}`;
3098
4407
  }
3099
- __name(agentDiagnosticKey, "agentDiagnosticKey");
4408
+ __name(agentDiagnosticKey, "agentDiagnosticKey");
4409
+
4410
+ // src/negotiated-tool-ui.ts
4411
+ function createNegotiatedUiCollector(toolCallId, scope, resolveCatalog, write) {
4412
+ const collector = createUiCollector(toolCallId, write);
4413
+ const texts = /* @__PURE__ */ new Map();
4414
+ let next = 0;
4415
+ return {
4416
+ components: collector.components,
4417
+ text: /* @__PURE__ */ __name(() => [
4418
+ ...texts.values()
4419
+ ].join("\n"), "text"),
4420
+ restart: /* @__PURE__ */ __name(() => {
4421
+ next = 0;
4422
+ texts.clear();
4423
+ collector.restart();
4424
+ }, "restart"),
4425
+ emit: /* @__PURE__ */ __name(async (component, props, options = {}) => {
4426
+ if (!resolveCatalog) {
4427
+ if (scope.uiCapabilities !== void 0) throw new Error("UI capabilities require an authorized server catalog");
4428
+ return collector.emit(component, props, options);
4429
+ }
4430
+ const capturedProps = snapshotActionProposal(props);
4431
+ const capturedOptions = {
4432
+ ...options
4433
+ };
4434
+ const catalog = await resolveCatalog(scope);
4435
+ const prepared = await prepareUiEmission(catalog, scope.uiCapabilities, component, capturedProps, capturedOptions.version);
4436
+ const id = capturedOptions.id ?? `${toolCallId}:ui:${next++}`;
4437
+ if (prepared.kind === "text") {
4438
+ texts.set(id, prepared.text);
4439
+ await write?.({
4440
+ kind: "text",
4441
+ text: prepared.text
4442
+ });
4443
+ return {
4444
+ id
4445
+ };
4446
+ }
4447
+ return collector.emit(prepared.component, prepared.props, {
4448
+ id,
4449
+ version: prepared.version,
4450
+ fallbackText: prepared.fallbackText,
4451
+ ...prepared.componentVersions !== void 0 ? {
4452
+ componentVersions: prepared.componentVersions
4453
+ } : {}
4454
+ });
4455
+ }, "emit")
4456
+ };
4457
+ }
4458
+ __name(createNegotiatedUiCollector, "createNegotiatedUiCollector");
3100
4459
 
3101
4460
  // src/tool-retry.ts
3102
4461
  function hasTransientShape(error) {
@@ -3123,6 +4482,7 @@ function hasTransientShape(error) {
3123
4482
  }
3124
4483
  __name(hasTransientShape, "hasTransientShape");
3125
4484
  function isTransientToolError(error) {
4485
+ if (error instanceof ToolPreflightDeniedError) return false;
3126
4486
  if (hasTransientShape(error)) {
3127
4487
  return true;
3128
4488
  }
@@ -3165,7 +4525,7 @@ async function invokeWithTransientRetry(fn, setting, options) {
3165
4525
  try {
3166
4526
  return await fn();
3167
4527
  } catch (error) {
3168
- if (isControlFlowSignal(error) || options?.isControlFlowError?.(error) === true) {
4528
+ if (error instanceof ToolPreflightDeniedError || isControlFlowSignal(error) || options?.isControlFlowError?.(error) === true) {
3169
4529
  throw error;
3170
4530
  }
3171
4531
  const attemptsRemain = attempt < attempts;
@@ -3345,6 +4705,9 @@ function promptContext(input, persona) {
3345
4705
  ...input.pageContext !== void 0 ? {
3346
4706
  pageContext: input.pageContext
3347
4707
  } : {},
4708
+ ...input.uiCapabilities !== void 0 ? {
4709
+ uiCapabilities: input.uiCapabilities
4710
+ } : {},
3348
4711
  ...persona !== void 0 ? {
3349
4712
  persona: {
3350
4713
  id: persona.id,
@@ -3541,7 +4904,8 @@ async function loadSelectedHistory(deps, input, hooks) {
3541
4904
  messages: keep,
3542
4905
  dropped: deps.historyPolicy?.summarize === void 0 ? [] : drop,
3543
4906
  title: thread.title,
3544
- hasAssistantMessage: thread.hasAssistantMessage
4907
+ hasAssistantMessage: thread.hasAssistantMessage,
4908
+ actionApprovalMode: deps.actionApprovalMode ?? "blocking"
3545
4909
  };
3546
4910
  });
3547
4911
  return {
@@ -3705,10 +5069,10 @@ async function structureAnswer(schema, deps, input, hooks, messages, step) {
3705
5069
  modelId: turn.modelId
3706
5070
  } : {}
3707
5071
  };
3708
- }, (result) => ({
3709
- inputTokens: result.usage.inputTokens,
3710
- outputTokens: result.usage.outputTokens,
3711
- textLength: result.text.length
5072
+ }, (result2) => ({
5073
+ inputTokens: result2.usage.inputTokens,
5074
+ outputTokens: result2.usage.outputTokens,
5075
+ textLength: result2.text.length
3712
5076
  })));
3713
5077
  await hooks.step(`persist:usage:structured:${step}:${attempt}`, () => deps.store.recordUsage({
3714
5078
  threadId: input.threadId,
@@ -3738,13 +5102,16 @@ async function structureAnswer(schema, deps, input, hooks, messages, step) {
3738
5102
  throw new StructuredOutputError(issues, text, maxAttempts);
3739
5103
  }
3740
5104
  __name(structureAnswer, "structureAnswer");
3741
- function stampToolKinds(result, deps) {
5105
+ function stampToolKinds(result2, deps) {
3742
5106
  return {
3743
- ...result,
3744
- toolCalls: result.toolCalls.map((call) => call.kind === void 0 ? {
3745
- ...call,
3746
- kind: declaredKind(deps, call.name)
3747
- } : call)
5107
+ ...result2,
5108
+ toolCalls: result2.toolCalls.map((call) => {
5109
+ const { preflight: _untrusted, prepared: _untrustedPrepared, actionApproval: _untrustedApproval, ...trusted } = call;
5110
+ return trusted.kind === void 0 ? {
5111
+ ...trusted,
5112
+ kind: declaredKind(deps, trusted.name)
5113
+ } : trusted;
5114
+ })
3748
5115
  };
3749
5116
  }
3750
5117
  __name(stampToolKinds, "stampToolKinds");
@@ -3807,6 +5174,9 @@ function toolContext(deps, input, hooks) {
3807
5174
  ...input.pageContext !== void 0 ? {
3808
5175
  pageContext: input.pageContext
3809
5176
  } : {},
5177
+ ...input.uiCapabilities !== void 0 ? {
5178
+ uiCapabilities: input.uiCapabilities
5179
+ } : {},
3810
5180
  ...deps.host !== void 0 ? {
3811
5181
  host: deps.host
3812
5182
  } : {},
@@ -3882,16 +5252,16 @@ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant)
3882
5252
  }
3883
5253
  const messageId = asked.messageId;
3884
5254
  const reply = await awaitElicitation(hooks, request, toolContext(deps, input, hooks));
3885
- const result = settleElicitation(request, reply);
5255
+ const result2 = settleElicitation(request, reply);
3886
5256
  await hooks.step("intake:answers", async () => {
3887
5257
  await deps.store.updateToolCall({
3888
5258
  toolCallId: request.id,
3889
5259
  // A skip is not an answer. Both leave the agent holding the same values, but only one of them
3890
5260
  // is evidence the user chose them, and a reader auditing what the agent was told has to be
3891
5261
  // able to tell those apart.
3892
- status: result.skipped ? "rejected" : "executed",
3893
- output: result,
3894
- ...result.skipped ? {
5262
+ status: result2.skipped ? "rejected" : "executed",
5263
+ output: result2,
5264
+ ...result2.skipped ? {
3895
5265
  error: "skipped by the user"
3896
5266
  } : {},
3897
5267
  ...reply.answeredByRef !== void 0 ? {
@@ -3902,13 +5272,13 @@ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant)
3902
5272
  {
3903
5273
  id: request.id,
3904
5274
  name: ASK_TOOL_NAME,
3905
- output: result
5275
+ output: result2
3906
5276
  }
3907
5277
  ]);
3908
5278
  await writer.write(encodeStreamEvent({
3909
5279
  kind: "tool-output",
3910
5280
  id: request.id,
3911
- output: result
5281
+ output: result2
3912
5282
  }));
3913
5283
  });
3914
5284
  return {
@@ -3921,7 +5291,7 @@ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant)
3921
5291
  {
3922
5292
  id: request.id,
3923
5293
  name: ASK_TOOL_NAME,
3924
- output: result
5294
+ output: result2
3925
5295
  }
3926
5296
  ]
3927
5297
  };
@@ -3967,16 +5337,140 @@ async function claimToolCall(turn, call) {
3967
5337
  const spec = deps.registry.spec(call.name);
3968
5338
  const kind = call.kind ?? declaredKind(deps, call.name);
3969
5339
  const awaitsHuman = kind === "action" || kind === "ask";
3970
- const approval = kind === "action" ? await claimApproval(turn, call, spec) : void 0;
3971
- const parks = kind === "ask" || approval?.mode === "ask";
5340
+ const independentApproval = turn.actionApprovalMode === "independent" && kind === "action" ? hooks.dispatchLlm !== void 0 && call.actionApproval !== void 0 ? call.actionApproval : await resolveActionProposalApproval({
5341
+ actor: input.actor,
5342
+ store: deps.store,
5343
+ tool: {
5344
+ name: call.name,
5345
+ kind: "action",
5346
+ ...spec !== void 0 ? {
5347
+ spec
5348
+ } : {}
5349
+ },
5350
+ thread: {
5351
+ threadId: input.threadId,
5352
+ runId: hooks.runId,
5353
+ ...input.agentName !== void 0 ? {
5354
+ agentName: input.agentName
5355
+ } : {}
5356
+ },
5357
+ ...deps.approvalPolicy !== void 0 ? {
5358
+ policy: deps.approvalPolicy
5359
+ } : {}
5360
+ }) : void 0;
5361
+ let preflight;
5362
+ let prepared;
5363
+ if (turn.actionApprovalMode === "independent" && call.prepared !== void 0 && hooks.dispatchLlm !== void 0) prepared = call.prepared;
5364
+ if (kind === "action" && hooks.dispatchLlm !== void 0 && call.preflight !== void 0) {
5365
+ preflight = call.preflight;
5366
+ } else if (kind === "action" && hooks.dispatchLlm !== void 0 && spec === void 0) {
5367
+ preflight = {
5368
+ status: "ready"
5369
+ };
5370
+ } else if (kind === "action") {
5371
+ try {
5372
+ const allowedTools = turn.persona?.allowedTools === void 0 ? deps.toolAllowList : intersectAllowLists(deps.toolAllowList, turn.persona.allowedTools);
5373
+ if (independentApproval?.mode === "ask") {
5374
+ prepared = await prepareActionProposal(deps.registry, call.name, call.input, toolCallContext(toolContext(deps, input, hooks), call.id), deps.rolesPolicy, allowedTools === void 0 ? {} : {
5375
+ allowedTools
5376
+ });
5377
+ preflight = prepared.preflight;
5378
+ } else {
5379
+ preflight = await deps.registry.prepare(call.name, call.input, toolCallContext(toolContext(deps, input, hooks), call.id), deps.rolesPolicy, allowedTools === void 0 ? {} : {
5380
+ allowedTools
5381
+ });
5382
+ }
5383
+ } catch (error) {
5384
+ if (hooks.isControlFlowError?.(error) === true || isControlFlowSignal(error) || isReplayIntegrityError(error)) throw error;
5385
+ preflight = {
5386
+ status: "failed",
5387
+ error: error instanceof Error ? error.message : String(error)
5388
+ };
5389
+ }
5390
+ }
5391
+ let confirmation = preflight?.status === "ready" ? preflight.confirmation : void 0;
5392
+ const approval = kind === "action" && preflight?.status === "ready" ? independentApproval ?? await claimApproval(turn, call, spec) : void 0;
5393
+ let proposalId;
5394
+ if (turn.actionApprovalMode === "independent" && approval?.mode === "ask") {
5395
+ if (!prepared) throw new Error("Independent action lacks trusted prepared input");
5396
+ const proposalStore = deps.store;
5397
+ if (typeof proposalStore.createActionProposal !== "function") throw new Error("Independent action proposal store unavailable");
5398
+ proposalId = `proposal-${(0, import_node_crypto.createHash)("sha256").update(JSON.stringify([
5399
+ hooks.runId,
5400
+ call.id
5401
+ ])).digest("hex")}`;
5402
+ const display = confirmation ?? spec?.presentation?.confirm ?? {
5403
+ title: `Allow ${call.name}?`,
5404
+ verb: "Approve"
5405
+ };
5406
+ const fill = /* @__PURE__ */ __name((value) => value.replace(/\{([^}]+)\}/g, (_match, path) => {
5407
+ let current = prepared?.input;
5408
+ for (const part of path.split(".")) current = current !== null && typeof current === "object" ? current[part] : void 0;
5409
+ return current === void 0 ? "" : String(current);
5410
+ }), "fill");
5411
+ const create = prepared.replacementKey === void 0 ? proposalStore.createActionProposal.bind(proposalStore) : (() => {
5412
+ const store = proposalStore;
5413
+ if (typeof store.createReplacingActionProposal !== "function") throw new Error("Independent replacement requires atomic supersession store");
5414
+ return store.createReplacingActionProposal.bind(store);
5415
+ })();
5416
+ const created = await create({
5417
+ ...prepared.replacementKey !== void 0 ? {
5418
+ replacementKey: prepared.replacementKey
5419
+ } : {},
5420
+ id: proposalId,
5421
+ tenantRef: input.actor.tenantRef ?? null,
5422
+ actorRef: input.actor.id,
5423
+ threadId: input.threadId,
5424
+ originRunId: hooks.runId,
5425
+ originMessageId: messageId,
5426
+ originToolCallId: call.id,
5427
+ toolName: call.name,
5428
+ input: prepared.input,
5429
+ preparationInput: prepared.preparationInput,
5430
+ executionContext: {
5431
+ requestId: hooks.runId,
5432
+ ...input.agentName !== void 0 ? {
5433
+ agentName: input.agentName
5434
+ } : {},
5435
+ ...input.persona !== void 0 ? {
5436
+ persona: input.persona
5437
+ } : {},
5438
+ ...input.pageContext !== void 0 ? {
5439
+ pageContext: input.pageContext
5440
+ } : {},
5441
+ ...input.uiCapabilities !== void 0 ? {
5442
+ uiCapabilities: input.uiCapabilities
5443
+ } : {}
5444
+ },
5445
+ confirmation: confirmation ?? {
5446
+ title: fill(display.title),
5447
+ verb: fill(display.verb),
5448
+ ...display.detail !== void 0 ? {
5449
+ detail: fill(display.detail)
5450
+ } : {}
5451
+ },
5452
+ approver: approval.approver,
5453
+ expiresAt: approval.expiresAt === void 0 ? null : Date.parse(approval.expiresAt),
5454
+ idempotencyKey: toolCallContext(toolContext(deps, input, hooks), call.id).idempotencyKey
5455
+ });
5456
+ if (created.status === "conflict") throw new Error("Action proposal replay payload conflict");
5457
+ confirmation = created.proposal?.confirmation ?? confirmation;
5458
+ }
5459
+ const parks = kind === "ask" || approval?.mode === "ask" && proposalId === void 0;
3972
5460
  await deps.store.recordToolCall({
3973
5461
  toolCallId: call.id,
3974
5462
  messageId,
3975
5463
  toolName: call.name,
3976
5464
  toolType: awaitsHuman ? "action" : "read",
3977
5465
  input: call.input,
3978
- status: parks ? "pending_approval" : "auto_executed",
5466
+ status: proposalId !== void 0 ? "proposed" : parks ? "pending_approval" : "auto_executed",
5467
+ ...proposalId !== void 0 ? {
5468
+ proposalId
5469
+ } : {},
3979
5470
  runId: hooks.runId,
5471
+ ...confirmation !== void 0 ? {
5472
+ confirmation
5473
+ } : {},
3980
5474
  ...approval !== void 0 && approval.mode !== "auto" ? {
3981
5475
  approver: approval.approver
3982
5476
  } : {},
@@ -3988,7 +5482,16 @@ async function claimToolCall(turn, call) {
3988
5482
  await turn.writer.write(encodeStreamEvent({
3989
5483
  kind: "approval-requested",
3990
5484
  id: call.id,
5485
+ ...proposalId !== void 0 ? {
5486
+ target: {
5487
+ kind: "proposal",
5488
+ proposalId
5489
+ }
5490
+ } : {},
3991
5491
  approver: approval.approver,
5492
+ ...confirmation !== void 0 ? {
5493
+ confirmation
5494
+ } : {},
3992
5495
  ...approval.expiresAt !== void 0 ? {
3993
5496
  expiresAt: approval.expiresAt
3994
5497
  } : {}
@@ -4011,6 +5514,12 @@ async function claimToolCall(turn, call) {
4011
5514
  } : {},
4012
5515
  ...approval !== void 0 ? {
4013
5516
  approval
5517
+ } : {},
5518
+ ...preflight !== void 0 ? {
5519
+ preflight
5520
+ } : {},
5521
+ ...proposalId !== void 0 ? {
5522
+ proposalId
4014
5523
  } : {}
4015
5524
  };
4016
5525
  });
@@ -4036,6 +5545,12 @@ async function claimToolCall(turn, call) {
4036
5545
  ...toolType === "action" && persisted?.approval !== void 0 ? {
4037
5546
  approval: persisted.approval
4038
5547
  } : {},
5548
+ ...persisted?.preflight !== void 0 ? {
5549
+ preflight: persisted.preflight
5550
+ } : {},
5551
+ ...persisted?.proposalId !== void 0 ? {
5552
+ proposalId: persisted.proposalId
5553
+ } : {},
4039
5554
  ctx: toolContext(deps, input, hooks)
4040
5555
  };
4041
5556
  }
@@ -4183,6 +5698,9 @@ function skillContext(input) {
4183
5698
  } : {},
4184
5699
  ...input.pageContext !== void 0 ? {
4185
5700
  pageContext: input.pageContext
5701
+ } : {},
5702
+ ...input.uiCapabilities !== void 0 ? {
5703
+ uiCapabilities: input.uiCapabilities
4186
5704
  } : {}
4187
5705
  };
4188
5706
  }
@@ -4199,7 +5717,7 @@ async function invokeClaimedTool(turn, claimed) {
4199
5717
  if (claimed.toolType === "memory") {
4200
5718
  return await rememberIntoTurn(turn, claimed, startedAt);
4201
5719
  }
4202
- const invokeAllowList = turn.persona?.allowedTools === void 0 ? void 0 : intersectAllowLists(deps.toolAllowList, turn.persona.allowedTools);
5720
+ const invokeAllowList = turn.persona?.allowedTools === void 0 ? deps.toolAllowList : intersectAllowLists(deps.toolAllowList, turn.persona.allowedTools);
4203
5721
  let raw;
4204
5722
  if (hooks.dispatchTool) {
4205
5723
  const stepCtx = {
@@ -4215,6 +5733,9 @@ async function invokeClaimedTool(turn, claimed) {
4215
5733
  } : {},
4216
5734
  ...input.pageContext !== void 0 ? {
4217
5735
  pageContext: input.pageContext
5736
+ } : {},
5737
+ ...input.uiCapabilities !== void 0 ? {
5738
+ uiCapabilities: input.uiCapabilities
4218
5739
  } : {}
4219
5740
  };
4220
5741
  const envelope = {
@@ -4236,37 +5757,49 @@ async function invokeClaimedTool(turn, claimed) {
4236
5757
  raw = await hooks.dispatchTool(resolvedCall, envelope);
4237
5758
  } else {
4238
5759
  const invocation = hooks.step(`tool:${call.id}`, async () => {
4239
- const ui2 = createUiCollector(call.id, (event) => turn.writer.write(encodeStreamEvent(event)));
4240
- const output2 = await traceToolExecution(hooks.runId, {
4241
- toolCallId: call.id,
4242
- toolName: call.name,
4243
- toolType
4244
- }, () => invokeWithTransientRetry(() => {
4245
- ui2.restart();
4246
- return deps.registry.invoke(call.name, call.input, {
4247
- ...toolCallContext(ctx, call.id),
4248
- emitUi: ui2.emit
4249
- }, deps.rolesPolicy, invokeAllowList !== void 0 ? {
4250
- allowedTools: invokeAllowList
4251
- } : {});
4252
- }, deps.toolTransientRetry ?? {}, {
4253
- ...hooks.isControlFlowError !== void 0 ? {
4254
- isControlFlowError: hooks.isControlFlowError
4255
- } : {},
4256
- onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
4257
- publishAgentToolRetry({
4258
- toolName: call.name,
4259
- toolCallId: call.id,
4260
- attempt,
4261
- message: retryError instanceof Error ? retryError.message : String(retryError)
4262
- });
4263
- }, "onRetry")
4264
- }));
4265
- return wrapToolStepOutput(output2, ui2.components());
5760
+ const ui2 = createNegotiatedUiCollector(call.id, ctx, deps.resolveUiCatalog, (event) => turn.writer.write(encodeStreamEvent(event)));
5761
+ let output2;
5762
+ try {
5763
+ output2 = await traceToolExecution(hooks.runId, {
5764
+ toolCallId: call.id,
5765
+ toolName: call.name,
5766
+ toolType
5767
+ }, () => invokeWithTransientRetry(() => {
5768
+ ui2.restart();
5769
+ return deps.registry.invoke(call.name, call.input, {
5770
+ ...toolCallContext(ctx, call.id),
5771
+ emitUi: ui2.emit
5772
+ }, deps.rolesPolicy, invokeAllowList !== void 0 ? {
5773
+ allowedTools: invokeAllowList
5774
+ } : {});
5775
+ }, deps.toolTransientRetry ?? {}, {
5776
+ ...hooks.isControlFlowError !== void 0 ? {
5777
+ isControlFlowError: hooks.isControlFlowError
5778
+ } : {},
5779
+ onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
5780
+ publishAgentToolRetry({
5781
+ toolName: call.name,
5782
+ toolCallId: call.id,
5783
+ attempt,
5784
+ message: retryError instanceof Error ? retryError.message : String(retryError)
5785
+ });
5786
+ }, "onRetry")
5787
+ }));
5788
+ } catch (error) {
5789
+ if (error instanceof Error && error.name === "ToolPreflightDeniedError") return wrapToolPreflightDenied(error.message);
5790
+ throw error;
5791
+ }
5792
+ return wrapToolStepOutput(output2, ui2.components(), ui2.text());
4266
5793
  });
4267
5794
  raw = deps.toolTimeoutMs !== void 0 ? await withToolTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
4268
5795
  }
4269
- const { output, ui } = unwrapToolStepOutput(raw);
5796
+ const { output, ui, preflightDenied, text } = unwrapToolStepOutput(raw);
5797
+ if (text) turn.toolText.set(call.id, text);
5798
+ if (preflightDenied !== void 0) return {
5799
+ status: "denied",
5800
+ reason: preflightDenied,
5801
+ executionMs: Date.now() - startedAt
5802
+ };
4270
5803
  if (ui.length > 0) {
4271
5804
  turn.toolUi.set(call.id, ui);
4272
5805
  }
@@ -4282,6 +5815,13 @@ async function invokeClaimedTool(turn, claimed) {
4282
5815
  if (hooks.isControlFlowError?.(error) === true || isControlFlowSignal(error) || isReplayIntegrityError(error)) {
4283
5816
  throw error;
4284
5817
  }
5818
+ if (error instanceof Error && error.name === "ToolPreflightDeniedError") {
5819
+ return {
5820
+ status: "denied",
5821
+ reason: error.message,
5822
+ executionMs: Date.now() - startedAt
5823
+ };
5824
+ }
4285
5825
  return {
4286
5826
  status: "failed",
4287
5827
  error: error instanceof Error ? error.message : String(error),
@@ -4323,6 +5863,27 @@ async function recordToolOutcome(turn, claimed, outcome, deciderRef, meta) {
4323
5863
  } : {}
4324
5864
  }));
4325
5865
  }, "settle");
5866
+ if (outcome.status === "denied") {
5867
+ await hooks.step(`persist:toolreject:${call.id}`, async () => {
5868
+ await deps.store.updateToolCall({
5869
+ toolCallId: call.id,
5870
+ status: meta === void 0 ? "rejected" : "failed",
5871
+ error: outcome.reason,
5872
+ ...decided
5873
+ });
5874
+ await settle();
5875
+ });
5876
+ return {
5877
+ id: call.id,
5878
+ name: call.name,
5879
+ output: {
5880
+ rejected: true,
5881
+ reason: outcome.reason
5882
+ },
5883
+ denied: true,
5884
+ error: `This action was denied by its preflight check: ${outcome.reason}. The requested mutation did not run.`
5885
+ };
5886
+ }
4326
5887
  if (outcome.status === "failed") {
4327
5888
  await hooks.step(`persist:toolfail:${call.id}`, async () => {
4328
5889
  await deps.store.updateToolCall({
@@ -4463,12 +6024,12 @@ async function elicitToolCall(turn, claimed) {
4463
6024
  }));
4464
6025
  });
4465
6026
  const reply = await awaitElicitation(hooks, request, ctx);
4466
- const result = settleElicitation(request, reply);
4467
- await hooks.step(result.skipped ? `persist:toolreject:${call.id}` : `persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
6027
+ const result2 = settleElicitation(request, reply);
6028
+ await hooks.step(result2.skipped ? `persist:toolreject:${call.id}` : `persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
4468
6029
  toolCallId: call.id,
4469
- status: result.skipped ? "rejected" : "executed",
4470
- output: result,
4471
- ...result.skipped ? {
6030
+ status: result2.skipped ? "rejected" : "executed",
6031
+ output: result2,
6032
+ ...result2.skipped ? {
4472
6033
  error: "skipped by the user"
4473
6034
  } : {},
4474
6035
  ...reply.answeredByRef !== void 0 ? {
@@ -4479,24 +6040,50 @@ async function elicitToolCall(turn, claimed) {
4479
6040
  runId: hooks.runId,
4480
6041
  toolName: call.name,
4481
6042
  toolType: "action",
4482
- status: result.skipped ? "rejected" : "executed"
6043
+ status: result2.skipped ? "rejected" : "executed"
4483
6044
  });
4484
6045
  return {
4485
6046
  id: call.id,
4486
6047
  name: call.name,
4487
- output: result
6048
+ output: result2
4488
6049
  };
4489
6050
  }
4490
6051
  __name(elicitToolCall, "elicitToolCall");
4491
6052
  async function runClaimedToolCall(turn, claimed) {
4492
6053
  const { deps, input, hooks } = turn;
4493
6054
  const { call, toolType, ctx } = claimed;
6055
+ if (claimed.proposalId !== void 0) {
6056
+ turn.halt.independentProposal = true;
6057
+ return {
6058
+ id: call.id,
6059
+ name: call.name,
6060
+ output: actionProposalReceipt(claimed.proposalId)
6061
+ };
6062
+ }
4494
6063
  if (toolType === "agent") {
4495
6064
  return delegateToolCall(turn, claimed);
4496
6065
  }
4497
6066
  if (toolType === "ask") {
4498
6067
  return elicitToolCall(turn, claimed);
4499
6068
  }
6069
+ if (claimed.preflight !== void 0 && claimed.preflight.status !== "ready") {
6070
+ const result2 = claimed.preflight;
6071
+ if (result2.status === "completed" && claimed.terminal === true) turn.halt.terminal = true;
6072
+ const outcome = result2.status === "completed" ? {
6073
+ status: "executed",
6074
+ output: result2.output,
6075
+ executionMs: 0
6076
+ } : result2.status === "denied" ? {
6077
+ status: "denied",
6078
+ reason: result2.reason,
6079
+ executionMs: 0
6080
+ } : {
6081
+ status: "failed",
6082
+ error: result2.error,
6083
+ executionMs: 0
6084
+ };
6085
+ return recordToolOutcome(turn, claimed, outcome, input.actor.id);
6086
+ }
4500
6087
  let deciderRef = input.actor.id;
4501
6088
  let meta;
4502
6089
  if (toolType === "action" && claimed.approval?.mode === "remembered") {
@@ -4621,30 +6208,30 @@ function expiryNarrative() {
4621
6208
  return "This action needed approval, and the approval request expired before anyone decided. Nothing ran and nothing changed. Do not run it again on your own; tell the person it was not approved in time and ask whether they still want it.";
4622
6209
  }
4623
6210
  __name(expiryNarrative, "expiryNarrative");
4624
- function outputFrame(result) {
4625
- if (result.denied === true) {
4626
- const reason = refusalReason(result);
6211
+ function outputFrame(result2) {
6212
+ if (result2.denied === true) {
6213
+ const reason = refusalReason(result2);
4627
6214
  return {
4628
6215
  kind: "tool-output-denied",
4629
- id: result.id,
6216
+ id: result2.id,
4630
6217
  ...reason !== void 0 ? {
4631
6218
  reason
4632
6219
  } : {}
4633
6220
  };
4634
6221
  }
4635
- return result.error !== void 0 ? {
6222
+ return result2.error !== void 0 ? {
4636
6223
  kind: "tool-output-error",
4637
- id: result.id,
4638
- error: result.error
6224
+ id: result2.id,
6225
+ error: result2.error
4639
6226
  } : {
4640
6227
  kind: "tool-output",
4641
- id: result.id,
4642
- output: result.output
6228
+ id: result2.id,
6229
+ output: result2.output
4643
6230
  };
4644
6231
  }
4645
6232
  __name(outputFrame, "outputFrame");
4646
- function refusalReason(result) {
4647
- const { output } = result;
6233
+ function refusalReason(result2) {
6234
+ const { output } = result2;
4648
6235
  if (output !== null && typeof output === "object" && "reason" in output) {
4649
6236
  const reason = output.reason;
4650
6237
  return typeof reason === "string" && reason !== DEFAULT_REFUSAL_REASON ? reason : void 0;
@@ -4884,6 +6471,25 @@ ${block}`;
4884
6471
  messages: prompt.messages,
4885
6472
  actor: input.actor,
4886
6473
  threadId: input.threadId,
6474
+ actionApprovalMode: history.actionApprovalMode ?? "blocking",
6475
+ preflightContext: {
6476
+ actor: input.actor,
6477
+ threadId: input.threadId,
6478
+ runId: hooks.runId,
6479
+ requestId: hooks.runId,
6480
+ ...input.agentName !== void 0 ? {
6481
+ agentName: input.agentName
6482
+ } : {},
6483
+ ...input.persona !== void 0 ? {
6484
+ persona: input.persona
6485
+ } : {},
6486
+ ...input.pageContext !== void 0 ? {
6487
+ pageContext: input.pageContext
6488
+ } : {},
6489
+ ...input.uiCapabilities !== void 0 ? {
6490
+ uiCapabilities: input.uiCapabilities
6491
+ } : {}
6492
+ },
4887
6493
  ...gated ? {
4888
6494
  bufferOutput: true
4889
6495
  } : {},
@@ -4900,6 +6506,9 @@ ${block}`;
4900
6506
  tools: withAskTool({
4901
6507
  tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, intersectAllowLists(deps.toolAllowList, persona?.allowedTools), {
4902
6508
  threadId: input.threadId,
6509
+ ...input.uiCapabilities !== void 0 ? {
6510
+ uiCapabilities: input.uiCapabilities
6511
+ } : {},
4903
6512
  ...input.agentName !== void 0 ? {
4904
6513
  agentName: input.agentName
4905
6514
  } : {}
@@ -4919,7 +6528,7 @@ ${block}`;
4919
6528
  }) : void 0;
4920
6529
  const buffer = gateMode === "whole" ? createFrameBuffer() : void 0;
4921
6530
  const frames = observeTurnFrames(incremental?.writer ?? buffer?.writer ?? writer);
4922
- const result = stampToolKinds(withTurnFrames(await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
6531
+ const result2 = stampToolKinds(withTurnFrames(await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
4923
6532
  system: prompt.system,
4924
6533
  messages: prompt.messages,
4925
6534
  tools,
@@ -4929,15 +6538,15 @@ ${block}`;
4929
6538
  await incremental.settled();
4930
6539
  const refusal = incremental.rejection();
4931
6540
  return {
4932
- ...result,
6541
+ ...result2,
4933
6542
  releasedText: incremental.released(),
4934
6543
  ...refusal !== void 0 ? {
4935
6544
  gateRejection: refusal
4936
6545
  } : {}
4937
6546
  };
4938
6547
  }
4939
- return buffer === void 0 ? result : {
4940
- ...result,
6548
+ return buffer === void 0 ? result2 : {
6549
+ ...result2,
4941
6550
  bufferedFrames: buffer.frames()
4942
6551
  };
4943
6552
  });
@@ -5031,12 +6640,12 @@ ${block}`;
5031
6640
  role: "assistant",
5032
6641
  content: turn.text
5033
6642
  }
5034
- ], count), (result) => ({
5035
- followUps: result.followUps.length,
5036
- inputTokens: result.usage.inputTokens,
5037
- outputTokens: result.usage.outputTokens,
5038
- ...result.modelId !== void 0 ? {
5039
- modelId: result.modelId
6643
+ ], count), (result2) => ({
6644
+ followUps: result2.followUps.length,
6645
+ inputTokens: result2.usage.inputTokens,
6646
+ outputTokens: result2.usage.outputTokens,
6647
+ ...result2.modelId !== void 0 ? {
6648
+ modelId: result2.modelId
5040
6649
  } : {}
5041
6650
  })));
5042
6651
  let suggestions = generated.followUps;
@@ -5147,7 +6756,7 @@ ${block}`;
5147
6756
  };
5148
6757
  modelMessages.push(assistantMessage);
5149
6758
  for (const entry of synthetic) {
5150
- const { call, result } = entry;
6759
+ const { call, result: result2 } = entry;
5151
6760
  await hooks.step(`${entry.step}:${assistant.id}`, async () => {
5152
6761
  await deps.store.recordToolCall({
5153
6762
  toolCallId: call.id,
@@ -5161,7 +6770,7 @@ ${block}`;
5161
6770
  await deps.store.updateToolCall({
5162
6771
  toolCallId: call.id,
5163
6772
  status: "executed",
5164
- output: result.output
6773
+ output: result2.output
5165
6774
  });
5166
6775
  await writer.write(encodeStreamEvent({
5167
6776
  kind: "tool-input-available",
@@ -5173,7 +6782,7 @@ ${block}`;
5173
6782
  await writer.write(encodeStreamEvent({
5174
6783
  kind: "tool-output",
5175
6784
  id: call.id,
5176
- output: result.output
6785
+ output: result2.output
5177
6786
  }));
5178
6787
  });
5179
6788
  }
@@ -5202,9 +6811,11 @@ ${block}`;
5202
6811
  messageId: assistant.id,
5203
6812
  writer,
5204
6813
  toolUi: /* @__PURE__ */ new Map(),
6814
+ toolText: /* @__PURE__ */ new Map(),
5205
6815
  halt: {
5206
6816
  terminal: false
5207
6817
  },
6818
+ actionApprovalMode: history.actionApprovalMode ?? "blocking",
5208
6819
  ...skillOffer !== void 0 ? {
5209
6820
  skills: skillOffer
5210
6821
  } : {},
@@ -5237,17 +6848,35 @@ ${block}`;
5237
6848
  ...results,
5238
6849
  ...syntheticResults
5239
6850
  ];
6851
+ const toolText = escapeUnsafeToolUiText(toolCallsWithKind.map((call) => turnCalls.toolText.get(call.id)).filter((text) => text !== void 0).join("\n"));
5240
6852
  assistantMessage.toolResults = settledResults;
5241
6853
  await hooks.step(`stream:tool-outputs:${i}`, async () => {
5242
6854
  await deps.store.setMessageToolResults(assistant.id, settledResults);
6855
+ if (toolText) await deps.store.appendMessage({
6856
+ threadId: input.threadId,
6857
+ role: "assistant",
6858
+ content: toolText,
6859
+ runId: hooks.runId,
6860
+ ...input.agentName !== void 0 ? {
6861
+ agentName: input.agentName
6862
+ } : {}
6863
+ });
5243
6864
  const toolUi = toolCallsWithKind.flatMap((call) => turnCalls.toolUi.get(call.id) ?? []);
5244
6865
  if (toolUi.length > 0) {
5245
6866
  await deps.store.setMessageUi?.(assistant.id, mergeUi(turn.ui, toolUi));
5246
6867
  }
5247
- for (const result of results) {
5248
- await writer.write(encodeStreamEvent(outputFrame(result)));
6868
+ for (const result2 of results) {
6869
+ await writer.write(encodeStreamEvent(outputFrame(result2)));
5249
6870
  }
5250
6871
  });
6872
+ if (toolText) {
6873
+ modelMessages.push({
6874
+ role: "assistant",
6875
+ content: toolText
6876
+ });
6877
+ lastText += `
6878
+ ${toolText}`;
6879
+ }
5251
6880
  await hooks.step(`stream:step-finish:${i}`, async () => {
5252
6881
  await writer.write(encodeStreamEvent({
5253
6882
  kind: "step-finish",
@@ -5261,7 +6890,7 @@ ${block}`;
5261
6890
  } : {}
5262
6891
  }));
5263
6892
  });
5264
- if (turnCalls.halt.terminal) {
6893
+ if (turnCalls.halt.terminal || turnCalls.halt.independentProposal) {
5265
6894
  break;
5266
6895
  }
5267
6896
  }
@@ -5317,8 +6946,152 @@ ${block}`;
5317
6946
  }
5318
6947
  __name(runAgentLoop, "runAgentLoop");
5319
6948
 
6949
+ // src/in-memory-action-proposal-store.ts
6950
+ var import_node_crypto2 = require("crypto");
6951
+ var InMemoryActionProposalStore = class {
6952
+ static {
6953
+ __name(this, "InMemoryActionProposalStore");
6954
+ }
6955
+ rows = /* @__PURE__ */ new Map();
6956
+ clock;
6957
+ constructor(options = {}) {
6958
+ this.clock = options.clock ?? Date.now;
6959
+ }
6960
+ async createActionProposal(input) {
6961
+ validateActionProposalCreation(input);
6962
+ const row = this.rows.get(input.id);
6963
+ if (row) {
6964
+ if (!actionProposalScopeMatches(row, input)) return {
6965
+ status: "conflict"
6966
+ };
6967
+ return {
6968
+ status: actionProposalCreationMatches(row, input) ? "unchanged" : "conflict",
6969
+ proposal: snapshotActionProposal(row)
6970
+ };
6971
+ }
6972
+ const proposal = initialActionProposal(input, this.clock());
6973
+ this.rows.set(input.id, proposal);
6974
+ return {
6975
+ status: "created",
6976
+ proposal: snapshotActionProposal(proposal)
6977
+ };
6978
+ }
6979
+ async getActionProposal(scope, id) {
6980
+ const row = this.rows.get(id);
6981
+ return row && actionProposalScopeMatches(row, scope) ? snapshotActionProposal(row) : null;
6982
+ }
6983
+ async listActionProposals(scope, query = {}) {
6984
+ const limit = validateActionProposalListQuery(query);
6985
+ return [
6986
+ ...this.rows.values()
6987
+ ].filter((row) => actionProposalScopeMatches(row, scope) && (query.decision === void 0 || row.decision === query.decision) && (query.after === void 0 || row.createdAt > query.after.createdAt || row.createdAt === query.after.createdAt && row.id > query.after.id)).sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).slice(0, limit).map(snapshotActionProposal);
6988
+ }
6989
+ mutate(scope, id, transition) {
6990
+ const row = this.rows.get(id);
6991
+ if (!row || !actionProposalScopeMatches(row, scope)) return {
6992
+ status: "not_found"
6993
+ };
6994
+ const result2 = transition(row, this.clock());
6995
+ if (result2.proposal) this.rows.set(id, snapshotActionProposal(result2.proposal));
6996
+ return result2;
6997
+ }
6998
+ async decideActionProposal(scope, id, command) {
6999
+ return this.mutate(scope, id, (row, now) => transitionActionProposalDecision(row, command, now));
7000
+ }
7001
+ async claimActionProposal(scope, id, command) {
7002
+ return this.mutate(scope, id, (row, now) => transitionActionProposalClaim(row, command, now, (0, import_node_crypto2.randomUUID)()));
7003
+ }
7004
+ async extendActionProposalLease(scope, id, command) {
7005
+ return this.mutate(scope, id, (row, now) => transitionActionProposalLease(row, command, now));
7006
+ }
7007
+ async settleActionProposal(scope, id, command) {
7008
+ return this.mutate(scope, id, (row, now) => transitionActionProposalSettlement(row, command, now));
7009
+ }
7010
+ async claimNextActionProposal(command) {
7011
+ const now = this.clock();
7012
+ validateActionProposalWorkerClaim(command, now);
7013
+ const candidates = [
7014
+ ...this.rows.values()
7015
+ ].filter((row) => row.decision === "approved" && (row.execution?.status === "queued" || row.execution?.status === "executing" && row.execution.lease !== null && now >= row.execution.lease.expiresAt)).sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).slice(0, 32);
7016
+ for (const candidate of candidates) {
7017
+ const claimed = await this.claimActionProposal(candidate, candidate.id, command);
7018
+ if (claimed.status === "applied" && claimed.proposal) return claimed.proposal;
7019
+ }
7020
+ return null;
7021
+ }
7022
+ async expireActionProposals(command) {
7023
+ const now = this.clock();
7024
+ validateActionProposalExpiryBatch(command, now);
7025
+ const candidates = [
7026
+ ...this.rows.values()
7027
+ ].filter((row) => row.decision === "pending" && row.expiresAt !== null && now >= row.expiresAt).sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).slice(0, command.limit);
7028
+ let expired = 0;
7029
+ for (const candidate of candidates) {
7030
+ const result2 = await this.decideActionProposal(candidate, candidate.id, {
7031
+ decision: "expired",
7032
+ actorRef: "system",
7033
+ via: "expiry"
7034
+ });
7035
+ if (result2.status === "applied") expired++;
7036
+ }
7037
+ return expired;
7038
+ }
7039
+ async claimNextActionProposalOutcome(command) {
7040
+ const now = this.clock();
7041
+ validateActionProposalWorkerClaim(command, now);
7042
+ const candidates = [
7043
+ ...this.rows.values()
7044
+ ].filter((row) => row.outcomeDelivery?.status === "pending" && (!row.outcomeDelivery.lease || now >= row.outcomeDelivery.lease.expiresAt)).sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).slice(0, 32);
7045
+ for (const candidate of candidates) {
7046
+ const row = this.rows.get(candidate.id);
7047
+ if (!row) continue;
7048
+ const next = claimActionProposalOutcome(row, command, this.clock(), (0, import_node_crypto2.randomUUID)());
7049
+ if (!next?.outcome || !next.outcomeDelivery?.lease) continue;
7050
+ this.rows.set(next.id, next);
7051
+ return {
7052
+ outcome: snapshotActionProposal(next.outcome),
7053
+ lease: {
7054
+ outcomeId: next.outcome.id,
7055
+ token: next.outcomeDelivery.lease.token,
7056
+ generation: next.outcomeDelivery.generation
7057
+ }
7058
+ };
7059
+ }
7060
+ return null;
7061
+ }
7062
+ async createReplacingActionProposal(input) {
7063
+ validateActionProposalCreation(input);
7064
+ const existing = this.rows.get(input.id);
7065
+ if (existing) return this.createActionProposal(input);
7066
+ const proposal = initialActionProposal(input, this.clock());
7067
+ const replacements = [];
7068
+ if (input.replacementKey) for (const row of this.rows.values()) {
7069
+ if (row.decision !== "pending" || !actionProposalScopeMatches(row, input) || row.toolName !== input.toolName || row.replacementKey !== input.replacementKey) continue;
7070
+ const result2 = transitionActionProposalSupersession(row, proposal, {
7071
+ replacementProposalId: proposal.id,
7072
+ actorRef: input.actorRef,
7073
+ via: "supersession"
7074
+ }, this.clock());
7075
+ if (result2.proposal) replacements.push(result2.proposal);
7076
+ }
7077
+ this.rows.set(proposal.id, proposal);
7078
+ for (const row of replacements) this.rows.set(row.id, row);
7079
+ return {
7080
+ status: "created",
7081
+ proposal: snapshotActionProposal(proposal)
7082
+ };
7083
+ }
7084
+ async supersedeActionProposal(scope, id, command) {
7085
+ const replacement = this.rows.get(command.replacementProposalId);
7086
+ if (!replacement || !actionProposalScopeMatches(replacement, scope)) return {
7087
+ status: "not_found"
7088
+ };
7089
+ return this.mutate(scope, id, (row, now) => transitionActionProposalSupersession(row, replacement, command, now));
7090
+ }
7091
+ };
7092
+
5320
7093
  // src/in-memory-store.ts
5321
- var InMemoryAgentStore = class {
7094
+ var InMemoryAgentStore = class extends InMemoryActionProposalStore {
5322
7095
  static {
5323
7096
  __name(this, "InMemoryAgentStore");
5324
7097
  }
@@ -5341,6 +7114,7 @@ var InMemoryAgentStore = class {
5341
7114
  const row = {
5342
7115
  id,
5343
7116
  actorRef: input.actor.id,
7117
+ tenantRef: input.actor.tenantRef ?? null,
5344
7118
  title: input.title ?? "New chat",
5345
7119
  transient: input.transient ?? false,
5346
7120
  createdAt: ts,
@@ -5376,8 +7150,12 @@ var InMemoryAgentStore = class {
5376
7150
  }
5377
7151
  const approval = toolCallApprovalFromRow({
5378
7152
  toolCallId: call.toolCallId,
7153
+ ...call.proposalId !== void 0 ? {
7154
+ proposalId: call.proposalId
7155
+ } : {},
5379
7156
  status: call.status,
5380
7157
  approver: call.approver,
7158
+ confirmation: call.confirmation,
5381
7159
  expiresAt: call.expiresAt,
5382
7160
  remember: call.remember,
5383
7161
  executedByRef: call.executedByRef,
@@ -5400,6 +7178,10 @@ var InMemoryAgentStore = class {
5400
7178
  names.add(call.toolName);
5401
7179
  }
5402
7180
  }
7181
+ const thread = this.threads.get(threadId);
7182
+ if (thread) {
7183
+ for (const proposal of this.rows.values()) if (proposal.threadId === threadId && proposal.actorRef === thread.actorRef && proposal.tenantRef === thread.tenantRef && proposal.decisionAudit?.remember === true && (proposal.execution?.status === "succeeded" || proposal.execution?.status === "failed")) names.add(proposal.toolName);
7184
+ }
5403
7185
  return [
5404
7186
  ...names
5405
7187
  ];
@@ -5440,6 +7222,7 @@ var InMemoryAgentStore = class {
5440
7222
  const row = {
5441
7223
  id,
5442
7224
  actorRef: source.actorRef,
7225
+ tenantRef: source.tenantRef,
5443
7226
  title: source.title,
5444
7227
  transient: false,
5445
7228
  createdAt: ts,
@@ -5640,6 +7423,9 @@ var InMemoryAgentStore = class {
5640
7423
  ...input.pageContext !== void 0 ? {
5641
7424
  pageContext: input.pageContext
5642
7425
  } : {},
7426
+ ...input.uiCapabilities !== void 0 ? {
7427
+ uiCapabilities: input.uiCapabilities
7428
+ } : {},
5643
7429
  ...input.interrupt === true ? {
5644
7430
  interrupt: true
5645
7431
  } : {},
@@ -5744,6 +7530,78 @@ var InMemoryAgentStore = class {
5744
7530
  }
5745
7531
  return void 0;
5746
7532
  }
7533
+ async getThreadActionProposalScope(threadId) {
7534
+ const row = this.threads.get(threadId);
7535
+ return row ? {
7536
+ threadId,
7537
+ actorRef: row.actorRef,
7538
+ tenantRef: row.tenantRef
7539
+ } : null;
7540
+ }
7541
+ async admitActionProposalOutcome(command) {
7542
+ const row = [
7543
+ ...this.rows.values()
7544
+ ].find((row2) => row2.outcome?.id === command.outcomeId);
7545
+ if (!row?.outcome || !row.outcomeDelivery) return {
7546
+ status: "not_found"
7547
+ };
7548
+ if (row.outcomeDelivery.status === "admitted") return {
7549
+ status: "unchanged",
7550
+ ...row.outcomeDelivery.messageId !== void 0 ? {
7551
+ messageId: row.outcomeDelivery.messageId
7552
+ } : {}
7553
+ };
7554
+ if (row.outcomeDelivery.status === "discarded") return {
7555
+ status: "discarded"
7556
+ };
7557
+ if (!actionProposalOutcomeFenceValid(row, command, this.clock())) return {
7558
+ status: "conflict"
7559
+ };
7560
+ const thread = this.threads.get(row.threadId);
7561
+ if (!thread) {
7562
+ this.rows.set(row.id, snapshotActionProposal({
7563
+ ...row,
7564
+ outcomeDelivery: {
7565
+ ...row.outcomeDelivery,
7566
+ status: "discarded",
7567
+ lease: null
7568
+ }
7569
+ }));
7570
+ return {
7571
+ status: "discarded"
7572
+ };
7573
+ }
7574
+ if (thread.actorRef !== row.actorRef || thread.tenantRef !== row.tenantRef) return {
7575
+ status: "conflict"
7576
+ };
7577
+ if (thread.activeStreamId) return {
7578
+ status: "busy"
7579
+ };
7580
+ const message = {
7581
+ id: crypto.randomUUID(),
7582
+ role: "assistant",
7583
+ content: actionProposalOutcomeText(row.outcome),
7584
+ createdAt: this.now(),
7585
+ actionProposalOutcome: snapshotActionProposal(row.outcome),
7586
+ ui: snapshotActionProposal(row.outcome.ui)
7587
+ };
7588
+ const next = snapshotActionProposal({
7589
+ ...row,
7590
+ outcomeDelivery: {
7591
+ ...row.outcomeDelivery,
7592
+ status: "admitted",
7593
+ lease: null,
7594
+ messageId: message.id
7595
+ }
7596
+ });
7597
+ thread.messages.push(message);
7598
+ thread.updatedAt = message.createdAt;
7599
+ this.rows.set(row.id, next);
7600
+ return {
7601
+ status: "applied",
7602
+ messageId: message.id
7603
+ };
7604
+ }
5747
7605
  async appendMessage(input) {
5748
7606
  const row = this.threads.get(input.threadId);
5749
7607
  if (row === void 0) {
@@ -5786,6 +7644,9 @@ var InMemoryAgentStore = class {
5786
7644
  } : {},
5787
7645
  ...input.ui !== void 0 ? {
5788
7646
  ui: input.ui
7647
+ } : {},
7648
+ ...input.actionProposalOutcome !== void 0 ? {
7649
+ actionProposalOutcome: input.actionProposalOutcome
5789
7650
  } : {}
5790
7651
  };
5791
7652
  row.messages.push(message);
@@ -5877,6 +7738,9 @@ var InMemoryAgentStore = class {
5877
7738
  async recordToolCall(input) {
5878
7739
  this.toolCalls.set(input.toolCallId, {
5879
7740
  toolCallId: input.toolCallId,
7741
+ ...input.proposalId !== void 0 ? {
7742
+ proposalId: input.proposalId
7743
+ } : {},
5880
7744
  messageId: input.messageId,
5881
7745
  threadId: this.threadIdForMessage(input.messageId) ?? "",
5882
7746
  toolName: input.toolName,
@@ -5890,6 +7754,9 @@ var InMemoryAgentStore = class {
5890
7754
  ...input.approver !== void 0 ? {
5891
7755
  approver: input.approver
5892
7756
  } : {},
7757
+ ...input.confirmation !== void 0 ? {
7758
+ confirmation: structuredClone(input.confirmation)
7759
+ } : {},
5893
7760
  ...input.expiresAt !== void 0 ? {
5894
7761
  expiresAt: input.expiresAt
5895
7762
  } : {}
@@ -6198,7 +8065,7 @@ var InMemoryAgentStore = class {
6198
8065
  };
6199
8066
 
6200
8067
  // src/confirm-token.ts
6201
- var import_node_crypto2 = require("crypto");
8068
+ var import_node_crypto3 = require("crypto");
6202
8069
  var DEFAULT_CONFIRM_TTL_MS = 15 * 60 * 1e3;
6203
8070
  function canonicalJson(value) {
6204
8071
  if (value === void 0) return "null";
@@ -6215,7 +8082,7 @@ function signature(subject, expiresAt, secret) {
6215
8082
  if (secret === "") {
6216
8083
  throw new Error("confirm token: the secret is empty");
6217
8084
  }
6218
- return (0, import_node_crypto2.createHmac)("sha256", secret).update(canonicalJson([
8085
+ return (0, import_node_crypto3.createHmac)("sha256", secret).update(canonicalJson([
6219
8086
  "agent-confirm",
6220
8087
  subject.tool,
6221
8088
  subject.actorId,
@@ -6242,11 +8109,11 @@ function verifyConfirmToken(token, subject, options) {
6242
8109
  if (expiresAt === void 0 || expiresAt < (options.now ?? Date.now())) return false;
6243
8110
  const expected = Buffer.from(signature(subject, expiresAt, options.secret));
6244
8111
  const given = Buffer.from(token.slice(token.indexOf(".") + 1));
6245
- return expected.length === given.length && (0, import_node_crypto2.timingSafeEqual)(expected, given);
8112
+ return expected.length === given.length && (0, import_node_crypto3.timingSafeEqual)(expected, given);
6246
8113
  }
6247
8114
  __name(verifyConfirmToken, "verifyConfirmToken");
6248
8115
  function hashConfirmToken(token) {
6249
- return (0, import_node_crypto2.createHash)("sha256").update(token).digest("hex");
8116
+ return (0, import_node_crypto3.createHash)("sha256").update(token).digest("hex");
6250
8117
  }
6251
8118
  __name(hashConfirmToken, "hashConfirmToken");
6252
8119
  var InMemoryConfirmTokenStore = class {
@@ -6394,12 +8261,12 @@ function withConfirmFields(schema) {
6394
8261
  });
6395
8262
  }
6396
8263
  const { args, fields } = splitConfirm(value);
6397
- const result = await inner.validate(args);
6398
- if (result.issues !== void 0) {
8264
+ const result2 = await inner.validate(args);
8265
+ if (result2.issues !== void 0) {
6399
8266
  return {
6400
8267
  issues: [
6401
8268
  ...issues,
6402
- ...result.issues
8269
+ ...result2.issues
6403
8270
  ]
6404
8271
  };
6405
8272
  }
@@ -6409,10 +8276,10 @@ function withConfirmFields(schema) {
6409
8276
  };
6410
8277
  }
6411
8278
  return {
6412
- value: isRecord(result.value) ? {
6413
- ...result.value,
8279
+ value: isRecord(result2.value) ? {
8280
+ ...result2.value,
6414
8281
  ...fields
6415
- } : result.value
8282
+ } : result2.value
6416
8283
  };
6417
8284
  }, "validate"),
6418
8285
  jsonSchema: {
@@ -6744,6 +8611,309 @@ var SqlTokenStreamSink = class {
6744
8611
  });
6745
8612
  }
6746
8613
  };
8614
+
8615
+ // src/action-proposal-text.ts
8616
+ function resolveTextActionProposalDecision(text, proposals) {
8617
+ const command = parseTextActionProposalCommand(text);
8618
+ if (command.status === "unmatched") return command;
8619
+ const { decision, remember } = command;
8620
+ const pending = proposals.filter((proposal) => proposal.decision === "pending");
8621
+ const explicitId = command.proposalId;
8622
+ if (explicitId !== void 0) {
8623
+ if (!pending.some((proposal) => proposal.id === explicitId)) return {
8624
+ status: "unmatched"
8625
+ };
8626
+ return {
8627
+ status: "decision",
8628
+ proposalId: explicitId,
8629
+ decision,
8630
+ remember
8631
+ };
8632
+ }
8633
+ if (pending.length === 0) return {
8634
+ status: "unmatched"
8635
+ };
8636
+ if (pending.length > 1) return {
8637
+ status: "ambiguous",
8638
+ proposalIds: pending.map(({ id }) => id)
8639
+ };
8640
+ const candidate = pending[0];
8641
+ if (!candidate) return {
8642
+ status: "unmatched"
8643
+ };
8644
+ return {
8645
+ status: "decision",
8646
+ proposalId: candidate.id,
8647
+ decision,
8648
+ remember
8649
+ };
8650
+ }
8651
+ __name(resolveTextActionProposalDecision, "resolveTextActionProposalDecision");
8652
+ function parseTextActionProposalCommand(text) {
8653
+ if (/[\r\n]/u.test(text)) return {
8654
+ status: "unmatched"
8655
+ };
8656
+ const normalized = text.trim();
8657
+ const match = /^(sim|confirmo|confirmar|aprovar|aprovo|cancelar|rejeitar|rejeito|não|nao)(?: (sempre nesta conversa))?(?: #([^\s]+?))?[.!]?$/iu.exec(normalized);
8658
+ if (!match) return {
8659
+ status: "unmatched"
8660
+ };
8661
+ const verb = match[1]?.toLocaleLowerCase("pt-BR");
8662
+ if (verb === void 0) return {
8663
+ status: "unmatched"
8664
+ };
8665
+ const decision = [
8666
+ "cancelar",
8667
+ "rejeitar",
8668
+ "rejeito",
8669
+ "n\xE3o",
8670
+ "nao"
8671
+ ].includes(verb) ? "rejected" : "approved";
8672
+ const remember = match[2] !== void 0;
8673
+ if (remember && decision !== "approved") return {
8674
+ status: "unmatched"
8675
+ };
8676
+ return {
8677
+ status: "command",
8678
+ decision,
8679
+ remember,
8680
+ ...match[3] !== void 0 ? {
8681
+ proposalId: match[3]
8682
+ } : {}
8683
+ };
8684
+ }
8685
+ __name(parseTextActionProposalCommand, "parseTextActionProposalCommand");
8686
+
8687
+ // src/action-proposal-executor.ts
8688
+ var ActionProposalExecutor = class {
8689
+ static {
8690
+ __name(this, "ActionProposalExecutor");
8691
+ }
8692
+ options;
8693
+ constructor(options) {
8694
+ this.options = options;
8695
+ }
8696
+ async execute(proposal) {
8697
+ let ui = createNegotiatedUiCollector(proposal.originToolCallId, {
8698
+ actor: {
8699
+ id: proposal.actorRef
8700
+ },
8701
+ threadId: proposal.threadId
8702
+ });
8703
+ try {
8704
+ const actor = await this.options.resolver.resolve({
8705
+ actorRef: proposal.actorRef,
8706
+ tenantRef: proposal.tenantRef
8707
+ });
8708
+ if (!actor || actor.id !== proposal.actorRef || (actor.tenantRef ?? null) !== proposal.tenantRef) throw new Error("Action proposal requester no longer exists or scope changed");
8709
+ const deps = await this.options.resolveExecution(proposal, actor);
8710
+ if (deps.registry.spec(proposal.toolName)?.kind !== "action") throw new Error("Action proposal tool no longer exists as an action");
8711
+ const context = proposal.executionContext;
8712
+ ui = createNegotiatedUiCollector(proposal.originToolCallId, {
8713
+ actor,
8714
+ threadId: proposal.threadId,
8715
+ ...context?.agentName !== void 0 ? {
8716
+ agentName: context.agentName
8717
+ } : {},
8718
+ ...context?.uiCapabilities !== void 0 ? {
8719
+ uiCapabilities: context.uiCapabilities
8720
+ } : {}
8721
+ }, deps.resolveUiCatalog);
8722
+ const output = await deps.registry.invoke(proposal.toolName, snapshotActionProposal(Object.hasOwn(proposal, "preparationInput") ? proposal.preparationInput : proposal.input), {
8723
+ actor,
8724
+ threadId: proposal.threadId,
8725
+ runId: proposal.originRunId,
8726
+ requestId: context?.requestId ?? proposal.originRunId,
8727
+ toolCallId: proposal.originToolCallId,
8728
+ idempotencyKey: proposal.idempotencyKey,
8729
+ ...context?.agentName !== void 0 ? {
8730
+ agentName: context.agentName
8731
+ } : {},
8732
+ ...context?.persona !== void 0 ? {
8733
+ persona: context.persona
8734
+ } : {},
8735
+ ...context?.pageContext !== void 0 ? {
8736
+ pageContext: context.pageContext
8737
+ } : {},
8738
+ ...context?.uiCapabilities !== void 0 ? {
8739
+ uiCapabilities: context.uiCapabilities
8740
+ } : {},
8741
+ ...deps.host !== void 0 ? {
8742
+ host: deps.host
8743
+ } : {},
8744
+ emitUi: ui.emit
8745
+ }, deps.rolesPolicy, {
8746
+ approvedInput: proposal.input,
8747
+ ...deps.allowedTools !== void 0 ? {
8748
+ allowedTools: deps.allowedTools
8749
+ } : {}
8750
+ });
8751
+ return snapshotActionProposal({
8752
+ status: "succeeded",
8753
+ ...output !== void 0 ? {
8754
+ result: output
8755
+ } : {},
8756
+ ui: ui.components(),
8757
+ ...ui.text() ? {
8758
+ text: ui.text()
8759
+ } : {}
8760
+ });
8761
+ } catch (error) {
8762
+ return {
8763
+ status: "failed",
8764
+ error: error instanceof Error ? error.message : String(error),
8765
+ ui: ui.components(),
8766
+ ...ui.text() ? {
8767
+ text: ui.text()
8768
+ } : {}
8769
+ };
8770
+ }
8771
+ }
8772
+ };
8773
+
8774
+ // src/action-proposal-worker.ts
8775
+ var ActionProposalWorker = class {
8776
+ static {
8777
+ __name(this, "ActionProposalWorker");
8778
+ }
8779
+ options;
8780
+ leaseMs;
8781
+ pollIntervalMs;
8782
+ maxConcurrency;
8783
+ timer;
8784
+ stopped = false;
8785
+ renewals = /* @__PURE__ */ new Set();
8786
+ active;
8787
+ constructor(options) {
8788
+ this.options = options;
8789
+ this.leaseMs = options.leaseMs ?? 3e4;
8790
+ this.pollIntervalMs = options.pollIntervalMs ?? 1e3;
8791
+ this.maxConcurrency = options.maxConcurrency ?? 1;
8792
+ for (const value of [
8793
+ this.leaseMs,
8794
+ this.pollIntervalMs,
8795
+ this.maxConcurrency
8796
+ ]) if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError("Worker configuration must be positive safe integers");
8797
+ if (this.pollIntervalMs >= this.leaseMs / 3) throw new RangeError("Worker poll interval must be below one third of lease");
8798
+ if (!options.workerId) throw new TypeError("workerId is required");
8799
+ }
8800
+ start() {
8801
+ if (this.timer !== void 0 || this.stopped) return;
8802
+ const tick = /* @__PURE__ */ __name(async () => {
8803
+ if (this.stopped) return;
8804
+ try {
8805
+ await this.runOnce();
8806
+ } catch (error) {
8807
+ this.options.onError?.(error);
8808
+ }
8809
+ if (!this.stopped) this.timer = setTimeout(tick, this.pollIntervalMs);
8810
+ }, "tick");
8811
+ this.timer = setTimeout(tick, 0);
8812
+ }
8813
+ async stop() {
8814
+ this.stopped = true;
8815
+ for (const timer of this.renewals) clearInterval(timer);
8816
+ this.renewals.clear();
8817
+ if (this.timer !== void 0) clearTimeout(this.timer);
8818
+ await this.active;
8819
+ }
8820
+ runOnce() {
8821
+ if (this.active) return this.active;
8822
+ if (this.stopped) return Promise.resolve();
8823
+ const task = this.tick();
8824
+ this.active = task;
8825
+ return task.finally(() => {
8826
+ if (this.active === task) this.active = void 0;
8827
+ });
8828
+ }
8829
+ async tick() {
8830
+ await this.options.store.expireActionProposals({
8831
+ limit: 100
8832
+ });
8833
+ const work = [];
8834
+ for (let index = 0; index < this.maxConcurrency && !this.stopped; index++) {
8835
+ const proposal = await this.options.store.claimNextActionProposal({
8836
+ workerId: this.options.workerId,
8837
+ leaseMs: this.leaseMs
8838
+ });
8839
+ if (!proposal) break;
8840
+ work.push(this.execute(proposal));
8841
+ }
8842
+ await Promise.all(work);
8843
+ for (let index = 0; index < 32 && !this.stopped; index++) {
8844
+ const delivery = await this.options.store.claimNextActionProposalOutcome({
8845
+ workerId: this.options.workerId,
8846
+ leaseMs: this.leaseMs
8847
+ });
8848
+ if (!delivery) break;
8849
+ await this.options.store.admitActionProposalOutcome(delivery.lease);
8850
+ }
8851
+ }
8852
+ async execute(proposal) {
8853
+ const lease = proposal.execution?.lease;
8854
+ if (!lease) throw new Error("Claimed action lacks execution lease");
8855
+ let fenced = true;
8856
+ let renewing;
8857
+ const renew = /* @__PURE__ */ __name(() => {
8858
+ if (renewing || !fenced) return;
8859
+ renewing = this.options.store.extendActionProposalLease(proposal, proposal.id, {
8860
+ token: lease.token,
8861
+ generation: lease.generation,
8862
+ leaseMs: this.leaseMs
8863
+ }).then((result2) => {
8864
+ if (result2.status !== "applied") fenced = false;
8865
+ }, (error) => {
8866
+ fenced = false;
8867
+ this.options.onError?.(error);
8868
+ }).finally(() => {
8869
+ renewing = void 0;
8870
+ });
8871
+ }, "renew");
8872
+ const timer = setInterval(renew, this.leaseMs / 3);
8873
+ this.renewals.add(timer);
8874
+ try {
8875
+ const result2 = await this.options.executor.execute(proposal);
8876
+ clearInterval(timer);
8877
+ await renewing;
8878
+ if (!fenced) return;
8879
+ const settled = await this.options.store.settleActionProposal(proposal, proposal.id, {
8880
+ ...result2,
8881
+ token: lease.token,
8882
+ generation: lease.generation
8883
+ });
8884
+ if (settled.status === "applied" && settled.proposal) await this.options.onSettled?.(settled.proposal);
8885
+ } finally {
8886
+ clearInterval(timer);
8887
+ this.renewals.delete(timer);
8888
+ await renewing;
8889
+ }
8890
+ }
8891
+ };
8892
+
8893
+ // src/action-proposal-capabilities.ts
8894
+ function assertIndependentActionCapabilities(store, resolver) {
8895
+ const candidate = store;
8896
+ if (!resolver || typeof resolver.resolve !== "function") throw new Error("Independent action mode requires a BackgroundActorResolver");
8897
+ if (!isChatQueueStore(store) || candidate.actionProposalAdmissionSupported === false) throw new Error("Independent action mode requires transactional thread admission");
8898
+ const required = [
8899
+ "createActionProposal",
8900
+ "getActionProposal",
8901
+ "listActionProposals",
8902
+ "decideActionProposal",
8903
+ "claimActionProposal",
8904
+ "extendActionProposalLease",
8905
+ "settleActionProposal",
8906
+ "claimNextActionProposal",
8907
+ "expireActionProposals",
8908
+ "getThreadActionProposalScope",
8909
+ "claimNextActionProposalOutcome",
8910
+ "admitActionProposalOutcome",
8911
+ "createReplacingActionProposal",
8912
+ "supersedeActionProposal"
8913
+ ];
8914
+ if (required.some((method) => typeof candidate[method] !== "function")) throw new Error("Independent action mode requires proposal, worker, outcome and admission capabilities on the conversation store");
8915
+ }
8916
+ __name(assertIndependentActionCapabilities, "assertIndependentActionCapabilities");
6747
8917
  // Annotate the CommonJS export names for ESM import in node:
6748
8918
  0 && (module.exports = {
6749
8919
  AGENT_ACTOR_DIRECTORY,
@@ -6778,6 +8948,8 @@ var SqlTokenStreamSink = class {
6778
8948
  APPROVAL_EXPIRED_REASON,
6779
8949
  ASK_TOOL_DESCRIPTION,
6780
8950
  ASK_TOOL_NAME,
8951
+ ActionProposalExecutor,
8952
+ ActionProposalWorker,
6781
8953
  AgentRegistry,
6782
8954
  AgentStreamError,
6783
8955
  CONFIRM_JSON_SCHEMA_PROPERTIES,
@@ -6798,6 +8970,7 @@ var SqlTokenStreamSink = class {
6798
8970
  DefaultRolesPolicy,
6799
8971
  ELICITATION_INPUT_TYPES,
6800
8972
  GLOBAL_SCOPE,
8973
+ InMemoryActionProposalStore,
6801
8974
  InMemoryAgentStore,
6802
8975
  InMemoryConfirmTokenStore,
6803
8976
  MAX_ASK_QUESTIONS,
@@ -6821,15 +8994,25 @@ var SqlTokenStreamSink = class {
6821
8994
  THREAD_DETAIL_CONTENT_CHARS,
6822
8995
  ToolDisabledError,
6823
8996
  ToolForbiddenError,
8997
+ ToolInputDriftError,
6824
8998
  ToolInputInvalidError,
6825
8999
  ToolNotFoundError,
9000
+ ToolPreflightDeniedError,
6826
9001
  ToolRegistry,
6827
9002
  UNFINISHED_TOOL_CALL,
9003
+ actionProposalCreationMatches,
9004
+ actionProposalOutcomeFenceValid,
9005
+ actionProposalOutcomeScope,
9006
+ actionProposalOutcomeText,
9007
+ actionProposalReceipt,
9008
+ actionProposalScopeMatches,
6828
9009
  actorScope,
6829
9010
  agentDiagnosticKey,
6830
9011
  agentFailureCode,
6831
9012
  askInputSchema,
6832
9013
  askToolDefinition,
9014
+ assertIndependentActionCapabilities,
9015
+ attachActionProposalOutcome,
6833
9016
  bucketByActor,
6834
9017
  bucketByModel,
6835
9018
  bucketByThread,
@@ -6837,12 +9020,15 @@ var SqlTokenStreamSink = class {
6837
9020
  buildMemoryBlock,
6838
9021
  buildSkillsBlock,
6839
9022
  canActorUseTool,
9023
+ canonicalActionProposalJson,
6840
9024
  canonicalJson,
9025
+ claimActionProposalOutcome,
6841
9026
  closeEmptyRoles,
6842
9027
  compositeSkillProvider,
6843
9028
  confirmTokenExpiry,
6844
9029
  createFrameBuffer,
6845
9030
  createIncrementalGate,
9031
+ createNegotiatedUiCollector,
6846
9032
  createNoopEmitUi,
6847
9033
  createUiCollector,
6848
9034
  danglingToolCallIds,
@@ -6855,6 +9041,7 @@ var SqlTokenStreamSink = class {
6855
9041
  detachedStarted,
6856
9042
  detachedUnsettled,
6857
9043
  encodeStreamEvent,
9044
+ escapeUnsafeToolUiText,
6858
9045
  estimateCost,
6859
9046
  estimateMessageTokens,
6860
9047
  exhaustedWindow,
@@ -6869,6 +9056,7 @@ var SqlTokenStreamSink = class {
6869
9056
  gateFollowUps,
6870
9057
  gateTail,
6871
9058
  hashConfirmToken,
9059
+ initialActionProposal,
6872
9060
  intersectAllowLists,
6873
9061
  invokeWithTransientRetry,
6874
9062
  isChatQueueStore,
@@ -6887,7 +9075,9 @@ var SqlTokenStreamSink = class {
6887
9075
  observeTurnFrames,
6888
9076
  offerMemories,
6889
9077
  offerSkills,
9078
+ parseTextActionProposalCommand,
6890
9079
  personaCatalogEntry,
9080
+ prepareActionProposal,
6891
9081
  publishAgentDelegated,
6892
9082
  publishAgentMemoryResolved,
6893
9083
  publishAgentMemoryWritten,
@@ -6913,12 +9103,14 @@ var SqlTokenStreamSink = class {
6913
9103
  rememberToolDefinition,
6914
9104
  renderElicitationAnswers,
6915
9105
  repairInstruction,
9106
+ resolveActionProposalApproval,
6916
9107
  resolveElicitation,
6917
9108
  resolveGateLookback,
6918
9109
  resolveMemoryDigest,
6919
9110
  resolveOutputGateMode,
6920
9111
  resolvePersonaAlias,
6921
9112
  resolveSkillCatalog,
9113
+ resolveTextActionProposalDecision,
6922
9114
  resolveToolTransientRetryNumbers,
6923
9115
  rollupThreadUsage,
6924
9116
  runAgentLoop,
@@ -6935,6 +9127,7 @@ var SqlTokenStreamSink = class {
6935
9127
  skillInputSchema,
6936
9128
  skillToolDefinition,
6937
9129
  skillWriteVerdict,
9130
+ snapshotActionProposal,
6938
9131
  stampToolKinds,
6939
9132
  staticModelCatalog,
6940
9133
  staticSkillProvider,
@@ -6945,8 +9138,18 @@ var SqlTokenStreamSink = class {
6945
9138
  toolCallContext,
6946
9139
  traceLlmTurn,
6947
9140
  traceToolExecution,
9141
+ transitionActionProposalClaim,
9142
+ transitionActionProposalDecision,
9143
+ transitionActionProposalLease,
9144
+ transitionActionProposalSettlement,
9145
+ transitionActionProposalSupersession,
6948
9146
  truncateDetailContent,
6949
9147
  unwrapToolStepOutput,
9148
+ validateActionProposalCreation,
9149
+ validateActionProposalDiscoveryIndexBatch,
9150
+ validateActionProposalExpiryBatch,
9151
+ validateActionProposalListQuery,
9152
+ validateActionProposalWorkerClaim,
6950
9153
  validateElicitationAnswer,
6951
9154
  validateElicitationValue,
6952
9155
  validateStructured,
@@ -6959,6 +9162,7 @@ var SqlTokenStreamSink = class {
6959
9162
  withSkillTool,
6960
9163
  withToolTimeout,
6961
9164
  withTurnFrames,
9165
+ wrapToolPreflightDenied,
6962
9166
  wrapToolStepOutput,
6963
9167
  writeMemory
6964
9168
  });