@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.js CHANGED
@@ -169,16 +169,16 @@ function observeTurnFrames(writer, now = Date.now) {
169
169
  };
170
170
  }
171
171
  __name(observeTurnFrames, "observeTurnFrames");
172
- function withTurnFrames(result, frames) {
172
+ function withTurnFrames(result2, frames) {
173
173
  return {
174
- ...result,
175
- ...result.reasoning === void 0 && frames.reasoning !== void 0 ? {
174
+ ...result2,
175
+ ...result2.reasoning === void 0 && frames.reasoning !== void 0 ? {
176
176
  reasoning: frames.reasoning
177
177
  } : {},
178
- ...result.reasoningMs === void 0 && frames.reasoningMs !== void 0 ? {
178
+ ...result2.reasoningMs === void 0 && frames.reasoningMs !== void 0 ? {
179
179
  reasoningMs: frames.reasoningMs
180
180
  } : {},
181
- ...result.ui === void 0 && frames.ui !== void 0 ? {
181
+ ...result2.ui === void 0 && frames.ui !== void 0 ? {
182
182
  ui: frames.ui
183
183
  } : {}
184
184
  };
@@ -187,8 +187,9 @@ __name(withTurnFrames, "withTurnFrames");
187
187
 
188
188
  // src/tool-ui.ts
189
189
  var TOOL_STEP_UI = "@@nestjs-agent/tool-step-ui";
190
- function wrapToolStepOutput(output, ui) {
191
- if (ui.length === 0) {
190
+ function wrapToolStepOutput(output, ui, text) {
191
+ const reserved = typeof output === "object" && output !== null && TOOL_STEP_UI in output;
192
+ if (ui.length === 0 && !reserved && !text) {
192
193
  return output;
193
194
  }
194
195
  const wrapped = {
@@ -196,17 +197,36 @@ function wrapToolStepOutput(output, ui) {
196
197
  output,
197
198
  ui: [
198
199
  ...ui
199
- ]
200
+ ],
201
+ ...text ? {
202
+ text
203
+ } : {}
200
204
  };
201
205
  return wrapped;
202
206
  }
203
207
  __name(wrapToolStepOutput, "wrapToolStepOutput");
208
+ function wrapToolPreflightDenied(reason) {
209
+ const wrapped = {
210
+ [TOOL_STEP_UI]: 1,
211
+ output: null,
212
+ ui: [],
213
+ preflightDenied: reason
214
+ };
215
+ return wrapped;
216
+ }
217
+ __name(wrapToolPreflightDenied, "wrapToolPreflightDenied");
204
218
  function unwrapToolStepOutput(raw) {
205
219
  if (typeof raw === "object" && raw !== null && raw[TOOL_STEP_UI] === 1) {
206
220
  const wrapped = raw;
207
221
  return {
208
222
  output: wrapped.output,
209
- ui: Array.isArray(wrapped.ui) ? wrapped.ui : []
223
+ ui: Array.isArray(wrapped.ui) ? wrapped.ui : [],
224
+ ...typeof wrapped.text === "string" ? {
225
+ text: wrapped.text
226
+ } : {},
227
+ ...typeof wrapped.preflightDenied === "string" ? {
228
+ preflightDenied: wrapped.preflightDenied
229
+ } : {}
210
230
  };
211
231
  }
212
232
  return {
@@ -255,6 +275,14 @@ function createUiCollector(toolCallId, write) {
255
275
  ...options.version !== void 0 ? {
256
276
  version: options.version
257
277
  } : {},
278
+ ...options.fallbackText !== void 0 ? {
279
+ fallbackText: options.fallbackText
280
+ } : {},
281
+ ...options.componentVersions !== void 0 ? {
282
+ componentVersions: {
283
+ ...options.componentVersions
284
+ }
285
+ } : {},
258
286
  toolCallId
259
287
  };
260
288
  pushed.set(id, entry);
@@ -289,6 +317,10 @@ function mergeUi(...lists) {
289
317
  ];
290
318
  }
291
319
  __name(mergeUi, "mergeUi");
320
+ function escapeUnsafeToolUiText(text) {
321
+ return text.replaceAll("\0", "\\u0000").replace(/[\uD800-\uDFFF]/gu, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
322
+ }
323
+ __name(escapeUnsafeToolUiText, "escapeUnsafeToolUiText");
292
324
 
293
325
  // src/tool-presentation.ts
294
326
  var ALL_AGENTS = "*";
@@ -500,12 +532,18 @@ function toolCallApprovalFromRow(row) {
500
532
  if (row.approver === null || row.approver === void 0) {
501
533
  return null;
502
534
  }
503
- const status = row.status === "pending_approval" ? "pending" : row.status === "rejected" ? "rejected" : row.status === "expired" ? "expired" : "approved";
535
+ const status = row.status === "pending_approval" || row.status === "proposed" ? "pending" : row.status === "rejected" ? "rejected" : row.status === "expired" ? "expired" : "approved";
504
536
  const expiresAt = row.expiresAt instanceof Date ? row.expiresAt.toISOString() : typeof row.expiresAt === "string" ? row.expiresAt : void 0;
505
537
  const decided = status === "approved" || status === "rejected";
506
538
  return {
507
539
  toolCallId: row.toolCallId,
540
+ ...row.proposalId != null ? {
541
+ proposalId: row.proposalId
542
+ } : {},
508
543
  approver: row.approver,
544
+ ...row.confirmation != null ? {
545
+ confirmation: row.confirmation
546
+ } : {},
509
547
  status,
510
548
  ...expiresAt !== void 0 ? {
511
549
  expiresAt
@@ -556,9 +594,9 @@ function bucketByModel(rows, prices) {
556
594
  bucket.costUsd += rowCost(row, prices);
557
595
  byModel.set(row.modelId, bucket);
558
596
  }
559
- const result = [];
597
+ const result2 = [];
560
598
  for (const [modelId, bucket] of byModel) {
561
- result.push({
599
+ result2.push({
562
600
  modelId,
563
601
  requests: bucket.requests,
564
602
  inputTokens: bucket.inputTokens,
@@ -566,8 +604,8 @@ function bucketByModel(rows, prices) {
566
604
  costUsd: bucket.costUsd
567
605
  });
568
606
  }
569
- result.sort((left, right) => right.costUsd - left.costUsd || left.modelId.localeCompare(right.modelId));
570
- return result;
607
+ result2.sort((left, right) => right.costUsd - left.costUsd || left.modelId.localeCompare(right.modelId));
608
+ return result2;
571
609
  }
572
610
  __name(bucketByModel, "bucketByModel");
573
611
  function bucketByActor(rows, prices) {
@@ -585,9 +623,9 @@ function bucketByActor(rows, prices) {
585
623
  bucket.threadIds.add(row.threadId);
586
624
  byActor.set(row.actorRef, bucket);
587
625
  }
588
- const result = [];
626
+ const result2 = [];
589
627
  for (const [actorRef, bucket] of byActor) {
590
- result.push({
628
+ result2.push({
591
629
  actorRef,
592
630
  requests: bucket.requests,
593
631
  totalTokens: bucket.totalTokens,
@@ -595,8 +633,8 @@ function bucketByActor(rows, prices) {
595
633
  threadCount: bucket.threadIds.size
596
634
  });
597
635
  }
598
- result.sort((left, right) => right.costUsd - left.costUsd || left.actorRef.localeCompare(right.actorRef));
599
- return result;
636
+ result2.sort((left, right) => right.costUsd - left.costUsd || left.actorRef.localeCompare(right.actorRef));
637
+ return result2;
600
638
  }
601
639
  __name(bucketByActor, "bucketByActor");
602
640
  function bucketByThread(rows, prices, threads, options) {
@@ -612,13 +650,13 @@ function bucketByThread(rows, prices, threads, options) {
612
650
  bucket.costUsd += rowCost(row, prices);
613
651
  byThread.set(row.threadId, bucket);
614
652
  }
615
- const result = [];
653
+ const result2 = [];
616
654
  for (const [threadId, bucket] of byThread) {
617
655
  const thread = threads.get(threadId);
618
656
  if (thread === void 0 && !options.includeUnknownThreads) {
619
657
  continue;
620
658
  }
621
- result.push({
659
+ result2.push({
622
660
  threadId,
623
661
  title: thread?.title ?? "",
624
662
  actorRef: thread?.actorRef ?? "",
@@ -627,8 +665,8 @@ function bucketByThread(rows, prices, threads, options) {
627
665
  costUsd: bucket.costUsd
628
666
  });
629
667
  }
630
- result.sort((left, right) => right.costUsd - left.costUsd || left.threadId.localeCompare(right.threadId));
631
- return result.slice(0, options.limit);
668
+ result2.sort((left, right) => right.costUsd - left.costUsd || left.threadId.localeCompare(right.threadId));
669
+ return result2.slice(0, options.limit);
632
670
  }
633
671
  __name(bucketByThread, "bucketByThread");
634
672
  function rollupThreadUsage(rows, prices) {
@@ -660,16 +698,16 @@ function bucketUsageTrend(rows, prices) {
660
698
  bucket.costUsd += rowCost(row, prices);
661
699
  byDay.set(row.day, bucket);
662
700
  }
663
- const result = [];
701
+ const result2 = [];
664
702
  for (const [day, bucket] of byDay) {
665
- result.push({
703
+ result2.push({
666
704
  day,
667
705
  totalTokens: bucket.totalTokens,
668
706
  costUsd: bucket.costUsd
669
707
  });
670
708
  }
671
- result.sort((left, right) => left.day.localeCompare(right.day));
672
- return result;
709
+ result2.sort((left, right) => left.day.localeCompare(right.day));
710
+ return result2;
673
711
  }
674
712
  __name(bucketUsageTrend, "bucketUsageTrend");
675
713
  function dayBoundsUtc(range) {
@@ -1113,13 +1151,13 @@ async function validateStructured(schema, text, reported) {
1113
1151
  issues: []
1114
1152
  };
1115
1153
  }
1116
- const result = await schema["~standard"].validate(candidate);
1117
- return result.issues !== void 0 ? {
1154
+ const result2 = await schema["~standard"].validate(candidate);
1155
+ return result2.issues !== void 0 ? {
1118
1156
  ok: false,
1119
- issues: result.issues
1157
+ issues: result2.issues
1120
1158
  } : {
1121
1159
  ok: true,
1122
- value: result.value
1160
+ value: result2.value
1123
1161
  };
1124
1162
  }
1125
1163
  __name(validateStructured, "validateStructured");
@@ -2298,12 +2336,12 @@ function memoryWriteVerdict(request) {
2298
2336
  };
2299
2337
  }
2300
2338
  __name(memoryWriteVerdict, "memoryWriteVerdict");
2301
- function memoryForgetVerdict({ record, actor }) {
2302
- return record.scope === actorScope(actor) ? {
2339
+ function memoryForgetVerdict({ record: record2, actor }) {
2340
+ return record2.scope === actorScope(actor) ? {
2303
2341
  allowed: true
2304
2342
  } : {
2305
2343
  allowed: false,
2306
- reason: `"${record.scope}" is not this actor's own scope; deleting it is an administrative action`
2344
+ reason: `"${record2.scope}" is not this actor's own scope; deleting it is an administrative action`
2307
2345
  };
2308
2346
  }
2309
2347
  __name(memoryForgetVerdict, "memoryForgetVerdict");
@@ -2346,7 +2384,7 @@ async function writeMemory({ config, digest, call, ctx, runId }) {
2346
2384
  error: verdict.reason
2347
2385
  };
2348
2386
  }
2349
- const record = await write({
2387
+ const record2 = await write({
2350
2388
  key,
2351
2389
  text,
2352
2390
  scope,
@@ -2360,7 +2398,7 @@ async function writeMemory({ config, digest, call, ctx, runId }) {
2360
2398
  });
2361
2399
  return {
2362
2400
  ok: true,
2363
- record
2401
+ record: record2
2364
2402
  };
2365
2403
  }
2366
2404
  __name(writeMemory, "writeMemory");
@@ -2412,7 +2450,7 @@ function danglingToolCallIds(messages) {
2412
2450
  if (message.role !== "assistant" || calls.length === 0) {
2413
2451
  continue;
2414
2452
  }
2415
- const settled = new Set((message.toolResults ?? []).map((result) => result.id));
2453
+ const settled = new Set((message.toolResults ?? []).map((result2) => result2.id));
2416
2454
  for (const call of calls) {
2417
2455
  if (!settled.has(call.id)) {
2418
2456
  ids.push(call.id);
@@ -2480,7 +2518,7 @@ function settleDanglingToolCalls(messages, outcomes = []) {
2480
2518
  return message;
2481
2519
  }
2482
2520
  const results = message.toolResults ?? [];
2483
- const settled = new Set(results.map((result) => result.id));
2521
+ const settled = new Set(results.map((result2) => result2.id));
2484
2522
  const missing = calls.filter((call) => !settled.has(call.id));
2485
2523
  if (missing.length === 0) {
2486
2524
  return message;
@@ -2588,6 +2626,1111 @@ async function settleUnsettledDelegation(args) {
2588
2626
  }
2589
2627
  __name(settleUnsettledDelegation, "settleUnsettledDelegation");
2590
2628
 
2629
+ // src/action-proposal-discovery.ts
2630
+ function validateActionProposalWorkerClaim(command, now) {
2631
+ if (!Number.isSafeInteger(now)) throw new RangeError("Invalid action proposal clock");
2632
+ if (typeof command.workerId !== "string" || command.workerId.length === 0) throw new TypeError("workerId must be a nonempty string");
2633
+ if (!Number.isSafeInteger(command.leaseMs) || command.leaseMs <= 0 || !Number.isSafeInteger(now + command.leaseMs)) throw new RangeError("leaseMs must be positive safe integer milliseconds");
2634
+ }
2635
+ __name(validateActionProposalWorkerClaim, "validateActionProposalWorkerClaim");
2636
+ function validateActionProposalExpiryBatch(command, now) {
2637
+ if (!Number.isSafeInteger(now)) throw new RangeError("Invalid action proposal clock");
2638
+ validateActionProposalDiscoveryIndexBatch(command);
2639
+ }
2640
+ __name(validateActionProposalExpiryBatch, "validateActionProposalExpiryBatch");
2641
+ function validateActionProposalDiscoveryIndexBatch(command) {
2642
+ if (!Number.isInteger(command.limit) || command.limit < 1 || command.limit > 1e3) throw new RangeError("limit must be an integer from 1 to 1000");
2643
+ }
2644
+ __name(validateActionProposalDiscoveryIndexBatch, "validateActionProposalDiscoveryIndexBatch");
2645
+
2646
+ // src/action-proposal-outcome.ts
2647
+ function attachActionProposalOutcome(row, now, ui = [], text) {
2648
+ if (row.outcome) return row;
2649
+ const execution = row.execution;
2650
+ const outcome = {
2651
+ id: `${row.id}:outcome:1`,
2652
+ proposalId: row.id,
2653
+ outcomeVersion: 1,
2654
+ tenantRef: row.tenantRef,
2655
+ actorRef: row.actorRef,
2656
+ threadId: row.threadId,
2657
+ originRunId: row.originRunId,
2658
+ originToolCallId: row.originToolCallId,
2659
+ toolName: row.toolName,
2660
+ decision: row.decision,
2661
+ ui,
2662
+ createdAt: now,
2663
+ ...text !== void 0 ? {
2664
+ text
2665
+ } : {},
2666
+ ...execution?.status === "succeeded" || execution?.status === "failed" ? {
2667
+ executionStatus: execution.status
2668
+ } : {},
2669
+ ...execution && Object.hasOwn(execution, "result") ? {
2670
+ result: execution.result
2671
+ } : {},
2672
+ ...execution?.error !== void 0 ? {
2673
+ error: execution.error
2674
+ } : row.decisionAudit?.reason !== void 0 ? {
2675
+ error: row.decisionAudit.reason
2676
+ } : {}
2677
+ };
2678
+ return snapshotActionProposal({
2679
+ ...row,
2680
+ outcome,
2681
+ outcomeDelivery: {
2682
+ status: "pending",
2683
+ generation: 0,
2684
+ lease: null
2685
+ }
2686
+ });
2687
+ }
2688
+ __name(attachActionProposalOutcome, "attachActionProposalOutcome");
2689
+ function claimActionProposalOutcome(row, command, now, token) {
2690
+ validateActionProposalWorkerClaim(command, now);
2691
+ const delivery = row.outcomeDelivery;
2692
+ if (!row.outcome || !delivery || delivery.status !== "pending" || delivery.lease && now < delivery.lease.expiresAt) return null;
2693
+ const generation = delivery.generation + 1;
2694
+ return snapshotActionProposal({
2695
+ ...row,
2696
+ outcomeDelivery: {
2697
+ ...delivery,
2698
+ generation,
2699
+ lease: {
2700
+ token,
2701
+ generation,
2702
+ workerId: command.workerId,
2703
+ expiresAt: now + command.leaseMs
2704
+ }
2705
+ }
2706
+ });
2707
+ }
2708
+ __name(claimActionProposalOutcome, "claimActionProposalOutcome");
2709
+ function actionProposalOutcomeFenceValid(row, command, now) {
2710
+ if (!Number.isSafeInteger(now)) throw new RangeError("Invalid server clock");
2711
+ 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");
2712
+ const delivery = row.outcomeDelivery;
2713
+ 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;
2714
+ }
2715
+ __name(actionProposalOutcomeFenceValid, "actionProposalOutcomeFenceValid");
2716
+ function outcomeSummary(outcome) {
2717
+ 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)}` : ""}`;
2718
+ if (outcome.executionStatus === "failed") return `Action ${JSON.stringify(outcome.toolName)} failed (proposal ${JSON.stringify(outcome.proposalId)}): ${JSON.stringify(outcome.error ?? "Execution failed")}`;
2719
+ 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)}` : ""}`;
2720
+ }
2721
+ __name(outcomeSummary, "outcomeSummary");
2722
+ function actionProposalOutcomeScope(outcome) {
2723
+ return {
2724
+ tenantRef: outcome.tenantRef,
2725
+ actorRef: outcome.actorRef,
2726
+ threadId: outcome.threadId
2727
+ };
2728
+ }
2729
+ __name(actionProposalOutcomeScope, "actionProposalOutcomeScope");
2730
+ function actionProposalOutcomeText(outcome) {
2731
+ return outcomeSummary(outcome) + (outcome.text ? ` UI: ${JSON.stringify(outcome.text)}` : "");
2732
+ }
2733
+ __name(actionProposalOutcomeText, "actionProposalOutcomeText");
2734
+
2735
+ // src/genui/schema.ts
2736
+ function isStandardSchema(schema) {
2737
+ return typeof schema === "object" && schema !== null && "~standard" in schema && typeof schema["~standard"]?.validate === "function";
2738
+ }
2739
+ __name(isStandardSchema, "isStandardSchema");
2740
+ function toJsonSchema(schema) {
2741
+ if (!isStandardSchema(schema)) {
2742
+ return schema;
2743
+ }
2744
+ const standard = schema["~standard"];
2745
+ const converter = standard.jsonSchema;
2746
+ if (converter === void 0 || typeof converter.input !== "function") {
2747
+ return void 0;
2748
+ }
2749
+ try {
2750
+ return converter.input({
2751
+ target: "draft-07"
2752
+ });
2753
+ } catch {
2754
+ return void 0;
2755
+ }
2756
+ }
2757
+ __name(toJsonSchema, "toJsonSchema");
2758
+ async function validateProps(schema, value, validator) {
2759
+ if (isStandardSchema(schema)) {
2760
+ const result2 = await schema["~standard"].validate(value);
2761
+ if (result2.issues !== void 0) {
2762
+ return {
2763
+ ok: false,
2764
+ issues: toGenuiIssues(result2.issues)
2765
+ };
2766
+ }
2767
+ return {
2768
+ ok: true,
2769
+ value: result2.value
2770
+ };
2771
+ }
2772
+ const issues = validator.validate(schema, value);
2773
+ return issues.length > 0 ? {
2774
+ ok: false,
2775
+ issues
2776
+ } : {
2777
+ ok: true,
2778
+ value
2779
+ };
2780
+ }
2781
+ __name(validateProps, "validateProps");
2782
+ function validatePropsSync(schema, value, validator) {
2783
+ if (!isStandardSchema(schema)) {
2784
+ const issues = validator.validate(schema, value);
2785
+ return issues.length > 0 ? {
2786
+ ok: false,
2787
+ issues
2788
+ } : {
2789
+ ok: true,
2790
+ value
2791
+ };
2792
+ }
2793
+ const result2 = schema["~standard"].validate(value);
2794
+ if (result2 instanceof Promise) {
2795
+ result2.catch(() => void 0);
2796
+ return void 0;
2797
+ }
2798
+ return result2.issues !== void 0 ? {
2799
+ ok: false,
2800
+ issues: toGenuiIssues(result2.issues)
2801
+ } : {
2802
+ ok: true,
2803
+ value: result2.value
2804
+ };
2805
+ }
2806
+ __name(validatePropsSync, "validatePropsSync");
2807
+ function toGenuiIssues(issues) {
2808
+ return issues.map((issue4) => ({
2809
+ path: (issue4.path ?? []).map((segment) => typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment),
2810
+ message: issue4.message
2811
+ }));
2812
+ }
2813
+ __name(toGenuiIssues, "toGenuiIssues");
2814
+ function formatIssues(issues) {
2815
+ return issues.map((issue4) => `${issue4.path.length > 0 ? formatPath(issue4.path) : "(root)"}: ${issue4.message}`).join("; ");
2816
+ }
2817
+ __name(formatIssues, "formatIssues");
2818
+ function formatPath(path) {
2819
+ return path.map((segment, index) => typeof segment === "number" ? `[${segment}]` : index === 0 ? segment : `.${segment}`).join("");
2820
+ }
2821
+ __name(formatPath, "formatPath");
2822
+ var builtinJsonSchemaValidator = {
2823
+ validate(schema, value) {
2824
+ const issues = [];
2825
+ check(schema, value, [], schema, issues, 0);
2826
+ return issues;
2827
+ }
2828
+ };
2829
+ var MAX_DEPTH = 64;
2830
+ function typeOf(value) {
2831
+ if (value === null) return "null";
2832
+ if (Array.isArray(value)) return "array";
2833
+ return typeof value;
2834
+ }
2835
+ __name(typeOf, "typeOf");
2836
+ function matchesType(type, value) {
2837
+ switch (type) {
2838
+ case "integer":
2839
+ return typeof value === "number" && Number.isInteger(value);
2840
+ case "number":
2841
+ return typeof value === "number" && Number.isFinite(value);
2842
+ case "object":
2843
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2844
+ default:
2845
+ return typeOf(value) === type;
2846
+ }
2847
+ }
2848
+ __name(matchesType, "matchesType");
2849
+ function resolveRef(root, ref) {
2850
+ if (ref === "#") return root;
2851
+ if (!ref.startsWith("#/")) return void 0;
2852
+ let current = root;
2853
+ for (const raw of ref.slice(2).split("/")) {
2854
+ const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
2855
+ if (typeof current !== "object" || current === null) return void 0;
2856
+ current = current[key];
2857
+ }
2858
+ return typeof current === "object" && current !== null ? current : void 0;
2859
+ }
2860
+ __name(resolveRef, "resolveRef");
2861
+ function deepEqual(a, b) {
2862
+ if (a === b) return true;
2863
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
2864
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2865
+ const keysA = Object.keys(a);
2866
+ const keysB = Object.keys(b);
2867
+ if (keysA.length !== keysB.length) return false;
2868
+ return keysA.every((key) => deepEqual(a[key], b[key]));
2869
+ }
2870
+ __name(deepEqual, "deepEqual");
2871
+ function check(schema, value, path, root, issues, depth) {
2872
+ if (schema === true || schema === void 0) return;
2873
+ if (schema === false) {
2874
+ issues.push({
2875
+ path,
2876
+ message: "is not allowed"
2877
+ });
2878
+ return;
2879
+ }
2880
+ if (typeof schema !== "object" || schema === null) return;
2881
+ if (depth > MAX_DEPTH) {
2882
+ issues.push({
2883
+ path,
2884
+ message: "is nested too deeply"
2885
+ });
2886
+ return;
2887
+ }
2888
+ const s = schema;
2889
+ if (typeof s.$ref === "string") {
2890
+ const target = resolveRef(root, s.$ref);
2891
+ if (target === void 0) {
2892
+ issues.push({
2893
+ path,
2894
+ message: `references unknown schema ${s.$ref}`
2895
+ });
2896
+ return;
2897
+ }
2898
+ check(target, value, path, root, issues, depth + 1);
2899
+ }
2900
+ if (s.type !== void 0) {
2901
+ const types = Array.isArray(s.type) ? s.type : [
2902
+ s.type
2903
+ ];
2904
+ if (!types.some((type) => matchesType(type, value))) {
2905
+ issues.push({
2906
+ path,
2907
+ message: `must be ${types.join(" or ")}`
2908
+ });
2909
+ return;
2910
+ }
2911
+ }
2912
+ if (Array.isArray(s.enum) && !s.enum.some((option) => deepEqual(option, value))) {
2913
+ issues.push({
2914
+ path,
2915
+ message: `must be one of ${s.enum.map((option) => JSON.stringify(option)).join(", ")}`
2916
+ });
2917
+ }
2918
+ if ("const" in s && !deepEqual(s.const, value)) {
2919
+ issues.push({
2920
+ path,
2921
+ message: `must be ${JSON.stringify(s.const)}`
2922
+ });
2923
+ }
2924
+ if (typeof value === "string") {
2925
+ if (typeof s.minLength === "number" && value.length < s.minLength) {
2926
+ issues.push({
2927
+ path,
2928
+ message: `must be at least ${s.minLength} characters`
2929
+ });
2930
+ }
2931
+ if (typeof s.maxLength === "number" && value.length > s.maxLength) {
2932
+ issues.push({
2933
+ path,
2934
+ message: `must be at most ${s.maxLength} characters`
2935
+ });
2936
+ }
2937
+ if (typeof s.pattern === "string") {
2938
+ let pattern;
2939
+ try {
2940
+ pattern = new RegExp(s.pattern, "u");
2941
+ } catch {
2942
+ pattern = void 0;
2943
+ }
2944
+ if (pattern !== void 0 && !pattern.test(value)) {
2945
+ issues.push({
2946
+ path,
2947
+ message: `must match ${s.pattern}`
2948
+ });
2949
+ }
2950
+ }
2951
+ }
2952
+ if (typeof value === "number") {
2953
+ if (typeof s.minimum === "number" && value < s.minimum) {
2954
+ issues.push({
2955
+ path,
2956
+ message: `must be >= ${s.minimum}`
2957
+ });
2958
+ }
2959
+ if (typeof s.maximum === "number" && value > s.maximum) {
2960
+ issues.push({
2961
+ path,
2962
+ message: `must be <= ${s.maximum}`
2963
+ });
2964
+ }
2965
+ }
2966
+ if (Array.isArray(value)) {
2967
+ if (typeof s.minItems === "number" && value.length < s.minItems) {
2968
+ issues.push({
2969
+ path,
2970
+ message: `must have at least ${s.minItems} items`
2971
+ });
2972
+ }
2973
+ if (typeof s.maxItems === "number" && value.length > s.maxItems) {
2974
+ issues.push({
2975
+ path,
2976
+ message: `must have at most ${s.maxItems} items`
2977
+ });
2978
+ }
2979
+ if (s.items !== void 0 && !Array.isArray(s.items)) {
2980
+ value.forEach((item, index) => {
2981
+ check(s.items, item, [
2982
+ ...path,
2983
+ index
2984
+ ], root, issues, depth + 1);
2985
+ });
2986
+ }
2987
+ }
2988
+ if (matchesType("object", value)) {
2989
+ const record2 = value;
2990
+ const properties = typeof s.properties === "object" && s.properties !== null ? s.properties : {};
2991
+ if (Array.isArray(s.required)) {
2992
+ for (const key of s.required) {
2993
+ if (record2[key] === void 0) {
2994
+ issues.push({
2995
+ path: [
2996
+ ...path,
2997
+ key
2998
+ ],
2999
+ message: "is required"
3000
+ });
3001
+ }
3002
+ }
3003
+ }
3004
+ for (const [key, entry] of Object.entries(record2)) {
3005
+ if (key in properties) {
3006
+ if (entry !== void 0) {
3007
+ check(properties[key], entry, [
3008
+ ...path,
3009
+ key
3010
+ ], root, issues, depth + 1);
3011
+ }
3012
+ } else if (s.additionalProperties === false) {
3013
+ issues.push({
3014
+ path: [
3015
+ ...path,
3016
+ key
3017
+ ],
3018
+ message: "is not a known property"
3019
+ });
3020
+ } else if (typeof s.additionalProperties === "object" && s.additionalProperties !== null) {
3021
+ check(s.additionalProperties, entry, [
3022
+ ...path,
3023
+ key
3024
+ ], root, issues, depth + 1);
3025
+ }
3026
+ }
3027
+ }
3028
+ if (Array.isArray(s.allOf)) {
3029
+ for (const sub of s.allOf) check(sub, value, path, root, issues, depth + 1);
3030
+ }
3031
+ if (Array.isArray(s.anyOf) || Array.isArray(s.oneOf)) {
3032
+ const options = Array.isArray(s.anyOf) ? s.anyOf : s.oneOf;
3033
+ const passing = options.filter((sub) => {
3034
+ const nested = [];
3035
+ check(sub, value, path, root, nested, depth + 1);
3036
+ return nested.length === 0;
3037
+ }).length;
3038
+ if (Array.isArray(s.anyOf) ? passing === 0 : passing !== 1) {
3039
+ issues.push({
3040
+ path,
3041
+ message: Array.isArray(s.anyOf) ? "does not match any allowed shape" : "must match exactly one allowed shape"
3042
+ });
3043
+ }
3044
+ }
3045
+ if (s.not !== void 0) {
3046
+ const nested = [];
3047
+ check(s.not, value, path, root, nested, depth + 1);
3048
+ if (nested.length === 0) {
3049
+ issues.push({
3050
+ path,
3051
+ message: "matches a shape that is not allowed"
3052
+ });
3053
+ }
3054
+ }
3055
+ }
3056
+ __name(check, "check");
3057
+
3058
+ // src/genui/catalog.ts
3059
+ var COMPONENT_NAME = /^[A-Za-z][A-Za-z0-9]{0,63}$/;
3060
+ function defineComponent(definition) {
3061
+ if (!COMPONENT_NAME.test(definition.name)) {
3062
+ throw new Error(`genui: component name "${definition.name}" must be letters and digits, starting with a letter (e.g. DataTable)`);
3063
+ }
3064
+ if (definition.version !== void 0 && (!Number.isSafeInteger(definition.version) || definition.version <= 0)) throw new RangeError("genui: component version must be a positive safe integer");
3065
+ if (definition.fallbackText !== void 0 && typeof definition.fallbackText !== "function") throw new TypeError("genui: fallbackText must be a function");
3066
+ return definition;
3067
+ }
3068
+ __name(defineComponent, "defineComponent");
3069
+ function defineCatalog(components, options = {}) {
3070
+ const byName = /* @__PURE__ */ new Map();
3071
+ for (const component of components) {
3072
+ defineComponent(component);
3073
+ if (byName.has(component.name)) {
3074
+ throw new Error(`genui: component "${component.name}" is defined twice in one catalog`);
3075
+ }
3076
+ byName.set(component.name, component);
3077
+ }
3078
+ return buildCatalog(byName, options.jsonSchemaValidator ?? builtinJsonSchemaValidator);
3079
+ }
3080
+ __name(defineCatalog, "defineCatalog");
3081
+ function buildCatalog(byName, validator) {
3082
+ const list = Object.freeze([
3083
+ ...byName.values()
3084
+ ]);
3085
+ return {
3086
+ components: list,
3087
+ validator,
3088
+ get: /* @__PURE__ */ __name((name) => byName.get(name), "get"),
3089
+ has: /* @__PURE__ */ __name((name) => byName.has(name), "has"),
3090
+ modelComponents: /* @__PURE__ */ __name(() => list.filter((component) => component.internal !== true), "modelComponents"),
3091
+ async validate(component, props) {
3092
+ const definition = byName.get(component);
3093
+ if (definition === void 0) {
3094
+ return {
3095
+ ok: false,
3096
+ issues: [
3097
+ {
3098
+ path: [],
3099
+ message: `unknown component "${component}"`
3100
+ }
3101
+ ]
3102
+ };
3103
+ }
3104
+ const result2 = await validateProps(definition.props, props, validator);
3105
+ return result2;
3106
+ },
3107
+ validateSync(component, props) {
3108
+ const definition = byName.get(component);
3109
+ if (definition === void 0) {
3110
+ return {
3111
+ ok: false,
3112
+ issues: [
3113
+ {
3114
+ path: [],
3115
+ message: `unknown component "${component}"`
3116
+ }
3117
+ ]
3118
+ };
3119
+ }
3120
+ return validatePropsSync(definition.props, props, validator);
3121
+ },
3122
+ jsonSchemaFor(component) {
3123
+ const definition = byName.get(component);
3124
+ return definition === void 0 ? void 0 : toJsonSchema(definition.props);
3125
+ },
3126
+ extend(more) {
3127
+ const next = new Map(byName);
3128
+ for (const component of more) {
3129
+ defineComponent(component);
3130
+ next.set(component.name, component);
3131
+ }
3132
+ return buildCatalog(next, validator);
3133
+ }
3134
+ };
3135
+ }
3136
+ __name(buildCatalog, "buildCatalog");
3137
+
3138
+ // src/genui/tree.ts
3139
+ var GENUI_TREE_COMPONENT = "genui:tree";
3140
+ async function validateTree(catalog, tree, limits = {}) {
3141
+ const maxDepth = limits.maxDepth ?? 12;
3142
+ const maxNodes = limits.maxNodes ?? 200;
3143
+ const issues = [];
3144
+ let nodes = 0;
3145
+ const visit = /* @__PURE__ */ __name(async (node, path, depth) => {
3146
+ nodes += 1;
3147
+ if (nodes > maxNodes) {
3148
+ if (nodes === maxNodes + 1) {
3149
+ issues.push({
3150
+ path,
3151
+ message: `the tree has more than ${maxNodes} elements`
3152
+ });
3153
+ }
3154
+ return void 0;
3155
+ }
3156
+ if (depth > maxDepth) {
3157
+ issues.push({
3158
+ path,
3159
+ message: `the tree is nested deeper than ${maxDepth} levels`
3160
+ });
3161
+ return void 0;
3162
+ }
3163
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
3164
+ issues.push({
3165
+ path,
3166
+ message: "must be an element object { type, props, children? }"
3167
+ });
3168
+ return void 0;
3169
+ }
3170
+ const element = node;
3171
+ if (typeof element.type !== "string") {
3172
+ issues.push({
3173
+ path: [
3174
+ ...path,
3175
+ "type"
3176
+ ],
3177
+ message: "must be a component name"
3178
+ });
3179
+ return void 0;
3180
+ }
3181
+ const definition = catalog.get(element.type);
3182
+ if (definition === void 0 || definition.internal === true) {
3183
+ issues.push({
3184
+ path: [
3185
+ ...path,
3186
+ "type"
3187
+ ],
3188
+ message: `unknown component "${element.type}" (allowed: ${catalog.modelComponents().map((component) => component.name).join(", ")})`
3189
+ });
3190
+ return void 0;
3191
+ }
3192
+ const validated = await catalog.validate(element.type, element.props ?? {});
3193
+ let props = {};
3194
+ if (!validated.ok) {
3195
+ for (const issue4 of validated.issues) {
3196
+ issues.push({
3197
+ path: [
3198
+ ...path,
3199
+ "props",
3200
+ ...issue4.path
3201
+ ],
3202
+ message: issue4.message
3203
+ });
3204
+ }
3205
+ } else {
3206
+ props = validated.value;
3207
+ }
3208
+ const rawChildren = parseChildrenIfStringified(element.children);
3209
+ if (rawChildren === void 0 || rawChildren === null) {
3210
+ return {
3211
+ type: element.type,
3212
+ props
3213
+ };
3214
+ }
3215
+ if (!Array.isArray(rawChildren)) {
3216
+ issues.push({
3217
+ path: [
3218
+ ...path,
3219
+ "children"
3220
+ ],
3221
+ message: "must be an array of elements (a literal array, not a string)"
3222
+ });
3223
+ return void 0;
3224
+ }
3225
+ if (rawChildren.length > 0 && definition.children !== true) {
3226
+ issues.push({
3227
+ path: [
3228
+ ...path,
3229
+ "children"
3230
+ ],
3231
+ message: `${element.type} does not take children`
3232
+ });
3233
+ return void 0;
3234
+ }
3235
+ const children = [];
3236
+ for (const [index, child] of rawChildren.entries()) {
3237
+ const next = await visit(child, [
3238
+ ...path,
3239
+ "children",
3240
+ index
3241
+ ], depth + 1);
3242
+ if (next !== void 0) {
3243
+ children.push(next);
3244
+ }
3245
+ }
3246
+ return children.length > 0 ? {
3247
+ type: element.type,
3248
+ props,
3249
+ children
3250
+ } : {
3251
+ type: element.type,
3252
+ props
3253
+ };
3254
+ }, "visit");
3255
+ const root = await visit(tree, [], 1);
3256
+ return issues.length > 0 || root === void 0 ? {
3257
+ ok: false,
3258
+ issues: issues.length > 0 ? issues : [
3259
+ {
3260
+ path: [],
3261
+ message: "is invalid"
3262
+ }
3263
+ ]
3264
+ } : {
3265
+ ok: true,
3266
+ value: root
3267
+ };
3268
+ }
3269
+ __name(validateTree, "validateTree");
3270
+ function parseChildrenIfStringified(value) {
3271
+ if (typeof value !== "string") return value;
3272
+ const trimmed = value.trim();
3273
+ if (!trimmed.startsWith("[")) return value;
3274
+ return leadingJsonArray(trimmed) ?? value;
3275
+ }
3276
+ __name(parseChildrenIfStringified, "parseChildrenIfStringified");
3277
+ function leadingJsonArray(text) {
3278
+ let depth = 0;
3279
+ let inString = false;
3280
+ let escaped = false;
3281
+ for (let index = 0; index < text.length; index += 1) {
3282
+ const char = text[index];
3283
+ if (inString) {
3284
+ if (escaped) escaped = false;
3285
+ else if (char === "\\") escaped = true;
3286
+ else if (char === '"') inString = false;
3287
+ continue;
3288
+ }
3289
+ if (char === '"') inString = true;
3290
+ else if (char === "[" || char === "{") depth += 1;
3291
+ else if (char === "]" || char === "}") {
3292
+ depth -= 1;
3293
+ if (depth === 0) {
3294
+ try {
3295
+ const parsed = JSON.parse(text.slice(0, index + 1));
3296
+ return Array.isArray(parsed) ? parsed : void 0;
3297
+ } catch {
3298
+ return void 0;
3299
+ }
3300
+ }
3301
+ }
3302
+ }
3303
+ return void 0;
3304
+ }
3305
+ __name(leadingJsonArray, "leadingJsonArray");
3306
+
3307
+ // src/genui/text.ts
3308
+ function componentToText(catalog, component, props) {
3309
+ if (component === GENUI_TREE_COMPONENT) {
3310
+ const root = props.root;
3311
+ return root === void 0 ? "" : treeToText(catalog, root);
3312
+ }
3313
+ const fallback = catalog.get(component)?.fallbackText;
3314
+ if (fallback !== void 0) {
3315
+ try {
3316
+ const text = fallback(props);
3317
+ if (typeof text === "string" && text.trim().length > 0) return text;
3318
+ } catch {
3319
+ }
3320
+ }
3321
+ return jsonBlock(props);
3322
+ }
3323
+ __name(componentToText, "componentToText");
3324
+ function treeToText(catalog, root) {
3325
+ const out = [];
3326
+ const visit = /* @__PURE__ */ __name((node, depth) => {
3327
+ if (depth > 32) return;
3328
+ const definition = catalog.get(node.type);
3329
+ if (definition?.fallbackText !== void 0) {
3330
+ try {
3331
+ const text = definition.fallbackText(node.props);
3332
+ out.push(typeof text === "string" && text.trim().length > 0 ? text : jsonBlock(node.props));
3333
+ } catch {
3334
+ out.push(jsonBlock(node.props));
3335
+ }
3336
+ } else if (definition?.children !== true) {
3337
+ out.push(jsonBlock(node.props));
3338
+ }
3339
+ for (const child of node.children ?? []) visit(child, depth + 1);
3340
+ }, "visit");
3341
+ visit(root, 0);
3342
+ return out.filter((line) => line.trim().length > 0).join("\n");
3343
+ }
3344
+ __name(treeToText, "treeToText");
3345
+ function jsonBlock(props) {
3346
+ const title = typeof props.title === "string" && props.title.length > 0 ? `*${props.title}*
3347
+ ` : "";
3348
+ return `${title}\`\`\`
3349
+ ${JSON.stringify(props, null, 2)}
3350
+ \`\`\``;
3351
+ }
3352
+ __name(jsonBlock, "jsonBlock");
3353
+
3354
+ // src/genui/capabilities.ts
3355
+ function validateUiCapabilities(value) {
3356
+ if (!record(value) || Object.keys(value).some((key) => key !== "components") || !Array.isArray(value.components)) throw new TypeError("Invalid UI capabilities");
3357
+ const seen = /* @__PURE__ */ new Set();
3358
+ const components = value.components.map((component) => {
3359
+ 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");
3360
+ seen.add(component.name);
3361
+ return {
3362
+ name: component.name,
3363
+ version: Number(component.version)
3364
+ };
3365
+ });
3366
+ return {
3367
+ components
3368
+ };
3369
+ }
3370
+ __name(validateUiCapabilities, "validateUiCapabilities");
3371
+ function record(value) {
3372
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3373
+ }
3374
+ __name(record, "record");
3375
+ function negotiateCatalog(catalog, capabilities) {
3376
+ if (capabilities === void 0) return catalog;
3377
+ const supported = validateUiCapabilities(capabilities).components;
3378
+ return defineCatalog(catalog.components.filter((component) => supported.some((entry) => entry.name === component.name && entry.version === (component.version ?? 1))), {
3379
+ jsonSchemaValidator: catalog.validator
3380
+ });
3381
+ }
3382
+ __name(negotiateCatalog, "negotiateCatalog");
3383
+ async function prepareUiEmission(catalog, capabilities, component, props, version) {
3384
+ const drawable = negotiateCatalog(catalog, capabilities);
3385
+ if (component === GENUI_TREE_COMPONENT) {
3386
+ if (version !== void 0 && version !== 1) throw new Error("Invalid UI tree version");
3387
+ const validated2 = await validateTree(catalog, props.root);
3388
+ if (!validated2.ok) throw new Error(`Invalid UI tree: ${formatIssues(validated2.issues)}`);
3389
+ const supported = /* @__PURE__ */ __name((node) => drawable.has(node.type) && (node.children ?? []).every(supported), "supported");
3390
+ const versions = /* @__PURE__ */ new Map();
3391
+ const collectVersions = /* @__PURE__ */ __name((node) => {
3392
+ versions.set(node.type, catalog.get(node.type)?.version ?? 1);
3393
+ for (const child of node.children ?? []) collectVersions(child);
3394
+ }, "collectVersions");
3395
+ collectVersions(validated2.value);
3396
+ return supported(validated2.value) ? {
3397
+ kind: "ui",
3398
+ component,
3399
+ props: {
3400
+ root: validated2.value
3401
+ },
3402
+ version: 1,
3403
+ fallbackText: treeToText(catalog, validated2.value),
3404
+ componentVersions: Object.fromEntries(versions)
3405
+ } : {
3406
+ kind: "text",
3407
+ text: treeToText(catalog, validated2.value)
3408
+ };
3409
+ }
3410
+ const definition = catalog.get(component);
3411
+ if (definition === void 0) throw new Error(`Unknown UI component "${component}"`);
3412
+ const currentVersion = definition.version ?? 1;
3413
+ if (version !== void 0 && version !== currentVersion) throw new Error(`Invalid UI component version for "${component}"`);
3414
+ const validated = await catalog.validate(component, props);
3415
+ if (!validated.ok) throw new Error(`Invalid UI props: ${formatIssues(validated.issues)}`);
3416
+ return drawable.has(component) ? {
3417
+ kind: "ui",
3418
+ component,
3419
+ props: validated.value,
3420
+ version: currentVersion,
3421
+ fallbackText: componentToText(catalog, component, validated.value)
3422
+ } : {
3423
+ kind: "text",
3424
+ text: componentToText(catalog, component, validated.value)
3425
+ };
3426
+ }
3427
+ __name(prepareUiEmission, "prepareUiEmission");
3428
+
3429
+ // src/action-proposal-transitions.ts
3430
+ function checkTime(value) {
3431
+ if (!Number.isSafeInteger(value)) throw new RangeError("Action proposal timestamps must be safe integer milliseconds");
3432
+ }
3433
+ __name(checkTime, "checkTime");
3434
+ function checkString(value) {
3435
+ if (typeof value !== "string" || value.length === 0) throw new TypeError("Action proposal identifiers must be nonempty strings");
3436
+ }
3437
+ __name(checkString, "checkString");
3438
+ function validateActionProposalCreation(input) {
3439
+ const allowed = [
3440
+ "id",
3441
+ "tenantRef",
3442
+ "actorRef",
3443
+ "threadId",
3444
+ "originRunId",
3445
+ "originMessageId",
3446
+ "originToolCallId",
3447
+ "toolName",
3448
+ "input",
3449
+ "preparationInput",
3450
+ "executionContext",
3451
+ "confirmation",
3452
+ "approver",
3453
+ "expiresAt",
3454
+ "idempotencyKey",
3455
+ "replacementKey"
3456
+ ];
3457
+ if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("Unknown action proposal creation field");
3458
+ for (const key of [
3459
+ "id",
3460
+ "actorRef",
3461
+ "threadId",
3462
+ "originRunId",
3463
+ "originMessageId",
3464
+ "originToolCallId",
3465
+ "toolName",
3466
+ "approver",
3467
+ "idempotencyKey"
3468
+ ]) checkString(input[key]);
3469
+ if (input.tenantRef !== null && typeof input.tenantRef !== "string") throw new TypeError("tenantRef must be an explicit string or null");
3470
+ for (const value of [
3471
+ input.id,
3472
+ input.actorRef,
3473
+ input.threadId,
3474
+ input.tenantRef
3475
+ ]) {
3476
+ if (value !== null && value.length > 255) throw new RangeError("Action proposal id and scope must fit 255 UTF-16 code units");
3477
+ }
3478
+ if (input.replacementKey !== void 0) {
3479
+ checkString(input.replacementKey);
3480
+ if (input.replacementKey.length > 255) throw new RangeError("replacementKey must fit 255 UTF-16 code units");
3481
+ }
3482
+ if (Object.hasOwn(input, "executionContext")) {
3483
+ const context = input.executionContext;
3484
+ if (context === null || typeof context !== "object" || Array.isArray(context)) throw new TypeError("executionContext must be a JSON object");
3485
+ const allowedContext = [
3486
+ "agentName",
3487
+ "persona",
3488
+ "requestId",
3489
+ "pageContext",
3490
+ "uiCapabilities"
3491
+ ];
3492
+ if (Object.keys(context).some((key) => !allowedContext.includes(key))) throw new TypeError("Unknown execution context field");
3493
+ checkString(context.requestId);
3494
+ if (Object.hasOwn(context, "uiCapabilities")) validateUiCapabilities(context.uiCapabilities);
3495
+ if (Object.hasOwn(context, "agentName")) checkString(context.agentName);
3496
+ if (Object.hasOwn(context, "persona")) checkString(context.persona);
3497
+ if (Object.hasOwn(context, "pageContext")) {
3498
+ const page = context.pageContext;
3499
+ if (page === null || typeof page !== "object" || Array.isArray(page)) throw new TypeError("pageContext must be a JSON object");
3500
+ if (Object.hasOwn(page, "kind") && typeof page.kind !== "string") throw new TypeError("pageContext.kind must be a string");
3501
+ }
3502
+ }
3503
+ if (input.expiresAt !== null) checkTime(input.expiresAt);
3504
+ checkString(input.confirmation?.title);
3505
+ checkString(input.confirmation?.verb);
3506
+ canonicalActionProposalJson(input);
3507
+ }
3508
+ __name(validateActionProposalCreation, "validateActionProposalCreation");
3509
+ function validateActionProposalListQuery(query = {}) {
3510
+ const limit = query.limit === void 0 ? 100 : query.limit;
3511
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) throw new RangeError("limit must be an integer from 1 to 1000");
3512
+ if (query.decision !== void 0 && ![
3513
+ "pending",
3514
+ "approved",
3515
+ "rejected",
3516
+ "expired",
3517
+ "superseded"
3518
+ ].includes(query.decision)) throw new TypeError("Unsupported action proposal list decision");
3519
+ if (query.after !== void 0) {
3520
+ const after = query.after;
3521
+ 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");
3522
+ checkTime(after.createdAt);
3523
+ checkString(after.id);
3524
+ if (after.id.length > 255) throw new TypeError("Action proposal cursor id is too long");
3525
+ }
3526
+ return limit;
3527
+ }
3528
+ __name(validateActionProposalListQuery, "validateActionProposalListQuery");
3529
+ function canonicalActionProposalJson(value) {
3530
+ const ancestors = /* @__PURE__ */ new Set();
3531
+ const sort = /* @__PURE__ */ __name((item) => {
3532
+ if (item === null || typeof item === "string" || typeof item === "boolean") return item;
3533
+ if (typeof item === "number" && Number.isFinite(item)) return item;
3534
+ if (typeof item !== "object" || item === null || ancestors.has(item)) throw new TypeError("Action proposals require finite, acyclic JSON values");
3535
+ const prototype = Object.getPrototypeOf(item);
3536
+ if (!Array.isArray(item) && prototype !== Object.prototype && prototype !== null) throw new TypeError("Action proposals require plain JSON objects");
3537
+ ancestors.add(item);
3538
+ const sorted = Array.isArray(item) ? Array.from(item, sort) : Object.fromEntries(Object.keys(item).sort().map((key) => [
3539
+ key,
3540
+ sort(item[key])
3541
+ ]));
3542
+ ancestors.delete(item);
3543
+ return sorted;
3544
+ }, "sort");
3545
+ return JSON.stringify(sort(value));
3546
+ }
3547
+ __name(canonicalActionProposalJson, "canonicalActionProposalJson");
3548
+ function snapshotActionProposal(value) {
3549
+ return JSON.parse(canonicalActionProposalJson(value));
3550
+ }
3551
+ __name(snapshotActionProposal, "snapshotActionProposal");
3552
+ function actionProposalScopeMatches(row, scope) {
3553
+ return row.tenantRef === scope.tenantRef && row.actorRef === scope.actorRef && row.threadId === scope.threadId;
3554
+ }
3555
+ __name(actionProposalScopeMatches, "actionProposalScopeMatches");
3556
+ function initialActionProposal(input, now) {
3557
+ validateActionProposalCreation(input);
3558
+ checkTime(now);
3559
+ return snapshotActionProposal({
3560
+ ...input,
3561
+ decision: "pending",
3562
+ decisionAudit: null,
3563
+ execution: null,
3564
+ createdAt: now,
3565
+ updatedAt: now
3566
+ });
3567
+ }
3568
+ __name(initialActionProposal, "initialActionProposal");
3569
+ function actionProposalCreationMatches(row, input) {
3570
+ validateActionProposalCreation(input);
3571
+ const { decision: _decision, decisionAudit: _audit, execution: _execution, createdAt: _created, updatedAt: _updated, supersededBy: _supersededBy, outcome: _outcome, outcomeDelivery: _delivery, ...original } = row;
3572
+ return canonicalActionProposalJson(original) === canonicalActionProposalJson(input);
3573
+ }
3574
+ __name(actionProposalCreationMatches, "actionProposalCreationMatches");
3575
+ function result(status, proposal) {
3576
+ return {
3577
+ status,
3578
+ proposal: snapshotActionProposal(proposal)
3579
+ };
3580
+ }
3581
+ __name(result, "result");
3582
+ function transitionActionProposalDecision(row, command, now) {
3583
+ checkTime(now);
3584
+ if (![
3585
+ "approved",
3586
+ "rejected",
3587
+ "expired"
3588
+ ].includes(command.decision)) throw new TypeError("Unsupported action proposal decision");
3589
+ checkString(command.actorRef);
3590
+ checkString(command.via);
3591
+ if (command.reason !== void 0 && typeof command.reason !== "string") throw new TypeError("reason must be a string");
3592
+ if (command.remember !== void 0 && typeof command.remember !== "boolean") throw new TypeError("remember must be a boolean");
3593
+ if (row.decision !== "pending") {
3594
+ return result(row.decision === command.decision ? "unchanged" : row.decision === "expired" ? "expired" : "conflict", row);
3595
+ }
3596
+ const next = snapshotActionProposal(row);
3597
+ if (row.expiresAt !== null && now >= row.expiresAt) {
3598
+ next.decision = "expired";
3599
+ next.decisionAudit = {
3600
+ actorRef: "system",
3601
+ via: "expiry",
3602
+ at: now
3603
+ };
3604
+ next.updatedAt = now;
3605
+ return result(command.decision === "expired" ? "applied" : "expired", attachActionProposalOutcome(next, now));
3606
+ }
3607
+ if (command.decision === "expired") return result("conflict", row);
3608
+ const { decision, ...audit } = command;
3609
+ next.decision = decision;
3610
+ next.decisionAudit = {
3611
+ actorRef: audit.actorRef,
3612
+ via: audit.via,
3613
+ at: now,
3614
+ ...audit.reason === void 0 ? {} : {
3615
+ reason: audit.reason
3616
+ },
3617
+ ...audit.remember === void 0 ? {} : {
3618
+ remember: audit.remember
3619
+ }
3620
+ };
3621
+ next.updatedAt = now;
3622
+ if (decision === "approved") next.execution = {
3623
+ status: "queued",
3624
+ generation: 0,
3625
+ lease: null
3626
+ };
3627
+ return result("applied", decision === "approved" ? next : attachActionProposalOutcome(next, now));
3628
+ }
3629
+ __name(transitionActionProposalDecision, "transitionActionProposalDecision");
3630
+ function checkLeaseMs(leaseMs, now) {
3631
+ checkTime(now);
3632
+ if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0 || !Number.isSafeInteger(now + leaseMs)) throw new RangeError("leaseMs must be finite and positive");
3633
+ }
3634
+ __name(checkLeaseMs, "checkLeaseMs");
3635
+ function transitionActionProposalClaim(row, command, now, token) {
3636
+ checkLeaseMs(command.leaseMs, now);
3637
+ checkString(command.workerId);
3638
+ checkString(token);
3639
+ const execution = row.execution;
3640
+ if (row.decision !== "approved" || !execution || execution.status !== "queued" && !(execution.status === "executing" && execution.lease && now >= execution.lease.expiresAt)) return result("conflict", row);
3641
+ const next = snapshotActionProposal(row);
3642
+ const generation = execution.generation + 1;
3643
+ next.execution = {
3644
+ status: "executing",
3645
+ generation,
3646
+ lease: {
3647
+ token,
3648
+ generation,
3649
+ workerId: command.workerId,
3650
+ expiresAt: now + command.leaseMs
3651
+ }
3652
+ };
3653
+ next.updatedAt = now;
3654
+ return result("applied", next);
3655
+ }
3656
+ __name(transitionActionProposalClaim, "transitionActionProposalClaim");
3657
+ function leaseStatus(row, command, now) {
3658
+ checkTime(now);
3659
+ checkString(command.token);
3660
+ if (!Number.isSafeInteger(command.generation) || command.generation < 1) throw new RangeError("Invalid lease generation");
3661
+ const work = row.execution;
3662
+ 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";
3663
+ return now >= work.lease.expiresAt ? "expired" : "valid";
3664
+ }
3665
+ __name(leaseStatus, "leaseStatus");
3666
+ function transitionActionProposalLease(row, command, now) {
3667
+ checkLeaseMs(command.leaseMs, now);
3668
+ const status = leaseStatus(row, command, now);
3669
+ if (status !== "valid") return result(status, row);
3670
+ const next = snapshotActionProposal(row);
3671
+ if (next.execution?.lease) next.execution.lease.expiresAt = Math.max(next.execution.lease.expiresAt, now + command.leaseMs);
3672
+ next.updatedAt = now;
3673
+ return result("applied", next);
3674
+ }
3675
+ __name(transitionActionProposalLease, "transitionActionProposalLease");
3676
+ function transitionActionProposalSettlement(row, command, now) {
3677
+ checkTime(now);
3678
+ if (command.status !== "succeeded" && command.status !== "failed") throw new TypeError("Unsupported settlement status");
3679
+ 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");
3680
+ const status = leaseStatus(row, command, now);
3681
+ if (status !== "valid") return result(status, row);
3682
+ const next = snapshotActionProposal(row);
3683
+ next.execution = command.status === "succeeded" ? {
3684
+ status: "succeeded",
3685
+ generation: command.generation,
3686
+ lease: null,
3687
+ ...command.result === void 0 ? {} : {
3688
+ result: command.result
3689
+ }
3690
+ } : {
3691
+ status: "failed",
3692
+ generation: command.generation,
3693
+ lease: null,
3694
+ error: command.error
3695
+ };
3696
+ if (command.ui !== void 0) canonicalActionProposalJson(command.ui);
3697
+ if (command.text !== void 0 && typeof command.text !== "string") throw new TypeError("Outcome text must be a string");
3698
+ next.updatedAt = now;
3699
+ return result("applied", attachActionProposalOutcome(next, now, command.ui, command.text));
3700
+ }
3701
+ __name(transitionActionProposalSettlement, "transitionActionProposalSettlement");
3702
+ function transitionActionProposalSupersession(row, replacement, command, now) {
3703
+ checkTime(now);
3704
+ checkString(command.replacementProposalId);
3705
+ checkString(command.actorRef);
3706
+ checkString(command.via);
3707
+ 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);
3708
+ if (row.decision === "superseded") return result(row.supersededBy === replacement.id ? "unchanged" : "conflict", row);
3709
+ if (row.decision !== "pending") return result("conflict", row);
3710
+ if (row.expiresAt !== null && now >= row.expiresAt) return {
3711
+ ...transitionActionProposalDecision(row, {
3712
+ decision: "expired",
3713
+ actorRef: "system",
3714
+ via: "expiry"
3715
+ }, now),
3716
+ status: "expired"
3717
+ };
3718
+ const next = snapshotActionProposal({
3719
+ ...row,
3720
+ decision: "superseded",
3721
+ supersededBy: replacement.id,
3722
+ decisionAudit: {
3723
+ actorRef: command.actorRef,
3724
+ via: command.via,
3725
+ replacementProposalId: replacement.id,
3726
+ at: now
3727
+ },
3728
+ updatedAt: now
3729
+ });
3730
+ return result("applied", attachActionProposalOutcome(next, now));
3731
+ }
3732
+ __name(transitionActionProposalSupersession, "transitionActionProposalSupersession");
3733
+
2591
3734
  // src/tool-registry.ts
2592
3735
  var ToolForbiddenError = class extends Error {
2593
3736
  static {
@@ -2630,6 +3773,35 @@ var ToolInputInvalidError = class extends Error {
2630
3773
  this.name = "ToolInputInvalidError";
2631
3774
  }
2632
3775
  };
3776
+ var ToolPreflightDeniedError = class extends Error {
3777
+ static {
3778
+ __name(this, "ToolPreflightDeniedError");
3779
+ }
3780
+ toolName;
3781
+ reason;
3782
+ constructor(toolName, reason) {
3783
+ super(reason), this.toolName = toolName, this.reason = reason;
3784
+ this.name = "ToolPreflightDeniedError";
3785
+ }
3786
+ };
3787
+ var ToolInputDriftError = class extends Error {
3788
+ static {
3789
+ __name(this, "ToolInputDriftError");
3790
+ }
3791
+ toolName;
3792
+ constructor(toolName) {
3793
+ super(`Tool "${toolName}" input differs from its trusted snapshot`), this.toolName = toolName;
3794
+ this.name = "ToolInputDriftError";
3795
+ }
3796
+ };
3797
+ function assertTrustedInput(name, value, approved) {
3798
+ try {
3799
+ if (canonicalActionProposalJson(value) === canonicalActionProposalJson(approved)) return;
3800
+ } catch {
3801
+ }
3802
+ throw new ToolInputDriftError(name);
3803
+ }
3804
+ __name(assertTrustedInput, "assertTrustedInput");
2633
3805
  var ToolRegistry = class {
2634
3806
  static {
2635
3807
  __name(this, "ToolRegistry");
@@ -2677,11 +3849,12 @@ var ToolRegistry = class {
2677
3849
  */
2678
3850
  async definitionsFor(actor, policy, allowedTools, scope = {}) {
2679
3851
  const visible = await this.visibleEntries(actor, policy, allowedTools);
2680
- return Promise.all(visible.map(async ({ spec, handler }) => {
3852
+ const definitions = await Promise.all(visible.map(async ({ spec, handler }) => {
2681
3853
  const override = handler.describe === void 0 ? void 0 : await handler.describe({
2682
3854
  actor,
2683
3855
  ...scope
2684
3856
  });
3857
+ if (override?.available === false) return null;
2685
3858
  return {
2686
3859
  name: spec.name,
2687
3860
  kind: spec.kind,
@@ -2689,6 +3862,7 @@ var ToolRegistry = class {
2689
3862
  inputSchema: override?.inputSchema ?? spec.inputSchema
2690
3863
  };
2691
3864
  }));
3865
+ return definitions.filter((definition) => definition !== null);
2692
3866
  }
2693
3867
  /**
2694
3868
  * The specs {@link definitionsFor} offers the model, whole — the same four gates (allow-list,
@@ -2714,6 +3888,40 @@ var ToolRegistry = class {
2714
3888
  * moved, neither of which went through `definitionsFor` again) and re-parses the input via Zod.
2715
3889
  */
2716
3890
  async invoke(name, input, ctx, policy, options = {}) {
3891
+ const { entry, value, ctx: withEmit } = await this.validated(name, input, ctx, policy, options);
3892
+ const trusted = Object.hasOwn(options, "approvedInput");
3893
+ if (trusted) assertTrustedInput(name, value, options.approvedInput);
3894
+ const executionInput = trusted ? snapshotActionProposal(value) : value;
3895
+ const hookInput = trusted ? snapshotActionProposal(executionInput) : value;
3896
+ const result2 = entry.spec.kind === "action" ? await entry.handler.preflight?.(hookInput, withEmit, {
3897
+ phase: "execute"
3898
+ }) : void 0;
3899
+ if (trusted) assertTrustedInput(name, hookInput, executionInput);
3900
+ if (result2?.status === "denied") throw new ToolPreflightDeniedError(name, result2.reason);
3901
+ if (result2?.status === "completed") return result2.output;
3902
+ return entry.handler.execute(executionInput, withEmit);
3903
+ }
3904
+ /** Authorize and validate without executing. The caller journals this result before approval. */
3905
+ async prepare(name, input, ctx, policy, options = {}) {
3906
+ return (await this.prepareValidated(name, input, ctx, policy, options)).preflight;
3907
+ }
3908
+ /** Expose the single schema parse; snapshotInput opts proposal callers into immutable JSON. */
3909
+ async prepareValidated(name, input, ctx, policy, options = {}) {
3910
+ const { entry, value, ctx: withEmit } = await this.validated(name, input, ctx, policy, options);
3911
+ const normalized = options.snapshotInput === true ? snapshotActionProposal(value) : value;
3912
+ const hookInput = options.snapshotInput === true ? snapshotActionProposal(normalized) : value;
3913
+ const preflight = (entry.spec.kind === "action" ? await entry.handler.preflight?.(hookInput, withEmit, {
3914
+ phase: "prepare"
3915
+ }) : void 0) ?? {
3916
+ status: "ready"
3917
+ };
3918
+ if (options.snapshotInput === true) assertTrustedInput(name, hookInput, normalized);
3919
+ return {
3920
+ input: normalized,
3921
+ preflight
3922
+ };
3923
+ }
3924
+ async validated(name, input, ctx, policy, options) {
2717
3925
  const entry = this.entries.get(name);
2718
3926
  if (entry === void 0) {
2719
3927
  throw new ToolNotFoundError(name);
@@ -2738,7 +3946,11 @@ var ToolRegistry = class {
2738
3946
  ...ctx,
2739
3947
  emitUi: createNoopEmitUi(ctx.requestId)
2740
3948
  };
2741
- return entry.handler.execute(validation.value, withEmit);
3949
+ return {
3950
+ entry,
3951
+ value: validation.value,
3952
+ ctx: withEmit
3953
+ };
2742
3954
  }
2743
3955
  };
2744
3956
  var DefaultRolesPolicy = class {
@@ -2785,6 +3997,69 @@ __name(closeEmptyRoles, "closeEmptyRoles");
2785
3997
  import { createHash } from "crypto";
2786
3998
  import { trace } from "@dudousxd/nestjs-diagnostics";
2787
3999
 
4000
+ // src/action-proposal-approval.ts
4001
+ async function resolveActionProposalApproval(input) {
4002
+ const policy = input.policy ?? new DefaultApprovalPolicy();
4003
+ const requirement = await policy.requirementFor(input.tool, input.actor, input.thread);
4004
+ if (!requirement.required) return {
4005
+ mode: "auto"
4006
+ };
4007
+ const remembered = await input.store.rememberedApprovals?.(input.thread.threadId) ?? [];
4008
+ if (remembered.includes(input.tool.name)) return {
4009
+ mode: "remembered",
4010
+ approver: requirement.approver
4011
+ };
4012
+ const roundedTtl = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) ? Math.floor(requirement.ttlMs) : 0;
4013
+ if (roundedTtl <= 0) return {
4014
+ mode: "ask",
4015
+ approver: requirement.approver
4016
+ };
4017
+ const now = (input.clock ?? Date.now)();
4018
+ if (!Number.isSafeInteger(now) || !Number.isSafeInteger(roundedTtl) || !Number.isSafeInteger(now + roundedTtl)) throw new RangeError("Invalid action approval deadline");
4019
+ return {
4020
+ mode: "ask",
4021
+ approver: requirement.approver,
4022
+ ttlMs: roundedTtl,
4023
+ expiresAt: new Date(now + roundedTtl).toISOString()
4024
+ };
4025
+ }
4026
+ __name(resolveActionProposalApproval, "resolveActionProposalApproval");
4027
+
4028
+ // src/action-proposal-preparation.ts
4029
+ async function prepareActionProposal(registry, name, input, ctx, policy, options = {}) {
4030
+ const preparationInput = snapshotActionProposal(input);
4031
+ const prepared = await registry.prepareValidated(name, snapshotActionProposal(preparationInput), ctx, policy, {
4032
+ ...options,
4033
+ snapshotInput: true
4034
+ });
4035
+ let replacementKey;
4036
+ const replacement = registry.spec(name)?.replacementKey;
4037
+ if (prepared.preflight.status === "ready" && replacement !== void 0) {
4038
+ replacementKey = typeof replacement === "string" ? replacement : await replacement(snapshotActionProposal(prepared.input), ctx);
4039
+ 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");
4040
+ }
4041
+ return {
4042
+ ...replacementKey !== void 0 ? {
4043
+ replacementKey
4044
+ } : {},
4045
+ preparationInput,
4046
+ input: prepared.input,
4047
+ // Completed results keep their normal handler contract; they never create a proposal.
4048
+ preflight: prepared.preflight.status === "ready" ? snapshotActionProposal(prepared.preflight) : prepared.preflight
4049
+ };
4050
+ }
4051
+ __name(prepareActionProposal, "prepareActionProposal");
4052
+
4053
+ // src/action-proposal-receipt.ts
4054
+ function actionProposalReceipt(proposalId) {
4055
+ return {
4056
+ proposalId,
4057
+ status: "pending",
4058
+ executed: false
4059
+ };
4060
+ }
4061
+ __name(actionProposalReceipt, "actionProposalReceipt");
4062
+
2788
4063
  // src/diagnostics.ts
2789
4064
  import { emit } from "@dudousxd/nestjs-diagnostics";
2790
4065
  function publishAgentRunStarted(payload) {
@@ -2860,6 +4135,56 @@ function agentDiagnosticKey(event) {
2860
4135
  }
2861
4136
  __name(agentDiagnosticKey, "agentDiagnosticKey");
2862
4137
 
4138
+ // src/negotiated-tool-ui.ts
4139
+ function createNegotiatedUiCollector(toolCallId, scope, resolveCatalog, write) {
4140
+ const collector = createUiCollector(toolCallId, write);
4141
+ const texts = /* @__PURE__ */ new Map();
4142
+ let next = 0;
4143
+ return {
4144
+ components: collector.components,
4145
+ text: /* @__PURE__ */ __name(() => [
4146
+ ...texts.values()
4147
+ ].join("\n"), "text"),
4148
+ restart: /* @__PURE__ */ __name(() => {
4149
+ next = 0;
4150
+ texts.clear();
4151
+ collector.restart();
4152
+ }, "restart"),
4153
+ emit: /* @__PURE__ */ __name(async (component, props, options = {}) => {
4154
+ if (!resolveCatalog) {
4155
+ if (scope.uiCapabilities !== void 0) throw new Error("UI capabilities require an authorized server catalog");
4156
+ return collector.emit(component, props, options);
4157
+ }
4158
+ const capturedProps = snapshotActionProposal(props);
4159
+ const capturedOptions = {
4160
+ ...options
4161
+ };
4162
+ const catalog = await resolveCatalog(scope);
4163
+ const prepared = await prepareUiEmission(catalog, scope.uiCapabilities, component, capturedProps, capturedOptions.version);
4164
+ const id = capturedOptions.id ?? `${toolCallId}:ui:${next++}`;
4165
+ if (prepared.kind === "text") {
4166
+ texts.set(id, prepared.text);
4167
+ await write?.({
4168
+ kind: "text",
4169
+ text: prepared.text
4170
+ });
4171
+ return {
4172
+ id
4173
+ };
4174
+ }
4175
+ return collector.emit(prepared.component, prepared.props, {
4176
+ id,
4177
+ version: prepared.version,
4178
+ fallbackText: prepared.fallbackText,
4179
+ ...prepared.componentVersions !== void 0 ? {
4180
+ componentVersions: prepared.componentVersions
4181
+ } : {}
4182
+ });
4183
+ }, "emit")
4184
+ };
4185
+ }
4186
+ __name(createNegotiatedUiCollector, "createNegotiatedUiCollector");
4187
+
2863
4188
  // src/tool-retry.ts
2864
4189
  function hasTransientShape(error) {
2865
4190
  if (typeof error !== "object" || error === null) {
@@ -2885,6 +4210,7 @@ function hasTransientShape(error) {
2885
4210
  }
2886
4211
  __name(hasTransientShape, "hasTransientShape");
2887
4212
  function isTransientToolError(error) {
4213
+ if (error instanceof ToolPreflightDeniedError) return false;
2888
4214
  if (hasTransientShape(error)) {
2889
4215
  return true;
2890
4216
  }
@@ -2927,7 +4253,7 @@ async function invokeWithTransientRetry(fn, setting, options) {
2927
4253
  try {
2928
4254
  return await fn();
2929
4255
  } catch (error) {
2930
- if (isControlFlowSignal(error) || options?.isControlFlowError?.(error) === true) {
4256
+ if (error instanceof ToolPreflightDeniedError || isControlFlowSignal(error) || options?.isControlFlowError?.(error) === true) {
2931
4257
  throw error;
2932
4258
  }
2933
4259
  const attemptsRemain = attempt < attempts;
@@ -3107,6 +4433,9 @@ function promptContext(input, persona) {
3107
4433
  ...input.pageContext !== void 0 ? {
3108
4434
  pageContext: input.pageContext
3109
4435
  } : {},
4436
+ ...input.uiCapabilities !== void 0 ? {
4437
+ uiCapabilities: input.uiCapabilities
4438
+ } : {},
3110
4439
  ...persona !== void 0 ? {
3111
4440
  persona: {
3112
4441
  id: persona.id,
@@ -3303,7 +4632,8 @@ async function loadSelectedHistory(deps, input, hooks) {
3303
4632
  messages: keep,
3304
4633
  dropped: deps.historyPolicy?.summarize === void 0 ? [] : drop,
3305
4634
  title: thread.title,
3306
- hasAssistantMessage: thread.hasAssistantMessage
4635
+ hasAssistantMessage: thread.hasAssistantMessage,
4636
+ actionApprovalMode: deps.actionApprovalMode ?? "blocking"
3307
4637
  };
3308
4638
  });
3309
4639
  return {
@@ -3467,10 +4797,10 @@ async function structureAnswer(schema, deps, input, hooks, messages, step) {
3467
4797
  modelId: turn.modelId
3468
4798
  } : {}
3469
4799
  };
3470
- }, (result) => ({
3471
- inputTokens: result.usage.inputTokens,
3472
- outputTokens: result.usage.outputTokens,
3473
- textLength: result.text.length
4800
+ }, (result2) => ({
4801
+ inputTokens: result2.usage.inputTokens,
4802
+ outputTokens: result2.usage.outputTokens,
4803
+ textLength: result2.text.length
3474
4804
  })));
3475
4805
  await hooks.step(`persist:usage:structured:${step}:${attempt}`, () => deps.store.recordUsage({
3476
4806
  threadId: input.threadId,
@@ -3500,13 +4830,16 @@ async function structureAnswer(schema, deps, input, hooks, messages, step) {
3500
4830
  throw new StructuredOutputError(issues, text, maxAttempts);
3501
4831
  }
3502
4832
  __name(structureAnswer, "structureAnswer");
3503
- function stampToolKinds(result, deps) {
4833
+ function stampToolKinds(result2, deps) {
3504
4834
  return {
3505
- ...result,
3506
- toolCalls: result.toolCalls.map((call) => call.kind === void 0 ? {
3507
- ...call,
3508
- kind: declaredKind(deps, call.name)
3509
- } : call)
4835
+ ...result2,
4836
+ toolCalls: result2.toolCalls.map((call) => {
4837
+ const { preflight: _untrusted, prepared: _untrustedPrepared, actionApproval: _untrustedApproval, ...trusted } = call;
4838
+ return trusted.kind === void 0 ? {
4839
+ ...trusted,
4840
+ kind: declaredKind(deps, trusted.name)
4841
+ } : trusted;
4842
+ })
3510
4843
  };
3511
4844
  }
3512
4845
  __name(stampToolKinds, "stampToolKinds");
@@ -3569,6 +4902,9 @@ function toolContext(deps, input, hooks) {
3569
4902
  ...input.pageContext !== void 0 ? {
3570
4903
  pageContext: input.pageContext
3571
4904
  } : {},
4905
+ ...input.uiCapabilities !== void 0 ? {
4906
+ uiCapabilities: input.uiCapabilities
4907
+ } : {},
3572
4908
  ...deps.host !== void 0 ? {
3573
4909
  host: deps.host
3574
4910
  } : {},
@@ -3644,16 +4980,16 @@ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant)
3644
4980
  }
3645
4981
  const messageId = asked.messageId;
3646
4982
  const reply = await awaitElicitation(hooks, request, toolContext(deps, input, hooks));
3647
- const result = settleElicitation(request, reply);
4983
+ const result2 = settleElicitation(request, reply);
3648
4984
  await hooks.step("intake:answers", async () => {
3649
4985
  await deps.store.updateToolCall({
3650
4986
  toolCallId: request.id,
3651
4987
  // A skip is not an answer. Both leave the agent holding the same values, but only one of them
3652
4988
  // is evidence the user chose them, and a reader auditing what the agent was told has to be
3653
4989
  // able to tell those apart.
3654
- status: result.skipped ? "rejected" : "executed",
3655
- output: result,
3656
- ...result.skipped ? {
4990
+ status: result2.skipped ? "rejected" : "executed",
4991
+ output: result2,
4992
+ ...result2.skipped ? {
3657
4993
  error: "skipped by the user"
3658
4994
  } : {},
3659
4995
  ...reply.answeredByRef !== void 0 ? {
@@ -3664,13 +5000,13 @@ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant)
3664
5000
  {
3665
5001
  id: request.id,
3666
5002
  name: ASK_TOOL_NAME,
3667
- output: result
5003
+ output: result2
3668
5004
  }
3669
5005
  ]);
3670
5006
  await writer.write(encodeStreamEvent({
3671
5007
  kind: "tool-output",
3672
5008
  id: request.id,
3673
- output: result
5009
+ output: result2
3674
5010
  }));
3675
5011
  });
3676
5012
  return {
@@ -3683,7 +5019,7 @@ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant)
3683
5019
  {
3684
5020
  id: request.id,
3685
5021
  name: ASK_TOOL_NAME,
3686
- output: result
5022
+ output: result2
3687
5023
  }
3688
5024
  ]
3689
5025
  };
@@ -3729,16 +5065,140 @@ async function claimToolCall(turn, call) {
3729
5065
  const spec = deps.registry.spec(call.name);
3730
5066
  const kind = call.kind ?? declaredKind(deps, call.name);
3731
5067
  const awaitsHuman = kind === "action" || kind === "ask";
3732
- const approval = kind === "action" ? await claimApproval(turn, call, spec) : void 0;
3733
- const parks = kind === "ask" || approval?.mode === "ask";
5068
+ const independentApproval = turn.actionApprovalMode === "independent" && kind === "action" ? hooks.dispatchLlm !== void 0 && call.actionApproval !== void 0 ? call.actionApproval : await resolveActionProposalApproval({
5069
+ actor: input.actor,
5070
+ store: deps.store,
5071
+ tool: {
5072
+ name: call.name,
5073
+ kind: "action",
5074
+ ...spec !== void 0 ? {
5075
+ spec
5076
+ } : {}
5077
+ },
5078
+ thread: {
5079
+ threadId: input.threadId,
5080
+ runId: hooks.runId,
5081
+ ...input.agentName !== void 0 ? {
5082
+ agentName: input.agentName
5083
+ } : {}
5084
+ },
5085
+ ...deps.approvalPolicy !== void 0 ? {
5086
+ policy: deps.approvalPolicy
5087
+ } : {}
5088
+ }) : void 0;
5089
+ let preflight;
5090
+ let prepared;
5091
+ if (turn.actionApprovalMode === "independent" && call.prepared !== void 0 && hooks.dispatchLlm !== void 0) prepared = call.prepared;
5092
+ if (kind === "action" && hooks.dispatchLlm !== void 0 && call.preflight !== void 0) {
5093
+ preflight = call.preflight;
5094
+ } else if (kind === "action" && hooks.dispatchLlm !== void 0 && spec === void 0) {
5095
+ preflight = {
5096
+ status: "ready"
5097
+ };
5098
+ } else if (kind === "action") {
5099
+ try {
5100
+ const allowedTools = turn.persona?.allowedTools === void 0 ? deps.toolAllowList : intersectAllowLists(deps.toolAllowList, turn.persona.allowedTools);
5101
+ if (independentApproval?.mode === "ask") {
5102
+ prepared = await prepareActionProposal(deps.registry, call.name, call.input, toolCallContext(toolContext(deps, input, hooks), call.id), deps.rolesPolicy, allowedTools === void 0 ? {} : {
5103
+ allowedTools
5104
+ });
5105
+ preflight = prepared.preflight;
5106
+ } else {
5107
+ preflight = await deps.registry.prepare(call.name, call.input, toolCallContext(toolContext(deps, input, hooks), call.id), deps.rolesPolicy, allowedTools === void 0 ? {} : {
5108
+ allowedTools
5109
+ });
5110
+ }
5111
+ } catch (error) {
5112
+ if (hooks.isControlFlowError?.(error) === true || isControlFlowSignal(error) || isReplayIntegrityError(error)) throw error;
5113
+ preflight = {
5114
+ status: "failed",
5115
+ error: error instanceof Error ? error.message : String(error)
5116
+ };
5117
+ }
5118
+ }
5119
+ let confirmation = preflight?.status === "ready" ? preflight.confirmation : void 0;
5120
+ const approval = kind === "action" && preflight?.status === "ready" ? independentApproval ?? await claimApproval(turn, call, spec) : void 0;
5121
+ let proposalId;
5122
+ if (turn.actionApprovalMode === "independent" && approval?.mode === "ask") {
5123
+ if (!prepared) throw new Error("Independent action lacks trusted prepared input");
5124
+ const proposalStore = deps.store;
5125
+ if (typeof proposalStore.createActionProposal !== "function") throw new Error("Independent action proposal store unavailable");
5126
+ proposalId = `proposal-${createHash("sha256").update(JSON.stringify([
5127
+ hooks.runId,
5128
+ call.id
5129
+ ])).digest("hex")}`;
5130
+ const display = confirmation ?? spec?.presentation?.confirm ?? {
5131
+ title: `Allow ${call.name}?`,
5132
+ verb: "Approve"
5133
+ };
5134
+ const fill = /* @__PURE__ */ __name((value) => value.replace(/\{([^}]+)\}/g, (_match, path) => {
5135
+ let current = prepared?.input;
5136
+ for (const part of path.split(".")) current = current !== null && typeof current === "object" ? current[part] : void 0;
5137
+ return current === void 0 ? "" : String(current);
5138
+ }), "fill");
5139
+ const create = prepared.replacementKey === void 0 ? proposalStore.createActionProposal.bind(proposalStore) : (() => {
5140
+ const store = proposalStore;
5141
+ if (typeof store.createReplacingActionProposal !== "function") throw new Error("Independent replacement requires atomic supersession store");
5142
+ return store.createReplacingActionProposal.bind(store);
5143
+ })();
5144
+ const created = await create({
5145
+ ...prepared.replacementKey !== void 0 ? {
5146
+ replacementKey: prepared.replacementKey
5147
+ } : {},
5148
+ id: proposalId,
5149
+ tenantRef: input.actor.tenantRef ?? null,
5150
+ actorRef: input.actor.id,
5151
+ threadId: input.threadId,
5152
+ originRunId: hooks.runId,
5153
+ originMessageId: messageId,
5154
+ originToolCallId: call.id,
5155
+ toolName: call.name,
5156
+ input: prepared.input,
5157
+ preparationInput: prepared.preparationInput,
5158
+ executionContext: {
5159
+ requestId: hooks.runId,
5160
+ ...input.agentName !== void 0 ? {
5161
+ agentName: input.agentName
5162
+ } : {},
5163
+ ...input.persona !== void 0 ? {
5164
+ persona: input.persona
5165
+ } : {},
5166
+ ...input.pageContext !== void 0 ? {
5167
+ pageContext: input.pageContext
5168
+ } : {},
5169
+ ...input.uiCapabilities !== void 0 ? {
5170
+ uiCapabilities: input.uiCapabilities
5171
+ } : {}
5172
+ },
5173
+ confirmation: confirmation ?? {
5174
+ title: fill(display.title),
5175
+ verb: fill(display.verb),
5176
+ ...display.detail !== void 0 ? {
5177
+ detail: fill(display.detail)
5178
+ } : {}
5179
+ },
5180
+ approver: approval.approver,
5181
+ expiresAt: approval.expiresAt === void 0 ? null : Date.parse(approval.expiresAt),
5182
+ idempotencyKey: toolCallContext(toolContext(deps, input, hooks), call.id).idempotencyKey
5183
+ });
5184
+ if (created.status === "conflict") throw new Error("Action proposal replay payload conflict");
5185
+ confirmation = created.proposal?.confirmation ?? confirmation;
5186
+ }
5187
+ const parks = kind === "ask" || approval?.mode === "ask" && proposalId === void 0;
3734
5188
  await deps.store.recordToolCall({
3735
5189
  toolCallId: call.id,
3736
5190
  messageId,
3737
5191
  toolName: call.name,
3738
5192
  toolType: awaitsHuman ? "action" : "read",
3739
5193
  input: call.input,
3740
- status: parks ? "pending_approval" : "auto_executed",
5194
+ status: proposalId !== void 0 ? "proposed" : parks ? "pending_approval" : "auto_executed",
5195
+ ...proposalId !== void 0 ? {
5196
+ proposalId
5197
+ } : {},
3741
5198
  runId: hooks.runId,
5199
+ ...confirmation !== void 0 ? {
5200
+ confirmation
5201
+ } : {},
3742
5202
  ...approval !== void 0 && approval.mode !== "auto" ? {
3743
5203
  approver: approval.approver
3744
5204
  } : {},
@@ -3750,7 +5210,16 @@ async function claimToolCall(turn, call) {
3750
5210
  await turn.writer.write(encodeStreamEvent({
3751
5211
  kind: "approval-requested",
3752
5212
  id: call.id,
5213
+ ...proposalId !== void 0 ? {
5214
+ target: {
5215
+ kind: "proposal",
5216
+ proposalId
5217
+ }
5218
+ } : {},
3753
5219
  approver: approval.approver,
5220
+ ...confirmation !== void 0 ? {
5221
+ confirmation
5222
+ } : {},
3754
5223
  ...approval.expiresAt !== void 0 ? {
3755
5224
  expiresAt: approval.expiresAt
3756
5225
  } : {}
@@ -3773,6 +5242,12 @@ async function claimToolCall(turn, call) {
3773
5242
  } : {},
3774
5243
  ...approval !== void 0 ? {
3775
5244
  approval
5245
+ } : {},
5246
+ ...preflight !== void 0 ? {
5247
+ preflight
5248
+ } : {},
5249
+ ...proposalId !== void 0 ? {
5250
+ proposalId
3776
5251
  } : {}
3777
5252
  };
3778
5253
  });
@@ -3798,6 +5273,12 @@ async function claimToolCall(turn, call) {
3798
5273
  ...toolType === "action" && persisted?.approval !== void 0 ? {
3799
5274
  approval: persisted.approval
3800
5275
  } : {},
5276
+ ...persisted?.preflight !== void 0 ? {
5277
+ preflight: persisted.preflight
5278
+ } : {},
5279
+ ...persisted?.proposalId !== void 0 ? {
5280
+ proposalId: persisted.proposalId
5281
+ } : {},
3801
5282
  ctx: toolContext(deps, input, hooks)
3802
5283
  };
3803
5284
  }
@@ -3945,6 +5426,9 @@ function skillContext(input) {
3945
5426
  } : {},
3946
5427
  ...input.pageContext !== void 0 ? {
3947
5428
  pageContext: input.pageContext
5429
+ } : {},
5430
+ ...input.uiCapabilities !== void 0 ? {
5431
+ uiCapabilities: input.uiCapabilities
3948
5432
  } : {}
3949
5433
  };
3950
5434
  }
@@ -3961,7 +5445,7 @@ async function invokeClaimedTool(turn, claimed) {
3961
5445
  if (claimed.toolType === "memory") {
3962
5446
  return await rememberIntoTurn(turn, claimed, startedAt);
3963
5447
  }
3964
- const invokeAllowList = turn.persona?.allowedTools === void 0 ? void 0 : intersectAllowLists(deps.toolAllowList, turn.persona.allowedTools);
5448
+ const invokeAllowList = turn.persona?.allowedTools === void 0 ? deps.toolAllowList : intersectAllowLists(deps.toolAllowList, turn.persona.allowedTools);
3965
5449
  let raw;
3966
5450
  if (hooks.dispatchTool) {
3967
5451
  const stepCtx = {
@@ -3977,6 +5461,9 @@ async function invokeClaimedTool(turn, claimed) {
3977
5461
  } : {},
3978
5462
  ...input.pageContext !== void 0 ? {
3979
5463
  pageContext: input.pageContext
5464
+ } : {},
5465
+ ...input.uiCapabilities !== void 0 ? {
5466
+ uiCapabilities: input.uiCapabilities
3980
5467
  } : {}
3981
5468
  };
3982
5469
  const envelope = {
@@ -3998,37 +5485,49 @@ async function invokeClaimedTool(turn, claimed) {
3998
5485
  raw = await hooks.dispatchTool(resolvedCall, envelope);
3999
5486
  } else {
4000
5487
  const invocation = hooks.step(`tool:${call.id}`, async () => {
4001
- const ui2 = createUiCollector(call.id, (event) => turn.writer.write(encodeStreamEvent(event)));
4002
- const output2 = await traceToolExecution(hooks.runId, {
4003
- toolCallId: call.id,
4004
- toolName: call.name,
4005
- toolType
4006
- }, () => invokeWithTransientRetry(() => {
4007
- ui2.restart();
4008
- return deps.registry.invoke(call.name, call.input, {
4009
- ...toolCallContext(ctx, call.id),
4010
- emitUi: ui2.emit
4011
- }, deps.rolesPolicy, invokeAllowList !== void 0 ? {
4012
- allowedTools: invokeAllowList
4013
- } : {});
4014
- }, deps.toolTransientRetry ?? {}, {
4015
- ...hooks.isControlFlowError !== void 0 ? {
4016
- isControlFlowError: hooks.isControlFlowError
4017
- } : {},
4018
- onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
4019
- publishAgentToolRetry({
4020
- toolName: call.name,
4021
- toolCallId: call.id,
4022
- attempt,
4023
- message: retryError instanceof Error ? retryError.message : String(retryError)
4024
- });
4025
- }, "onRetry")
4026
- }));
4027
- return wrapToolStepOutput(output2, ui2.components());
5488
+ const ui2 = createNegotiatedUiCollector(call.id, ctx, deps.resolveUiCatalog, (event) => turn.writer.write(encodeStreamEvent(event)));
5489
+ let output2;
5490
+ try {
5491
+ output2 = await traceToolExecution(hooks.runId, {
5492
+ toolCallId: call.id,
5493
+ toolName: call.name,
5494
+ toolType
5495
+ }, () => invokeWithTransientRetry(() => {
5496
+ ui2.restart();
5497
+ return deps.registry.invoke(call.name, call.input, {
5498
+ ...toolCallContext(ctx, call.id),
5499
+ emitUi: ui2.emit
5500
+ }, deps.rolesPolicy, invokeAllowList !== void 0 ? {
5501
+ allowedTools: invokeAllowList
5502
+ } : {});
5503
+ }, deps.toolTransientRetry ?? {}, {
5504
+ ...hooks.isControlFlowError !== void 0 ? {
5505
+ isControlFlowError: hooks.isControlFlowError
5506
+ } : {},
5507
+ onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
5508
+ publishAgentToolRetry({
5509
+ toolName: call.name,
5510
+ toolCallId: call.id,
5511
+ attempt,
5512
+ message: retryError instanceof Error ? retryError.message : String(retryError)
5513
+ });
5514
+ }, "onRetry")
5515
+ }));
5516
+ } catch (error) {
5517
+ if (error instanceof Error && error.name === "ToolPreflightDeniedError") return wrapToolPreflightDenied(error.message);
5518
+ throw error;
5519
+ }
5520
+ return wrapToolStepOutput(output2, ui2.components(), ui2.text());
4028
5521
  });
4029
5522
  raw = deps.toolTimeoutMs !== void 0 ? await withToolTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
4030
5523
  }
4031
- const { output, ui } = unwrapToolStepOutput(raw);
5524
+ const { output, ui, preflightDenied, text } = unwrapToolStepOutput(raw);
5525
+ if (text) turn.toolText.set(call.id, text);
5526
+ if (preflightDenied !== void 0) return {
5527
+ status: "denied",
5528
+ reason: preflightDenied,
5529
+ executionMs: Date.now() - startedAt
5530
+ };
4032
5531
  if (ui.length > 0) {
4033
5532
  turn.toolUi.set(call.id, ui);
4034
5533
  }
@@ -4044,6 +5543,13 @@ async function invokeClaimedTool(turn, claimed) {
4044
5543
  if (hooks.isControlFlowError?.(error) === true || isControlFlowSignal(error) || isReplayIntegrityError(error)) {
4045
5544
  throw error;
4046
5545
  }
5546
+ if (error instanceof Error && error.name === "ToolPreflightDeniedError") {
5547
+ return {
5548
+ status: "denied",
5549
+ reason: error.message,
5550
+ executionMs: Date.now() - startedAt
5551
+ };
5552
+ }
4047
5553
  return {
4048
5554
  status: "failed",
4049
5555
  error: error instanceof Error ? error.message : String(error),
@@ -4085,6 +5591,27 @@ async function recordToolOutcome(turn, claimed, outcome, deciderRef, meta) {
4085
5591
  } : {}
4086
5592
  }));
4087
5593
  }, "settle");
5594
+ if (outcome.status === "denied") {
5595
+ await hooks.step(`persist:toolreject:${call.id}`, async () => {
5596
+ await deps.store.updateToolCall({
5597
+ toolCallId: call.id,
5598
+ status: meta === void 0 ? "rejected" : "failed",
5599
+ error: outcome.reason,
5600
+ ...decided
5601
+ });
5602
+ await settle();
5603
+ });
5604
+ return {
5605
+ id: call.id,
5606
+ name: call.name,
5607
+ output: {
5608
+ rejected: true,
5609
+ reason: outcome.reason
5610
+ },
5611
+ denied: true,
5612
+ error: `This action was denied by its preflight check: ${outcome.reason}. The requested mutation did not run.`
5613
+ };
5614
+ }
4088
5615
  if (outcome.status === "failed") {
4089
5616
  await hooks.step(`persist:toolfail:${call.id}`, async () => {
4090
5617
  await deps.store.updateToolCall({
@@ -4225,12 +5752,12 @@ async function elicitToolCall(turn, claimed) {
4225
5752
  }));
4226
5753
  });
4227
5754
  const reply = await awaitElicitation(hooks, request, ctx);
4228
- const result = settleElicitation(request, reply);
4229
- await hooks.step(result.skipped ? `persist:toolreject:${call.id}` : `persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
5755
+ const result2 = settleElicitation(request, reply);
5756
+ await hooks.step(result2.skipped ? `persist:toolreject:${call.id}` : `persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
4230
5757
  toolCallId: call.id,
4231
- status: result.skipped ? "rejected" : "executed",
4232
- output: result,
4233
- ...result.skipped ? {
5758
+ status: result2.skipped ? "rejected" : "executed",
5759
+ output: result2,
5760
+ ...result2.skipped ? {
4234
5761
  error: "skipped by the user"
4235
5762
  } : {},
4236
5763
  ...reply.answeredByRef !== void 0 ? {
@@ -4241,24 +5768,50 @@ async function elicitToolCall(turn, claimed) {
4241
5768
  runId: hooks.runId,
4242
5769
  toolName: call.name,
4243
5770
  toolType: "action",
4244
- status: result.skipped ? "rejected" : "executed"
5771
+ status: result2.skipped ? "rejected" : "executed"
4245
5772
  });
4246
5773
  return {
4247
5774
  id: call.id,
4248
5775
  name: call.name,
4249
- output: result
5776
+ output: result2
4250
5777
  };
4251
5778
  }
4252
5779
  __name(elicitToolCall, "elicitToolCall");
4253
5780
  async function runClaimedToolCall(turn, claimed) {
4254
5781
  const { deps, input, hooks } = turn;
4255
5782
  const { call, toolType, ctx } = claimed;
5783
+ if (claimed.proposalId !== void 0) {
5784
+ turn.halt.independentProposal = true;
5785
+ return {
5786
+ id: call.id,
5787
+ name: call.name,
5788
+ output: actionProposalReceipt(claimed.proposalId)
5789
+ };
5790
+ }
4256
5791
  if (toolType === "agent") {
4257
5792
  return delegateToolCall(turn, claimed);
4258
5793
  }
4259
5794
  if (toolType === "ask") {
4260
5795
  return elicitToolCall(turn, claimed);
4261
5796
  }
5797
+ if (claimed.preflight !== void 0 && claimed.preflight.status !== "ready") {
5798
+ const result2 = claimed.preflight;
5799
+ if (result2.status === "completed" && claimed.terminal === true) turn.halt.terminal = true;
5800
+ const outcome = result2.status === "completed" ? {
5801
+ status: "executed",
5802
+ output: result2.output,
5803
+ executionMs: 0
5804
+ } : result2.status === "denied" ? {
5805
+ status: "denied",
5806
+ reason: result2.reason,
5807
+ executionMs: 0
5808
+ } : {
5809
+ status: "failed",
5810
+ error: result2.error,
5811
+ executionMs: 0
5812
+ };
5813
+ return recordToolOutcome(turn, claimed, outcome, input.actor.id);
5814
+ }
4262
5815
  let deciderRef = input.actor.id;
4263
5816
  let meta;
4264
5817
  if (toolType === "action" && claimed.approval?.mode === "remembered") {
@@ -4383,30 +5936,30 @@ function expiryNarrative() {
4383
5936
  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.";
4384
5937
  }
4385
5938
  __name(expiryNarrative, "expiryNarrative");
4386
- function outputFrame(result) {
4387
- if (result.denied === true) {
4388
- const reason = refusalReason(result);
5939
+ function outputFrame(result2) {
5940
+ if (result2.denied === true) {
5941
+ const reason = refusalReason(result2);
4389
5942
  return {
4390
5943
  kind: "tool-output-denied",
4391
- id: result.id,
5944
+ id: result2.id,
4392
5945
  ...reason !== void 0 ? {
4393
5946
  reason
4394
5947
  } : {}
4395
5948
  };
4396
5949
  }
4397
- return result.error !== void 0 ? {
5950
+ return result2.error !== void 0 ? {
4398
5951
  kind: "tool-output-error",
4399
- id: result.id,
4400
- error: result.error
5952
+ id: result2.id,
5953
+ error: result2.error
4401
5954
  } : {
4402
5955
  kind: "tool-output",
4403
- id: result.id,
4404
- output: result.output
5956
+ id: result2.id,
5957
+ output: result2.output
4405
5958
  };
4406
5959
  }
4407
5960
  __name(outputFrame, "outputFrame");
4408
- function refusalReason(result) {
4409
- const { output } = result;
5961
+ function refusalReason(result2) {
5962
+ const { output } = result2;
4410
5963
  if (output !== null && typeof output === "object" && "reason" in output) {
4411
5964
  const reason = output.reason;
4412
5965
  return typeof reason === "string" && reason !== DEFAULT_REFUSAL_REASON ? reason : void 0;
@@ -4646,6 +6199,25 @@ ${block}`;
4646
6199
  messages: prompt.messages,
4647
6200
  actor: input.actor,
4648
6201
  threadId: input.threadId,
6202
+ actionApprovalMode: history.actionApprovalMode ?? "blocking",
6203
+ preflightContext: {
6204
+ actor: input.actor,
6205
+ threadId: input.threadId,
6206
+ runId: hooks.runId,
6207
+ requestId: hooks.runId,
6208
+ ...input.agentName !== void 0 ? {
6209
+ agentName: input.agentName
6210
+ } : {},
6211
+ ...input.persona !== void 0 ? {
6212
+ persona: input.persona
6213
+ } : {},
6214
+ ...input.pageContext !== void 0 ? {
6215
+ pageContext: input.pageContext
6216
+ } : {},
6217
+ ...input.uiCapabilities !== void 0 ? {
6218
+ uiCapabilities: input.uiCapabilities
6219
+ } : {}
6220
+ },
4649
6221
  ...gated ? {
4650
6222
  bufferOutput: true
4651
6223
  } : {},
@@ -4662,6 +6234,9 @@ ${block}`;
4662
6234
  tools: withAskTool({
4663
6235
  tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, intersectAllowLists(deps.toolAllowList, persona?.allowedTools), {
4664
6236
  threadId: input.threadId,
6237
+ ...input.uiCapabilities !== void 0 ? {
6238
+ uiCapabilities: input.uiCapabilities
6239
+ } : {},
4665
6240
  ...input.agentName !== void 0 ? {
4666
6241
  agentName: input.agentName
4667
6242
  } : {}
@@ -4681,7 +6256,7 @@ ${block}`;
4681
6256
  }) : void 0;
4682
6257
  const buffer = gateMode === "whole" ? createFrameBuffer() : void 0;
4683
6258
  const frames = observeTurnFrames(incremental?.writer ?? buffer?.writer ?? writer);
4684
- const result = stampToolKinds(withTurnFrames(await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
6259
+ const result2 = stampToolKinds(withTurnFrames(await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
4685
6260
  system: prompt.system,
4686
6261
  messages: prompt.messages,
4687
6262
  tools,
@@ -4691,15 +6266,15 @@ ${block}`;
4691
6266
  await incremental.settled();
4692
6267
  const refusal = incremental.rejection();
4693
6268
  return {
4694
- ...result,
6269
+ ...result2,
4695
6270
  releasedText: incremental.released(),
4696
6271
  ...refusal !== void 0 ? {
4697
6272
  gateRejection: refusal
4698
6273
  } : {}
4699
6274
  };
4700
6275
  }
4701
- return buffer === void 0 ? result : {
4702
- ...result,
6276
+ return buffer === void 0 ? result2 : {
6277
+ ...result2,
4703
6278
  bufferedFrames: buffer.frames()
4704
6279
  };
4705
6280
  });
@@ -4793,12 +6368,12 @@ ${block}`;
4793
6368
  role: "assistant",
4794
6369
  content: turn.text
4795
6370
  }
4796
- ], count), (result) => ({
4797
- followUps: result.followUps.length,
4798
- inputTokens: result.usage.inputTokens,
4799
- outputTokens: result.usage.outputTokens,
4800
- ...result.modelId !== void 0 ? {
4801
- modelId: result.modelId
6371
+ ], count), (result2) => ({
6372
+ followUps: result2.followUps.length,
6373
+ inputTokens: result2.usage.inputTokens,
6374
+ outputTokens: result2.usage.outputTokens,
6375
+ ...result2.modelId !== void 0 ? {
6376
+ modelId: result2.modelId
4802
6377
  } : {}
4803
6378
  })));
4804
6379
  let suggestions = generated.followUps;
@@ -4909,7 +6484,7 @@ ${block}`;
4909
6484
  };
4910
6485
  modelMessages.push(assistantMessage);
4911
6486
  for (const entry of synthetic) {
4912
- const { call, result } = entry;
6487
+ const { call, result: result2 } = entry;
4913
6488
  await hooks.step(`${entry.step}:${assistant.id}`, async () => {
4914
6489
  await deps.store.recordToolCall({
4915
6490
  toolCallId: call.id,
@@ -4923,7 +6498,7 @@ ${block}`;
4923
6498
  await deps.store.updateToolCall({
4924
6499
  toolCallId: call.id,
4925
6500
  status: "executed",
4926
- output: result.output
6501
+ output: result2.output
4927
6502
  });
4928
6503
  await writer.write(encodeStreamEvent({
4929
6504
  kind: "tool-input-available",
@@ -4935,7 +6510,7 @@ ${block}`;
4935
6510
  await writer.write(encodeStreamEvent({
4936
6511
  kind: "tool-output",
4937
6512
  id: call.id,
4938
- output: result.output
6513
+ output: result2.output
4939
6514
  }));
4940
6515
  });
4941
6516
  }
@@ -4964,9 +6539,11 @@ ${block}`;
4964
6539
  messageId: assistant.id,
4965
6540
  writer,
4966
6541
  toolUi: /* @__PURE__ */ new Map(),
6542
+ toolText: /* @__PURE__ */ new Map(),
4967
6543
  halt: {
4968
6544
  terminal: false
4969
6545
  },
6546
+ actionApprovalMode: history.actionApprovalMode ?? "blocking",
4970
6547
  ...skillOffer !== void 0 ? {
4971
6548
  skills: skillOffer
4972
6549
  } : {},
@@ -4999,17 +6576,35 @@ ${block}`;
4999
6576
  ...results,
5000
6577
  ...syntheticResults
5001
6578
  ];
6579
+ const toolText = escapeUnsafeToolUiText(toolCallsWithKind.map((call) => turnCalls.toolText.get(call.id)).filter((text) => text !== void 0).join("\n"));
5002
6580
  assistantMessage.toolResults = settledResults;
5003
6581
  await hooks.step(`stream:tool-outputs:${i}`, async () => {
5004
6582
  await deps.store.setMessageToolResults(assistant.id, settledResults);
6583
+ if (toolText) await deps.store.appendMessage({
6584
+ threadId: input.threadId,
6585
+ role: "assistant",
6586
+ content: toolText,
6587
+ runId: hooks.runId,
6588
+ ...input.agentName !== void 0 ? {
6589
+ agentName: input.agentName
6590
+ } : {}
6591
+ });
5005
6592
  const toolUi = toolCallsWithKind.flatMap((call) => turnCalls.toolUi.get(call.id) ?? []);
5006
6593
  if (toolUi.length > 0) {
5007
6594
  await deps.store.setMessageUi?.(assistant.id, mergeUi(turn.ui, toolUi));
5008
6595
  }
5009
- for (const result of results) {
5010
- await writer.write(encodeStreamEvent(outputFrame(result)));
6596
+ for (const result2 of results) {
6597
+ await writer.write(encodeStreamEvent(outputFrame(result2)));
5011
6598
  }
5012
6599
  });
6600
+ if (toolText) {
6601
+ modelMessages.push({
6602
+ role: "assistant",
6603
+ content: toolText
6604
+ });
6605
+ lastText += `
6606
+ ${toolText}`;
6607
+ }
5013
6608
  await hooks.step(`stream:step-finish:${i}`, async () => {
5014
6609
  await writer.write(encodeStreamEvent({
5015
6610
  kind: "step-finish",
@@ -5023,7 +6618,7 @@ ${block}`;
5023
6618
  } : {}
5024
6619
  }));
5025
6620
  });
5026
- if (turnCalls.halt.terminal) {
6621
+ if (turnCalls.halt.terminal || turnCalls.halt.independentProposal) {
5027
6622
  break;
5028
6623
  }
5029
6624
  }
@@ -5079,8 +6674,152 @@ ${block}`;
5079
6674
  }
5080
6675
  __name(runAgentLoop, "runAgentLoop");
5081
6676
 
6677
+ // src/in-memory-action-proposal-store.ts
6678
+ import { randomUUID } from "crypto";
6679
+ var InMemoryActionProposalStore = class {
6680
+ static {
6681
+ __name(this, "InMemoryActionProposalStore");
6682
+ }
6683
+ rows = /* @__PURE__ */ new Map();
6684
+ clock;
6685
+ constructor(options = {}) {
6686
+ this.clock = options.clock ?? Date.now;
6687
+ }
6688
+ async createActionProposal(input) {
6689
+ validateActionProposalCreation(input);
6690
+ const row = this.rows.get(input.id);
6691
+ if (row) {
6692
+ if (!actionProposalScopeMatches(row, input)) return {
6693
+ status: "conflict"
6694
+ };
6695
+ return {
6696
+ status: actionProposalCreationMatches(row, input) ? "unchanged" : "conflict",
6697
+ proposal: snapshotActionProposal(row)
6698
+ };
6699
+ }
6700
+ const proposal = initialActionProposal(input, this.clock());
6701
+ this.rows.set(input.id, proposal);
6702
+ return {
6703
+ status: "created",
6704
+ proposal: snapshotActionProposal(proposal)
6705
+ };
6706
+ }
6707
+ async getActionProposal(scope, id) {
6708
+ const row = this.rows.get(id);
6709
+ return row && actionProposalScopeMatches(row, scope) ? snapshotActionProposal(row) : null;
6710
+ }
6711
+ async listActionProposals(scope, query = {}) {
6712
+ const limit = validateActionProposalListQuery(query);
6713
+ return [
6714
+ ...this.rows.values()
6715
+ ].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);
6716
+ }
6717
+ mutate(scope, id, transition) {
6718
+ const row = this.rows.get(id);
6719
+ if (!row || !actionProposalScopeMatches(row, scope)) return {
6720
+ status: "not_found"
6721
+ };
6722
+ const result2 = transition(row, this.clock());
6723
+ if (result2.proposal) this.rows.set(id, snapshotActionProposal(result2.proposal));
6724
+ return result2;
6725
+ }
6726
+ async decideActionProposal(scope, id, command) {
6727
+ return this.mutate(scope, id, (row, now) => transitionActionProposalDecision(row, command, now));
6728
+ }
6729
+ async claimActionProposal(scope, id, command) {
6730
+ return this.mutate(scope, id, (row, now) => transitionActionProposalClaim(row, command, now, randomUUID()));
6731
+ }
6732
+ async extendActionProposalLease(scope, id, command) {
6733
+ return this.mutate(scope, id, (row, now) => transitionActionProposalLease(row, command, now));
6734
+ }
6735
+ async settleActionProposal(scope, id, command) {
6736
+ return this.mutate(scope, id, (row, now) => transitionActionProposalSettlement(row, command, now));
6737
+ }
6738
+ async claimNextActionProposal(command) {
6739
+ const now = this.clock();
6740
+ validateActionProposalWorkerClaim(command, now);
6741
+ const candidates = [
6742
+ ...this.rows.values()
6743
+ ].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);
6744
+ for (const candidate of candidates) {
6745
+ const claimed = await this.claimActionProposal(candidate, candidate.id, command);
6746
+ if (claimed.status === "applied" && claimed.proposal) return claimed.proposal;
6747
+ }
6748
+ return null;
6749
+ }
6750
+ async expireActionProposals(command) {
6751
+ const now = this.clock();
6752
+ validateActionProposalExpiryBatch(command, now);
6753
+ const candidates = [
6754
+ ...this.rows.values()
6755
+ ].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);
6756
+ let expired = 0;
6757
+ for (const candidate of candidates) {
6758
+ const result2 = await this.decideActionProposal(candidate, candidate.id, {
6759
+ decision: "expired",
6760
+ actorRef: "system",
6761
+ via: "expiry"
6762
+ });
6763
+ if (result2.status === "applied") expired++;
6764
+ }
6765
+ return expired;
6766
+ }
6767
+ async claimNextActionProposalOutcome(command) {
6768
+ const now = this.clock();
6769
+ validateActionProposalWorkerClaim(command, now);
6770
+ const candidates = [
6771
+ ...this.rows.values()
6772
+ ].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);
6773
+ for (const candidate of candidates) {
6774
+ const row = this.rows.get(candidate.id);
6775
+ if (!row) continue;
6776
+ const next = claimActionProposalOutcome(row, command, this.clock(), randomUUID());
6777
+ if (!next?.outcome || !next.outcomeDelivery?.lease) continue;
6778
+ this.rows.set(next.id, next);
6779
+ return {
6780
+ outcome: snapshotActionProposal(next.outcome),
6781
+ lease: {
6782
+ outcomeId: next.outcome.id,
6783
+ token: next.outcomeDelivery.lease.token,
6784
+ generation: next.outcomeDelivery.generation
6785
+ }
6786
+ };
6787
+ }
6788
+ return null;
6789
+ }
6790
+ async createReplacingActionProposal(input) {
6791
+ validateActionProposalCreation(input);
6792
+ const existing = this.rows.get(input.id);
6793
+ if (existing) return this.createActionProposal(input);
6794
+ const proposal = initialActionProposal(input, this.clock());
6795
+ const replacements = [];
6796
+ if (input.replacementKey) for (const row of this.rows.values()) {
6797
+ if (row.decision !== "pending" || !actionProposalScopeMatches(row, input) || row.toolName !== input.toolName || row.replacementKey !== input.replacementKey) continue;
6798
+ const result2 = transitionActionProposalSupersession(row, proposal, {
6799
+ replacementProposalId: proposal.id,
6800
+ actorRef: input.actorRef,
6801
+ via: "supersession"
6802
+ }, this.clock());
6803
+ if (result2.proposal) replacements.push(result2.proposal);
6804
+ }
6805
+ this.rows.set(proposal.id, proposal);
6806
+ for (const row of replacements) this.rows.set(row.id, row);
6807
+ return {
6808
+ status: "created",
6809
+ proposal: snapshotActionProposal(proposal)
6810
+ };
6811
+ }
6812
+ async supersedeActionProposal(scope, id, command) {
6813
+ const replacement = this.rows.get(command.replacementProposalId);
6814
+ if (!replacement || !actionProposalScopeMatches(replacement, scope)) return {
6815
+ status: "not_found"
6816
+ };
6817
+ return this.mutate(scope, id, (row, now) => transitionActionProposalSupersession(row, replacement, command, now));
6818
+ }
6819
+ };
6820
+
5082
6821
  // src/in-memory-store.ts
5083
- var InMemoryAgentStore = class {
6822
+ var InMemoryAgentStore = class extends InMemoryActionProposalStore {
5084
6823
  static {
5085
6824
  __name(this, "InMemoryAgentStore");
5086
6825
  }
@@ -5103,6 +6842,7 @@ var InMemoryAgentStore = class {
5103
6842
  const row = {
5104
6843
  id,
5105
6844
  actorRef: input.actor.id,
6845
+ tenantRef: input.actor.tenantRef ?? null,
5106
6846
  title: input.title ?? "New chat",
5107
6847
  transient: input.transient ?? false,
5108
6848
  createdAt: ts,
@@ -5138,8 +6878,12 @@ var InMemoryAgentStore = class {
5138
6878
  }
5139
6879
  const approval = toolCallApprovalFromRow({
5140
6880
  toolCallId: call.toolCallId,
6881
+ ...call.proposalId !== void 0 ? {
6882
+ proposalId: call.proposalId
6883
+ } : {},
5141
6884
  status: call.status,
5142
6885
  approver: call.approver,
6886
+ confirmation: call.confirmation,
5143
6887
  expiresAt: call.expiresAt,
5144
6888
  remember: call.remember,
5145
6889
  executedByRef: call.executedByRef,
@@ -5162,6 +6906,10 @@ var InMemoryAgentStore = class {
5162
6906
  names.add(call.toolName);
5163
6907
  }
5164
6908
  }
6909
+ const thread = this.threads.get(threadId);
6910
+ if (thread) {
6911
+ 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);
6912
+ }
5165
6913
  return [
5166
6914
  ...names
5167
6915
  ];
@@ -5202,6 +6950,7 @@ var InMemoryAgentStore = class {
5202
6950
  const row = {
5203
6951
  id,
5204
6952
  actorRef: source.actorRef,
6953
+ tenantRef: source.tenantRef,
5205
6954
  title: source.title,
5206
6955
  transient: false,
5207
6956
  createdAt: ts,
@@ -5402,6 +7151,9 @@ var InMemoryAgentStore = class {
5402
7151
  ...input.pageContext !== void 0 ? {
5403
7152
  pageContext: input.pageContext
5404
7153
  } : {},
7154
+ ...input.uiCapabilities !== void 0 ? {
7155
+ uiCapabilities: input.uiCapabilities
7156
+ } : {},
5405
7157
  ...input.interrupt === true ? {
5406
7158
  interrupt: true
5407
7159
  } : {},
@@ -5506,6 +7258,78 @@ var InMemoryAgentStore = class {
5506
7258
  }
5507
7259
  return void 0;
5508
7260
  }
7261
+ async getThreadActionProposalScope(threadId) {
7262
+ const row = this.threads.get(threadId);
7263
+ return row ? {
7264
+ threadId,
7265
+ actorRef: row.actorRef,
7266
+ tenantRef: row.tenantRef
7267
+ } : null;
7268
+ }
7269
+ async admitActionProposalOutcome(command) {
7270
+ const row = [
7271
+ ...this.rows.values()
7272
+ ].find((row2) => row2.outcome?.id === command.outcomeId);
7273
+ if (!row?.outcome || !row.outcomeDelivery) return {
7274
+ status: "not_found"
7275
+ };
7276
+ if (row.outcomeDelivery.status === "admitted") return {
7277
+ status: "unchanged",
7278
+ ...row.outcomeDelivery.messageId !== void 0 ? {
7279
+ messageId: row.outcomeDelivery.messageId
7280
+ } : {}
7281
+ };
7282
+ if (row.outcomeDelivery.status === "discarded") return {
7283
+ status: "discarded"
7284
+ };
7285
+ if (!actionProposalOutcomeFenceValid(row, command, this.clock())) return {
7286
+ status: "conflict"
7287
+ };
7288
+ const thread = this.threads.get(row.threadId);
7289
+ if (!thread) {
7290
+ this.rows.set(row.id, snapshotActionProposal({
7291
+ ...row,
7292
+ outcomeDelivery: {
7293
+ ...row.outcomeDelivery,
7294
+ status: "discarded",
7295
+ lease: null
7296
+ }
7297
+ }));
7298
+ return {
7299
+ status: "discarded"
7300
+ };
7301
+ }
7302
+ if (thread.actorRef !== row.actorRef || thread.tenantRef !== row.tenantRef) return {
7303
+ status: "conflict"
7304
+ };
7305
+ if (thread.activeStreamId) return {
7306
+ status: "busy"
7307
+ };
7308
+ const message = {
7309
+ id: crypto.randomUUID(),
7310
+ role: "assistant",
7311
+ content: actionProposalOutcomeText(row.outcome),
7312
+ createdAt: this.now(),
7313
+ actionProposalOutcome: snapshotActionProposal(row.outcome),
7314
+ ui: snapshotActionProposal(row.outcome.ui)
7315
+ };
7316
+ const next = snapshotActionProposal({
7317
+ ...row,
7318
+ outcomeDelivery: {
7319
+ ...row.outcomeDelivery,
7320
+ status: "admitted",
7321
+ lease: null,
7322
+ messageId: message.id
7323
+ }
7324
+ });
7325
+ thread.messages.push(message);
7326
+ thread.updatedAt = message.createdAt;
7327
+ this.rows.set(row.id, next);
7328
+ return {
7329
+ status: "applied",
7330
+ messageId: message.id
7331
+ };
7332
+ }
5509
7333
  async appendMessage(input) {
5510
7334
  const row = this.threads.get(input.threadId);
5511
7335
  if (row === void 0) {
@@ -5548,6 +7372,9 @@ var InMemoryAgentStore = class {
5548
7372
  } : {},
5549
7373
  ...input.ui !== void 0 ? {
5550
7374
  ui: input.ui
7375
+ } : {},
7376
+ ...input.actionProposalOutcome !== void 0 ? {
7377
+ actionProposalOutcome: input.actionProposalOutcome
5551
7378
  } : {}
5552
7379
  };
5553
7380
  row.messages.push(message);
@@ -5639,6 +7466,9 @@ var InMemoryAgentStore = class {
5639
7466
  async recordToolCall(input) {
5640
7467
  this.toolCalls.set(input.toolCallId, {
5641
7468
  toolCallId: input.toolCallId,
7469
+ ...input.proposalId !== void 0 ? {
7470
+ proposalId: input.proposalId
7471
+ } : {},
5642
7472
  messageId: input.messageId,
5643
7473
  threadId: this.threadIdForMessage(input.messageId) ?? "",
5644
7474
  toolName: input.toolName,
@@ -5652,6 +7482,9 @@ var InMemoryAgentStore = class {
5652
7482
  ...input.approver !== void 0 ? {
5653
7483
  approver: input.approver
5654
7484
  } : {},
7485
+ ...input.confirmation !== void 0 ? {
7486
+ confirmation: structuredClone(input.confirmation)
7487
+ } : {},
5655
7488
  ...input.expiresAt !== void 0 ? {
5656
7489
  expiresAt: input.expiresAt
5657
7490
  } : {}
@@ -6156,12 +7989,12 @@ function withConfirmFields(schema) {
6156
7989
  });
6157
7990
  }
6158
7991
  const { args, fields } = splitConfirm(value);
6159
- const result = await inner.validate(args);
6160
- if (result.issues !== void 0) {
7992
+ const result2 = await inner.validate(args);
7993
+ if (result2.issues !== void 0) {
6161
7994
  return {
6162
7995
  issues: [
6163
7996
  ...issues,
6164
- ...result.issues
7997
+ ...result2.issues
6165
7998
  ]
6166
7999
  };
6167
8000
  }
@@ -6171,10 +8004,10 @@ function withConfirmFields(schema) {
6171
8004
  };
6172
8005
  }
6173
8006
  return {
6174
- value: isRecord(result.value) ? {
6175
- ...result.value,
8007
+ value: isRecord(result2.value) ? {
8008
+ ...result2.value,
6176
8009
  ...fields
6177
- } : result.value
8010
+ } : result2.value
6178
8011
  };
6179
8012
  }, "validate"),
6180
8013
  jsonSchema: {
@@ -6506,6 +8339,309 @@ var SqlTokenStreamSink = class {
6506
8339
  });
6507
8340
  }
6508
8341
  };
8342
+
8343
+ // src/action-proposal-text.ts
8344
+ function resolveTextActionProposalDecision(text, proposals) {
8345
+ const command = parseTextActionProposalCommand(text);
8346
+ if (command.status === "unmatched") return command;
8347
+ const { decision, remember } = command;
8348
+ const pending = proposals.filter((proposal) => proposal.decision === "pending");
8349
+ const explicitId = command.proposalId;
8350
+ if (explicitId !== void 0) {
8351
+ if (!pending.some((proposal) => proposal.id === explicitId)) return {
8352
+ status: "unmatched"
8353
+ };
8354
+ return {
8355
+ status: "decision",
8356
+ proposalId: explicitId,
8357
+ decision,
8358
+ remember
8359
+ };
8360
+ }
8361
+ if (pending.length === 0) return {
8362
+ status: "unmatched"
8363
+ };
8364
+ if (pending.length > 1) return {
8365
+ status: "ambiguous",
8366
+ proposalIds: pending.map(({ id }) => id)
8367
+ };
8368
+ const candidate = pending[0];
8369
+ if (!candidate) return {
8370
+ status: "unmatched"
8371
+ };
8372
+ return {
8373
+ status: "decision",
8374
+ proposalId: candidate.id,
8375
+ decision,
8376
+ remember
8377
+ };
8378
+ }
8379
+ __name(resolveTextActionProposalDecision, "resolveTextActionProposalDecision");
8380
+ function parseTextActionProposalCommand(text) {
8381
+ if (/[\r\n]/u.test(text)) return {
8382
+ status: "unmatched"
8383
+ };
8384
+ const normalized = text.trim();
8385
+ const match = /^(sim|confirmo|confirmar|aprovar|aprovo|cancelar|rejeitar|rejeito|não|nao)(?: (sempre nesta conversa))?(?: #([^\s]+?))?[.!]?$/iu.exec(normalized);
8386
+ if (!match) return {
8387
+ status: "unmatched"
8388
+ };
8389
+ const verb = match[1]?.toLocaleLowerCase("pt-BR");
8390
+ if (verb === void 0) return {
8391
+ status: "unmatched"
8392
+ };
8393
+ const decision = [
8394
+ "cancelar",
8395
+ "rejeitar",
8396
+ "rejeito",
8397
+ "n\xE3o",
8398
+ "nao"
8399
+ ].includes(verb) ? "rejected" : "approved";
8400
+ const remember = match[2] !== void 0;
8401
+ if (remember && decision !== "approved") return {
8402
+ status: "unmatched"
8403
+ };
8404
+ return {
8405
+ status: "command",
8406
+ decision,
8407
+ remember,
8408
+ ...match[3] !== void 0 ? {
8409
+ proposalId: match[3]
8410
+ } : {}
8411
+ };
8412
+ }
8413
+ __name(parseTextActionProposalCommand, "parseTextActionProposalCommand");
8414
+
8415
+ // src/action-proposal-executor.ts
8416
+ var ActionProposalExecutor = class {
8417
+ static {
8418
+ __name(this, "ActionProposalExecutor");
8419
+ }
8420
+ options;
8421
+ constructor(options) {
8422
+ this.options = options;
8423
+ }
8424
+ async execute(proposal) {
8425
+ let ui = createNegotiatedUiCollector(proposal.originToolCallId, {
8426
+ actor: {
8427
+ id: proposal.actorRef
8428
+ },
8429
+ threadId: proposal.threadId
8430
+ });
8431
+ try {
8432
+ const actor = await this.options.resolver.resolve({
8433
+ actorRef: proposal.actorRef,
8434
+ tenantRef: proposal.tenantRef
8435
+ });
8436
+ if (!actor || actor.id !== proposal.actorRef || (actor.tenantRef ?? null) !== proposal.tenantRef) throw new Error("Action proposal requester no longer exists or scope changed");
8437
+ const deps = await this.options.resolveExecution(proposal, actor);
8438
+ if (deps.registry.spec(proposal.toolName)?.kind !== "action") throw new Error("Action proposal tool no longer exists as an action");
8439
+ const context = proposal.executionContext;
8440
+ ui = createNegotiatedUiCollector(proposal.originToolCallId, {
8441
+ actor,
8442
+ threadId: proposal.threadId,
8443
+ ...context?.agentName !== void 0 ? {
8444
+ agentName: context.agentName
8445
+ } : {},
8446
+ ...context?.uiCapabilities !== void 0 ? {
8447
+ uiCapabilities: context.uiCapabilities
8448
+ } : {}
8449
+ }, deps.resolveUiCatalog);
8450
+ const output = await deps.registry.invoke(proposal.toolName, snapshotActionProposal(Object.hasOwn(proposal, "preparationInput") ? proposal.preparationInput : proposal.input), {
8451
+ actor,
8452
+ threadId: proposal.threadId,
8453
+ runId: proposal.originRunId,
8454
+ requestId: context?.requestId ?? proposal.originRunId,
8455
+ toolCallId: proposal.originToolCallId,
8456
+ idempotencyKey: proposal.idempotencyKey,
8457
+ ...context?.agentName !== void 0 ? {
8458
+ agentName: context.agentName
8459
+ } : {},
8460
+ ...context?.persona !== void 0 ? {
8461
+ persona: context.persona
8462
+ } : {},
8463
+ ...context?.pageContext !== void 0 ? {
8464
+ pageContext: context.pageContext
8465
+ } : {},
8466
+ ...context?.uiCapabilities !== void 0 ? {
8467
+ uiCapabilities: context.uiCapabilities
8468
+ } : {},
8469
+ ...deps.host !== void 0 ? {
8470
+ host: deps.host
8471
+ } : {},
8472
+ emitUi: ui.emit
8473
+ }, deps.rolesPolicy, {
8474
+ approvedInput: proposal.input,
8475
+ ...deps.allowedTools !== void 0 ? {
8476
+ allowedTools: deps.allowedTools
8477
+ } : {}
8478
+ });
8479
+ return snapshotActionProposal({
8480
+ status: "succeeded",
8481
+ ...output !== void 0 ? {
8482
+ result: output
8483
+ } : {},
8484
+ ui: ui.components(),
8485
+ ...ui.text() ? {
8486
+ text: ui.text()
8487
+ } : {}
8488
+ });
8489
+ } catch (error) {
8490
+ return {
8491
+ status: "failed",
8492
+ error: error instanceof Error ? error.message : String(error),
8493
+ ui: ui.components(),
8494
+ ...ui.text() ? {
8495
+ text: ui.text()
8496
+ } : {}
8497
+ };
8498
+ }
8499
+ }
8500
+ };
8501
+
8502
+ // src/action-proposal-worker.ts
8503
+ var ActionProposalWorker = class {
8504
+ static {
8505
+ __name(this, "ActionProposalWorker");
8506
+ }
8507
+ options;
8508
+ leaseMs;
8509
+ pollIntervalMs;
8510
+ maxConcurrency;
8511
+ timer;
8512
+ stopped = false;
8513
+ renewals = /* @__PURE__ */ new Set();
8514
+ active;
8515
+ constructor(options) {
8516
+ this.options = options;
8517
+ this.leaseMs = options.leaseMs ?? 3e4;
8518
+ this.pollIntervalMs = options.pollIntervalMs ?? 1e3;
8519
+ this.maxConcurrency = options.maxConcurrency ?? 1;
8520
+ for (const value of [
8521
+ this.leaseMs,
8522
+ this.pollIntervalMs,
8523
+ this.maxConcurrency
8524
+ ]) if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError("Worker configuration must be positive safe integers");
8525
+ if (this.pollIntervalMs >= this.leaseMs / 3) throw new RangeError("Worker poll interval must be below one third of lease");
8526
+ if (!options.workerId) throw new TypeError("workerId is required");
8527
+ }
8528
+ start() {
8529
+ if (this.timer !== void 0 || this.stopped) return;
8530
+ const tick = /* @__PURE__ */ __name(async () => {
8531
+ if (this.stopped) return;
8532
+ try {
8533
+ await this.runOnce();
8534
+ } catch (error) {
8535
+ this.options.onError?.(error);
8536
+ }
8537
+ if (!this.stopped) this.timer = setTimeout(tick, this.pollIntervalMs);
8538
+ }, "tick");
8539
+ this.timer = setTimeout(tick, 0);
8540
+ }
8541
+ async stop() {
8542
+ this.stopped = true;
8543
+ for (const timer of this.renewals) clearInterval(timer);
8544
+ this.renewals.clear();
8545
+ if (this.timer !== void 0) clearTimeout(this.timer);
8546
+ await this.active;
8547
+ }
8548
+ runOnce() {
8549
+ if (this.active) return this.active;
8550
+ if (this.stopped) return Promise.resolve();
8551
+ const task = this.tick();
8552
+ this.active = task;
8553
+ return task.finally(() => {
8554
+ if (this.active === task) this.active = void 0;
8555
+ });
8556
+ }
8557
+ async tick() {
8558
+ await this.options.store.expireActionProposals({
8559
+ limit: 100
8560
+ });
8561
+ const work = [];
8562
+ for (let index = 0; index < this.maxConcurrency && !this.stopped; index++) {
8563
+ const proposal = await this.options.store.claimNextActionProposal({
8564
+ workerId: this.options.workerId,
8565
+ leaseMs: this.leaseMs
8566
+ });
8567
+ if (!proposal) break;
8568
+ work.push(this.execute(proposal));
8569
+ }
8570
+ await Promise.all(work);
8571
+ for (let index = 0; index < 32 && !this.stopped; index++) {
8572
+ const delivery = await this.options.store.claimNextActionProposalOutcome({
8573
+ workerId: this.options.workerId,
8574
+ leaseMs: this.leaseMs
8575
+ });
8576
+ if (!delivery) break;
8577
+ await this.options.store.admitActionProposalOutcome(delivery.lease);
8578
+ }
8579
+ }
8580
+ async execute(proposal) {
8581
+ const lease = proposal.execution?.lease;
8582
+ if (!lease) throw new Error("Claimed action lacks execution lease");
8583
+ let fenced = true;
8584
+ let renewing;
8585
+ const renew = /* @__PURE__ */ __name(() => {
8586
+ if (renewing || !fenced) return;
8587
+ renewing = this.options.store.extendActionProposalLease(proposal, proposal.id, {
8588
+ token: lease.token,
8589
+ generation: lease.generation,
8590
+ leaseMs: this.leaseMs
8591
+ }).then((result2) => {
8592
+ if (result2.status !== "applied") fenced = false;
8593
+ }, (error) => {
8594
+ fenced = false;
8595
+ this.options.onError?.(error);
8596
+ }).finally(() => {
8597
+ renewing = void 0;
8598
+ });
8599
+ }, "renew");
8600
+ const timer = setInterval(renew, this.leaseMs / 3);
8601
+ this.renewals.add(timer);
8602
+ try {
8603
+ const result2 = await this.options.executor.execute(proposal);
8604
+ clearInterval(timer);
8605
+ await renewing;
8606
+ if (!fenced) return;
8607
+ const settled = await this.options.store.settleActionProposal(proposal, proposal.id, {
8608
+ ...result2,
8609
+ token: lease.token,
8610
+ generation: lease.generation
8611
+ });
8612
+ if (settled.status === "applied" && settled.proposal) await this.options.onSettled?.(settled.proposal);
8613
+ } finally {
8614
+ clearInterval(timer);
8615
+ this.renewals.delete(timer);
8616
+ await renewing;
8617
+ }
8618
+ }
8619
+ };
8620
+
8621
+ // src/action-proposal-capabilities.ts
8622
+ function assertIndependentActionCapabilities(store, resolver) {
8623
+ const candidate = store;
8624
+ if (!resolver || typeof resolver.resolve !== "function") throw new Error("Independent action mode requires a BackgroundActorResolver");
8625
+ if (!isChatQueueStore(store) || candidate.actionProposalAdmissionSupported === false) throw new Error("Independent action mode requires transactional thread admission");
8626
+ const required = [
8627
+ "createActionProposal",
8628
+ "getActionProposal",
8629
+ "listActionProposals",
8630
+ "decideActionProposal",
8631
+ "claimActionProposal",
8632
+ "extendActionProposalLease",
8633
+ "settleActionProposal",
8634
+ "claimNextActionProposal",
8635
+ "expireActionProposals",
8636
+ "getThreadActionProposalScope",
8637
+ "claimNextActionProposalOutcome",
8638
+ "admitActionProposalOutcome",
8639
+ "createReplacingActionProposal",
8640
+ "supersedeActionProposal"
8641
+ ];
8642
+ 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");
8643
+ }
8644
+ __name(assertIndependentActionCapabilities, "assertIndependentActionCapabilities");
6509
8645
  export {
6510
8646
  AGENT_ACTOR_DIRECTORY,
6511
8647
  AGENT_ACTOR_RESOLVER,
@@ -6539,6 +8675,8 @@ export {
6539
8675
  APPROVAL_EXPIRED_REASON,
6540
8676
  ASK_TOOL_DESCRIPTION,
6541
8677
  ASK_TOOL_NAME,
8678
+ ActionProposalExecutor,
8679
+ ActionProposalWorker,
6542
8680
  AgentRegistry,
6543
8681
  AgentStreamError,
6544
8682
  CONFIRM_JSON_SCHEMA_PROPERTIES,
@@ -6559,6 +8697,7 @@ export {
6559
8697
  DefaultRolesPolicy,
6560
8698
  ELICITATION_INPUT_TYPES,
6561
8699
  GLOBAL_SCOPE,
8700
+ InMemoryActionProposalStore,
6562
8701
  InMemoryAgentStore,
6563
8702
  InMemoryConfirmTokenStore,
6564
8703
  MAX_ASK_QUESTIONS,
@@ -6582,15 +8721,25 @@ export {
6582
8721
  THREAD_DETAIL_CONTENT_CHARS,
6583
8722
  ToolDisabledError,
6584
8723
  ToolForbiddenError,
8724
+ ToolInputDriftError,
6585
8725
  ToolInputInvalidError,
6586
8726
  ToolNotFoundError,
8727
+ ToolPreflightDeniedError,
6587
8728
  ToolRegistry,
6588
8729
  UNFINISHED_TOOL_CALL,
8730
+ actionProposalCreationMatches,
8731
+ actionProposalOutcomeFenceValid,
8732
+ actionProposalOutcomeScope,
8733
+ actionProposalOutcomeText,
8734
+ actionProposalReceipt,
8735
+ actionProposalScopeMatches,
6589
8736
  actorScope,
6590
8737
  agentDiagnosticKey,
6591
8738
  agentFailureCode,
6592
8739
  askInputSchema,
6593
8740
  askToolDefinition,
8741
+ assertIndependentActionCapabilities,
8742
+ attachActionProposalOutcome,
6594
8743
  bucketByActor,
6595
8744
  bucketByModel,
6596
8745
  bucketByThread,
@@ -6598,12 +8747,15 @@ export {
6598
8747
  buildMemoryBlock,
6599
8748
  buildSkillsBlock,
6600
8749
  canActorUseTool,
8750
+ canonicalActionProposalJson,
6601
8751
  canonicalJson,
8752
+ claimActionProposalOutcome,
6602
8753
  closeEmptyRoles,
6603
8754
  compositeSkillProvider,
6604
8755
  confirmTokenExpiry,
6605
8756
  createFrameBuffer,
6606
8757
  createIncrementalGate,
8758
+ createNegotiatedUiCollector,
6607
8759
  createNoopEmitUi,
6608
8760
  createUiCollector,
6609
8761
  danglingToolCallIds,
@@ -6616,6 +8768,7 @@ export {
6616
8768
  detachedStarted,
6617
8769
  detachedUnsettled,
6618
8770
  encodeStreamEvent,
8771
+ escapeUnsafeToolUiText,
6619
8772
  estimateCost,
6620
8773
  estimateMessageTokens,
6621
8774
  exhaustedWindow,
@@ -6630,6 +8783,7 @@ export {
6630
8783
  gateFollowUps,
6631
8784
  gateTail,
6632
8785
  hashConfirmToken,
8786
+ initialActionProposal,
6633
8787
  intersectAllowLists,
6634
8788
  invokeWithTransientRetry,
6635
8789
  isChatQueueStore,
@@ -6648,7 +8802,9 @@ export {
6648
8802
  observeTurnFrames,
6649
8803
  offerMemories,
6650
8804
  offerSkills,
8805
+ parseTextActionProposalCommand,
6651
8806
  personaCatalogEntry,
8807
+ prepareActionProposal,
6652
8808
  publishAgentDelegated,
6653
8809
  publishAgentMemoryResolved,
6654
8810
  publishAgentMemoryWritten,
@@ -6674,12 +8830,14 @@ export {
6674
8830
  rememberToolDefinition,
6675
8831
  renderElicitationAnswers,
6676
8832
  repairInstruction,
8833
+ resolveActionProposalApproval,
6677
8834
  resolveElicitation,
6678
8835
  resolveGateLookback,
6679
8836
  resolveMemoryDigest,
6680
8837
  resolveOutputGateMode,
6681
8838
  resolvePersonaAlias,
6682
8839
  resolveSkillCatalog,
8840
+ resolveTextActionProposalDecision,
6683
8841
  resolveToolTransientRetryNumbers,
6684
8842
  rollupThreadUsage,
6685
8843
  runAgentLoop,
@@ -6696,6 +8854,7 @@ export {
6696
8854
  skillInputSchema,
6697
8855
  skillToolDefinition,
6698
8856
  skillWriteVerdict,
8857
+ snapshotActionProposal,
6699
8858
  stampToolKinds,
6700
8859
  staticModelCatalog,
6701
8860
  staticSkillProvider,
@@ -6706,8 +8865,18 @@ export {
6706
8865
  toolCallContext,
6707
8866
  traceLlmTurn,
6708
8867
  traceToolExecution,
8868
+ transitionActionProposalClaim,
8869
+ transitionActionProposalDecision,
8870
+ transitionActionProposalLease,
8871
+ transitionActionProposalSettlement,
8872
+ transitionActionProposalSupersession,
6709
8873
  truncateDetailContent,
6710
8874
  unwrapToolStepOutput,
8875
+ validateActionProposalCreation,
8876
+ validateActionProposalDiscoveryIndexBatch,
8877
+ validateActionProposalExpiryBatch,
8878
+ validateActionProposalListQuery,
8879
+ validateActionProposalWorkerClaim,
6711
8880
  validateElicitationAnswer,
6712
8881
  validateElicitationValue,
6713
8882
  validateStructured,
@@ -6720,6 +8889,7 @@ export {
6720
8889
  withSkillTool,
6721
8890
  withToolTimeout,
6722
8891
  withTurnFrames,
8892
+ wrapToolPreflightDenied,
6723
8893
  wrapToolStepOutput,
6724
8894
  writeMemory
6725
8895
  };