@rowan-agent/agent 0.17.0 → 0.18.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
@@ -945,7 +945,7 @@ function createRuntimeCoreTools(input = {}) {
945
945
  import { mkdir as mkdir2, chmod as chmod2, writeFile as writeFile2 } from "fs/promises";
946
946
  import { randomUUID } from "crypto";
947
947
  import { join as join5 } from "path";
948
- import { createModelStream } from "@rowan-agent/models";
948
+ import { createModelStream, isValidToolProgress } from "@rowan-agent/models";
949
949
 
950
950
  // src/utils.ts
951
951
  function createId(prefix) {
@@ -961,6 +961,49 @@ function createTimestamp(date = /* @__PURE__ */ new Date()) {
961
961
  const mm = String(abs % 60).padStart(2, "0");
962
962
  return `${iso}${sign}${hh}:${mm}`;
963
963
  }
964
+ function combineSignals(signalA, signalB) {
965
+ if (!signalA && !signalB) return new AbortController().signal;
966
+ if (!signalA) return signalB;
967
+ if (!signalB) return signalA;
968
+ if (typeof AbortSignal.any === "function") {
969
+ return AbortSignal.any([signalA, signalB]);
970
+ }
971
+ if (signalA.aborted) return signalA;
972
+ if (signalB.aborted) return signalB;
973
+ const controller = new AbortController();
974
+ const onAbort = (ev) => {
975
+ const target = ev.target;
976
+ controller.abort(target.reason);
977
+ };
978
+ signalA.addEventListener("abort", onAbort, { once: true });
979
+ signalB.addEventListener("abort", onAbort, { once: true });
980
+ return controller.signal;
981
+ }
982
+ function withAbort(promise, signal) {
983
+ if (!signal) return promise;
984
+ if (signal.aborted) {
985
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException(typeof signal.reason === "string" ? signal.reason : "Tool execution aborted.", "AbortError");
986
+ return Promise.reject(error);
987
+ }
988
+ return new Promise((resolve9, reject) => {
989
+ const onAbort = () => {
990
+ signal.removeEventListener("abort", onAbort);
991
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException(typeof signal.reason === "string" ? signal.reason : "Tool execution aborted.", "AbortError");
992
+ reject(error);
993
+ };
994
+ signal.addEventListener("abort", onAbort, { once: true });
995
+ promise.then(
996
+ (val) => {
997
+ signal.removeEventListener("abort", onAbort);
998
+ resolve9(val);
999
+ },
1000
+ (err) => {
1001
+ signal.removeEventListener("abort", onAbort);
1002
+ reject(err);
1003
+ }
1004
+ );
1005
+ });
1006
+ }
964
1007
 
965
1008
  // src/loop/state.ts
966
1009
  function snapshotMessage(message) {
@@ -991,12 +1034,18 @@ function contentBlocksToMessageContent(blocks) {
991
1034
  if (block.type === "thinking") {
992
1035
  return { type: "thinking", thinking: block.thinking, ...block.signature ? { signature: block.signature } : {} };
993
1036
  }
994
- let input = block.args;
995
- try {
996
- input = JSON.parse(block.args);
997
- } catch {
1037
+ if (block.type === "image") {
1038
+ return { type: "image", data: block.data, mimeType: block.mimeType };
1039
+ }
1040
+ if (block.type === "tool_call") {
1041
+ let input = block.args;
1042
+ try {
1043
+ input = JSON.parse(block.args);
1044
+ } catch {
1045
+ }
1046
+ return { type: "tool_use", id: block.id, name: block.name, input };
998
1047
  }
999
- return { type: "tool_use", id: block.id, name: block.name, input };
1048
+ return { type: "text", text: JSON.stringify(block) };
1000
1049
  });
1001
1050
  }
1002
1051
 
@@ -1329,7 +1378,42 @@ var LoopGuard = {
1329
1378
  // src/loop/stream-collector.ts
1330
1379
  var MAX_STREAMED_OUTPUT_CHARACTERS = 1024 * 1024;
1331
1380
  async function invokeModel(input) {
1332
- const events = input.config.stream(input.request, { signal: input.config.signal });
1381
+ const callContext = input.callContext ?? {
1382
+ signal: input.config.signal ?? new AbortController().signal,
1383
+ run: {
1384
+ id: input.config.execution.runId,
1385
+ agentId: input.config.execution.agentId,
1386
+ scope: input.config.execution.scope ?? [],
1387
+ cwd: input.config.execution.cwd ?? input.config.context.cwd ?? ""
1388
+ },
1389
+ interact: async (request) => {
1390
+ const handler = input.config.runtime?.interact ?? input.config.interact;
1391
+ if (!handler) {
1392
+ throw new Error("Run interactions are not supported in this runtime.");
1393
+ }
1394
+ return handler(request);
1395
+ },
1396
+ tools: {
1397
+ list: () => {
1398
+ return input.config.context.tools.map((t) => ({
1399
+ name: t.name,
1400
+ description: t.description,
1401
+ parameters: t.parameters,
1402
+ ...t._meta !== void 0 ? { _meta: t._meta } : {}
1403
+ }));
1404
+ },
1405
+ call: async (_name, _args, _options) => {
1406
+ throw new Error("Tool calling is not available in standalone stream collector without callContext.");
1407
+ },
1408
+ report: (update) => {
1409
+ input.config.onToolReport?.({
1410
+ runId: input.config.execution.runId,
1411
+ update
1412
+ });
1413
+ }
1414
+ }
1415
+ };
1416
+ const events = input.config.stream(input.request, callContext);
1333
1417
  const result = await collectStreamResult({
1334
1418
  config: input.config,
1335
1419
  message: input.message,
@@ -1552,16 +1636,17 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1552
1636
  let outputPayload = phaseIn?.state?.payload;
1553
1637
  let nextPhase;
1554
1638
  let outputMessage;
1555
- let settingsProvider;
1556
1639
  return {
1557
- on: (eventType, handler) => {
1558
- assertActive();
1559
- hooks?.on(eventType, handler);
1560
- trackCleanup?.(() => hooks?.off(eventType, handler));
1561
- },
1562
- off: (eventType, handler) => {
1563
- assertActive();
1564
- hooks?.off(eventType, handler);
1640
+ hooks: {
1641
+ on: (eventType, handler) => {
1642
+ assertActive();
1643
+ hooks?.on(eventType, handler);
1644
+ trackCleanup?.(() => hooks?.off(eventType, handler));
1645
+ },
1646
+ off: (eventType, handler) => {
1647
+ assertActive();
1648
+ hooks?.off(eventType, handler);
1649
+ }
1565
1650
  },
1566
1651
  config: {
1567
1652
  get: async (scope) => {
@@ -1611,9 +1696,28 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1611
1696
  return store?.delete(key);
1612
1697
  }
1613
1698
  };
1699
+ },
1700
+ global: () => {
1701
+ assertActive();
1702
+ const store = options?.state?.global();
1703
+ return {
1704
+ get: async (key) => {
1705
+ assertActive();
1706
+ return store?.get(key);
1707
+ },
1708
+ set: async (key, value) => {
1709
+ assertActive();
1710
+ assertJsonValue(value);
1711
+ return store?.set(key, value);
1712
+ },
1713
+ delete: async (key) => {
1714
+ assertActive();
1715
+ return store?.delete(key);
1716
+ }
1717
+ };
1614
1718
  }
1615
1719
  },
1616
- tool: {
1720
+ tools: {
1617
1721
  register: (tool) => {
1618
1722
  assertActive();
1619
1723
  options?.registerTool?.(tool);
@@ -1642,14 +1746,16 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1642
1746
  };
1643
1747
  }
1644
1748
  },
1645
- registerProvider: (config) => {
1646
- assertActive();
1647
- options?.registerProvider?.(config);
1648
- trackCleanup?.(() => options?.unregisterProvider?.(config.id));
1649
- },
1650
- unregisterProvider: (name) => {
1651
- assertActive();
1652
- options?.unregisterProvider?.(name);
1749
+ providers: {
1750
+ register: (config) => {
1751
+ assertActive();
1752
+ options?.registerProvider?.(config);
1753
+ trackCleanup?.(() => options?.unregisterProvider?.(config.id));
1754
+ },
1755
+ unregister: (id) => {
1756
+ assertActive();
1757
+ options?.unregisterProvider?.(id);
1758
+ }
1653
1759
  },
1654
1760
  manifest: options?.manifest,
1655
1761
  utils: {
@@ -1671,7 +1777,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1671
1777
  }, off: () => {
1672
1778
  }, emit: () => {
1673
1779
  }, has: () => false, count: () => 0 },
1674
- phase: {
1780
+ phases: {
1675
1781
  register: async (registration) => {
1676
1782
  assertActive();
1677
1783
  await options?.registerPhase?.(registration);
@@ -1692,19 +1798,37 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1692
1798
  nextPhase = id;
1693
1799
  },
1694
1800
  getNextPhase: () => nextPhase,
1695
- getMessage: () => outputMessage,
1696
- settings: {
1697
- register: (provider) => {
1698
- assertActive();
1699
- if (typeof provider !== "function") {
1700
- throw new Error("Phase Settings registration requires a provider function.");
1801
+ getMessage: () => outputMessage
1802
+ },
1803
+ ui: {
1804
+ contribute: (contribution) => {
1805
+ assertActive();
1806
+ if (!contribution || typeof contribution !== "object") {
1807
+ throw new TypeError("UI contribution must be an object.");
1808
+ }
1809
+ if (contribution.slot !== "settings" && contribution.slot !== "model-picker") {
1810
+ throw new TypeError(`Unsupported UI contribution slot: "${contribution.slot}".`);
1811
+ }
1812
+ if (typeof contribution.id !== "string" || contribution.id.trim() === "") {
1813
+ throw new TypeError("UI contribution requires a non-empty id.");
1814
+ }
1815
+ if (contribution.slot === "settings") {
1816
+ if (typeof contribution.title !== "string") {
1817
+ throw new TypeError("Settings UI contribution requires a title.");
1818
+ }
1819
+ if (!contribution.settings || typeof contribution.settings !== "object") {
1820
+ throw new TypeError("Settings UI contribution requires a settings definition.");
1701
1821
  }
1702
- if (settingsProvider) {
1703
- throw new Error("A Phase may register only one Settings provider.");
1822
+ }
1823
+ if (contribution.slot === "model-picker") {
1824
+ if (typeof contribution.provider !== "string" || contribution.provider.trim() === "") {
1825
+ throw new TypeError("Model-picker UI contribution requires a provider.");
1704
1826
  }
1705
- settingsProvider = provider;
1706
- options?.registerSettings?.(provider);
1707
1827
  }
1828
+ const dispose = options?.contributeUi ? options.contributeUi(contribution) : () => {
1829
+ };
1830
+ trackCleanup?.(dispose);
1831
+ return dispose;
1708
1832
  }
1709
1833
  }
1710
1834
  };
@@ -1713,8 +1837,8 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1713
1837
  // src/harness/phases/interactions.ts
1714
1838
  var RunInteractionCancelledError = class extends Error {
1715
1839
  code = "run_interaction_cancelled";
1716
- constructor() {
1717
- super("Run interaction work was cancelled.");
1840
+ constructor(message = "Run interaction work was cancelled.") {
1841
+ super(message);
1718
1842
  this.name = "RunInteractionCancelledError";
1719
1843
  }
1720
1844
  };
@@ -2185,17 +2309,6 @@ async function loadPhase(targetPath) {
2185
2309
  }
2186
2310
  return phase;
2187
2311
  }
2188
- async function loadPhaseSettings(phase, context) {
2189
- if (!phase.factory) return void 0;
2190
- let provider;
2191
- const api = createExtensionAPI(void 0, {
2192
- registerSettings: (candidate) => {
2193
- provider = candidate;
2194
- }
2195
- });
2196
- await phase.factory(api);
2197
- return provider ? provider(context) : void 0;
2198
- }
2199
2312
  async function loadPhaseSkills(baseDir) {
2200
2313
  const entries = await readdir2(baseDir, { withFileTypes: true });
2201
2314
  const skills = [];
@@ -2637,9 +2750,11 @@ function extractToolResults(transcript) {
2637
2750
  }
2638
2751
  for (const part of msg.content) {
2639
2752
  if (part.type !== "tool_result") continue;
2640
- const result = parseToolResult(part.content);
2641
- if (result) {
2642
- results.push(result);
2753
+ if (typeof part.content === "string") {
2754
+ const result = parseToolResult(part.content);
2755
+ if (result) {
2756
+ results.push(result);
2757
+ }
2643
2758
  }
2644
2759
  }
2645
2760
  }
@@ -3488,10 +3603,10 @@ async function executePhase(ctx) {
3488
3603
  });
3489
3604
  await phase.factory(api);
3490
3605
  return {
3491
- message: api.phase.getMessage() ?? `${phase.name} phase completed.`,
3492
- route: api.phase.getNextPhase() || "stop",
3606
+ message: api.phases.getMessage() ?? `${phase.name} phase completed.`,
3607
+ route: api.phases.getNextPhase() || "stop",
3493
3608
  phase: phase.name,
3494
- payload: api.phase.getPayload()
3609
+ payload: api.phases.getPayload()
3495
3610
  };
3496
3611
  }
3497
3612
  return executePhaseWithModel(ctx);
@@ -3503,7 +3618,8 @@ async function executeToolCall(input) {
3503
3618
  config: input.config,
3504
3619
  toolCall: input.toolCall,
3505
3620
  contentBlocks: input.contentBlocks,
3506
- driver: input.driver
3621
+ driver: input.driver,
3622
+ options: input.options
3507
3623
  });
3508
3624
  } else {
3509
3625
  const toolContext = {
@@ -3516,7 +3632,7 @@ async function executeToolCall(input) {
3516
3632
  toolContext,
3517
3633
  beforeToolCall: input.config.beforeToolCall,
3518
3634
  afterToolCall: input.config.afterToolCall,
3519
- signal: input.config.signal
3635
+ signal: combineSignals(input.config.signal, input.options?.signal)
3520
3636
  });
3521
3637
  }
3522
3638
  return {
@@ -3548,6 +3664,28 @@ async function executeToolCalls(input) {
3548
3664
  }
3549
3665
  function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
3550
3666
  const interaction = createRunInteractionDriver(state, phase.name, config.signal);
3667
+ const executePhaseTool = async (phaseContext, toolCall, options) => {
3668
+ return runTurn(async () => {
3669
+ await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
3670
+ const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
3671
+ const result = await executeToolCall({
3672
+ config: {
3673
+ ...config,
3674
+ context: {
3675
+ ...config.context,
3676
+ tools,
3677
+ skills: phaseContext.skills
3678
+ }
3679
+ },
3680
+ tools,
3681
+ toolCall,
3682
+ driver: interaction,
3683
+ options
3684
+ });
3685
+ await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
3686
+ return result;
3687
+ });
3688
+ };
3551
3689
  return {
3552
3690
  interaction,
3553
3691
  messages: messageManager,
@@ -3591,6 +3729,51 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3591
3729
  }));
3592
3730
  }
3593
3731
  }
3732
+ const scope = config.execution.scope ?? [];
3733
+ const cwd = config.execution.cwd ?? config.context.cwd ?? "";
3734
+ const callContext = {
3735
+ signal: config.signal ?? new AbortController().signal,
3736
+ run: {
3737
+ id: config.execution.runId,
3738
+ agentId: config.execution.agentId,
3739
+ scope,
3740
+ cwd
3741
+ },
3742
+ interact: async (interactionReq) => {
3743
+ const handler = config.runtime?.interact ?? config.interact;
3744
+ if (!handler) {
3745
+ throw new Error("Run interactions are not supported in this runtime.");
3746
+ }
3747
+ return handler(interactionReq);
3748
+ },
3749
+ tools: {
3750
+ list: () => {
3751
+ const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
3752
+ return tools.map((t) => ({
3753
+ name: t.name,
3754
+ description: t.description,
3755
+ parameters: t.parameters,
3756
+ ...t._meta !== void 0 ? { _meta: t._meta } : {}
3757
+ }));
3758
+ },
3759
+ call: async (name, args, options2) => {
3760
+ const toolCallId = createId("call");
3761
+ const toolCall = {
3762
+ id: toolCallId,
3763
+ name,
3764
+ args
3765
+ };
3766
+ const res = await executePhaseTool(phaseContext, toolCall, options2);
3767
+ return res.ok ? { ok: true, content: res.content } : { ok: false, error: res.error ?? "Tool execution failed", ...res.content !== void 0 ? { content: res.content } : {} };
3768
+ },
3769
+ report: (update) => {
3770
+ config.onToolReport?.({
3771
+ runId: config.execution.runId,
3772
+ update
3773
+ });
3774
+ }
3775
+ }
3776
+ };
3594
3777
  const result = await runTurn(
3595
3778
  () => withRetry(
3596
3779
  () => invokeModel({
@@ -3600,7 +3783,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3600
3783
  phaseId: phase.name,
3601
3784
  output: options.output,
3602
3785
  onThinkingDelta: config.onThinkingDelta,
3603
- onToolCallDelta: config.onToolCallDelta
3786
+ onToolCallDelta: config.onToolCallDelta,
3787
+ callContext
3604
3788
  }),
3605
3789
  {
3606
3790
  signal: config.signal,
@@ -3623,27 +3807,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3623
3807
  async reportStatus(status) {
3624
3808
  await config.onPhaseStatus?.(phase.name, status);
3625
3809
  },
3626
- async executeTool(phaseContext, toolCall) {
3627
- return runTurn(async () => {
3628
- await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
3629
- const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
3630
- const result = await executeToolCall({
3631
- config: {
3632
- ...config,
3633
- context: {
3634
- ...config.context,
3635
- tools,
3636
- skills: phaseContext.skills
3637
- }
3638
- },
3639
- tools,
3640
- toolCall,
3641
- driver: interaction
3642
- });
3643
- await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
3644
- return result;
3645
- });
3646
- },
3810
+ executeTool: executePhaseTool,
3647
3811
  async executeTools(phaseContext, toolCalls, contentBlocks) {
3648
3812
  return runTurn(async () => {
3649
3813
  const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
@@ -3913,6 +4077,7 @@ async function executeOnce(input) {
3913
4077
  onModelTranscript: input.onModelTranscript,
3914
4078
  onPhaseEntered: input.onPhaseEntered,
3915
4079
  onPhaseStatus: input.onPhaseStatus,
4080
+ onToolReport: input.onToolReport,
3916
4081
  runtime: input.runtime,
3917
4082
  waitForInput: async (nextState, request) => {
3918
4083
  if (!nextState || !request) throw new Error("Input boundary did not include execution state.");
@@ -4003,6 +4168,46 @@ function isContinuation(value) {
4003
4168
  return value.previousResults.every((result) => isRecord2(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
4004
4169
  }
4005
4170
 
4171
+ // src/runtime/tool-call-merge.ts
4172
+ function isValidPresentationOutput(output) {
4173
+ if (typeof output !== "object" || output === null || Array.isArray(output)) return false;
4174
+ const o = output;
4175
+ if (o.title !== void 0 && typeof o.title !== "string") return false;
4176
+ if (o.locations !== void 0) {
4177
+ if (!Array.isArray(o.locations)) return false;
4178
+ for (const loc of o.locations) {
4179
+ if (typeof loc !== "object" || loc === null || typeof loc.path !== "string") return false;
4180
+ if (loc.line !== void 0 && typeof loc.line !== "number") return false;
4181
+ }
4182
+ }
4183
+ if (o.content !== void 0) {
4184
+ if (!Array.isArray(o.content)) return false;
4185
+ for (const c of o.content) {
4186
+ if (typeof c !== "object" || c === null || typeof c.type !== "string") return false;
4187
+ }
4188
+ }
4189
+ if (o._meta !== void 0) {
4190
+ if (typeof o._meta !== "object" || o._meta === null || Array.isArray(o._meta)) return false;
4191
+ }
4192
+ return true;
4193
+ }
4194
+ function mergeToolCall(target, patch) {
4195
+ if (patch.title !== void 0) target.title = patch.title;
4196
+ if (patch.kind !== void 0) target.kind = patch.kind;
4197
+ if (patch.status !== void 0) target.status = patch.status;
4198
+ if (patch.locations !== void 0) target.locations = structuredClone(patch.locations);
4199
+ if (patch.content !== void 0) target.content = structuredClone(patch.content);
4200
+ if (patch.rawInput !== void 0) target.rawInput = structuredClone(patch.rawInput);
4201
+ if (patch.rawOutput !== void 0) target.rawOutput = structuredClone(patch.rawOutput);
4202
+ if (patch._meta !== void 0) {
4203
+ target._meta = {
4204
+ ...target._meta ?? {},
4205
+ ...structuredClone(patch._meta)
4206
+ };
4207
+ }
4208
+ return target;
4209
+ }
4210
+
4006
4211
  // src/runtime/contracts.ts
4007
4212
  var THINKING_LEVELS = [
4008
4213
  "off",
@@ -4013,23 +4218,8 @@ var THINKING_LEVELS = [
4013
4218
  "xhigh",
4014
4219
  "max"
4015
4220
  ];
4016
- function thinkingLevelFromMetadata(metadata) {
4017
- const record = isRecord3(metadata) ? metadata : void 0;
4018
- const mori = record?.mori;
4019
- if (!isRecord3(mori)) return void 0;
4020
- const level = mori.thinkingLevel;
4021
- return typeof level === "string" && THINKING_LEVELS.includes(level) ? level : void 0;
4022
- }
4023
- function thinkingLevelFromMessages(messages) {
4024
- for (let index = messages.length - 1; index >= 0; index -= 1) {
4025
- const message = messages[index];
4026
- if (message?.role !== "user") continue;
4027
- const metadata = message.metadata;
4028
- const kind = isRecord3(metadata) && typeof metadata.kind === "string" ? metadata.kind : void 0;
4029
- if (kind === "phase_prompt" || kind === "phase_input") continue;
4030
- return thinkingLevelFromMetadata(metadata);
4031
- }
4032
- return void 0;
4221
+ function thinkingLevelFromUserInput(input) {
4222
+ return typeof input === "string" ? void 0 : input.thinkingLevel;
4033
4223
  }
4034
4224
  var METADATA_LIMIT = 64 * 1024;
4035
4225
  var IDENTITY_LIMIT = 256;
@@ -4085,8 +4275,13 @@ function isAssistantContent(value) {
4085
4275
  }
4086
4276
  function normalizeUserInput(input) {
4087
4277
  const normalized = typeof input === "string" ? { content: input } : input;
4088
- if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
4278
+ if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata", "thinkingLevel"]) || !isUserContent(normalized.content)) {
4279
+ throw new TypeError("input must contain only valid UserContent");
4280
+ }
4089
4281
  if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
4282
+ if (normalized.thinkingLevel !== void 0 && !THINKING_LEVELS.includes(normalized.thinkingLevel)) {
4283
+ throw new TypeError("input.thinkingLevel must be a valid ThinkingLevel");
4284
+ }
4090
4285
  assertJsonValue(normalized, "input");
4091
4286
  return normalized;
4092
4287
  }
@@ -4094,9 +4289,16 @@ function isAssistantMessage(value) {
4094
4289
  return isRecord3(value) && hasOnlyKeys(value, ["id", "agentId", "runId", "messageRevision", "role", "content", "metadata", "sequenceWithinRun", "createdAt", "interrupted"]) && typeof value.id === "string" && typeof value.agentId === "string" && typeof value.runId === "string" && value.role === "assistant" && (value.messageRevision === void 0 || Number.isInteger(value.messageRevision) && value.messageRevision >= 0) && Number.isInteger(value.sequenceWithinRun) && value.sequenceWithinRun >= 0 && typeof value.createdAt === "string" && isAssistantContent(value.content) && (value.metadata === void 0 || isMetadata(value.metadata)) && (value.interrupted === void 0 || typeof value.interrupted === "boolean");
4095
4290
  }
4096
4291
  function isToolResult(value) {
4097
- if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
4292
+ if (!isRecord3(value)) return false;
4293
+ if (value.structuredContent !== void 0 && !isJsonValue(value.structuredContent)) return false;
4294
+ if (Array.isArray(value.content)) {
4295
+ if (value.isError !== void 0 && typeof value.isError !== "boolean") return false;
4296
+ return true;
4297
+ }
4298
+ if (!isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
4299
+ const keys = Object.keys(value).filter((k) => k !== "structuredContent").sort();
4098
4300
  const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
4099
- if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
4301
+ if (JSON.stringify(keys) !== JSON.stringify(expected)) return false;
4100
4302
  return value.ok || typeof value.error === "string";
4101
4303
  }
4102
4304
  function assertAgentConfiguration(config) {
@@ -4269,17 +4471,20 @@ function encodeIdempotencyScope(storeIncarnation, scope) {
4269
4471
  if (typeof storeIncarnation !== "string" || storeIncarnation.length === 0) throw new TypeError("storeIncarnation must be non-empty");
4270
4472
  return canonicalJson([storeIncarnation, ...scope]);
4271
4473
  }
4272
- function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken, entryPhases) {
4474
+ function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken, entryPhases, scope, cwd) {
4273
4475
  const normalized = normalizeUserInput(input);
4274
4476
  if (metadata !== void 0) assertUtf8ByteLimit(canonicalJson(metadata), METADATA_JSON_BYTES, "run.metadata");
4275
4477
  if (phasePayload !== void 0) assertUtf8ByteLimit(canonicalJson(phasePayload), MESSAGE_CONTENT_JSON_BYTES, "run.phasePayload");
4276
4478
  if (entryPhases !== void 0) assertUtf8ByteLimit(canonicalJson(entryPhases), MESSAGE_CONTENT_JSON_BYTES, "run.entryPhases");
4479
+ if (cwd !== void 0 && typeof cwd !== "string") throw new TypeError("run.cwd must be a string");
4277
4480
  return canonicalJson({
4278
4481
  input: normalized,
4279
4482
  ...metadata === void 0 ? {} : { metadata },
4280
4483
  ...phasePayload === void 0 ? {} : { phasePayload },
4281
4484
  ...entryPhases === void 0 ? {} : { entryPhases },
4282
- ...pinnedConfigToken === void 0 ? {} : { pinnedConfigToken }
4485
+ ...pinnedConfigToken === void 0 ? {} : { pinnedConfigToken },
4486
+ ...scope === void 0 ? {} : { scope },
4487
+ ...cwd === void 0 ? {} : { cwd }
4283
4488
  });
4284
4489
  }
4285
4490
 
@@ -4821,7 +5026,8 @@ function projectToolMessage(message, toolInteractionTexts) {
4821
5026
  id: message.id,
4822
5027
  role: message.role,
4823
5028
  content: message.content.map((part) => {
4824
- const rawResult = jsonText(part.result.content);
5029
+ const isError = "isError" in part.result ? Boolean(part.result.isError) : !part.result.ok;
5030
+ const rawResult = Array.isArray(part.result.content) ? part.result.content.map((block) => block?.type === "text" ? block.text : JSON.stringify(block)).join("\n\n") : jsonText(part.result.content);
4825
5031
  const interactionTexts = toolInteractionTexts.get(String(part.toolCallId));
4826
5032
  let content = rawResult;
4827
5033
  if (interactionTexts && interactionTexts.length > 0) {
@@ -4834,7 +5040,7 @@ ${rawResult}`;
4834
5040
  type: "tool_result",
4835
5041
  toolUseId: part.providerToolCallId ?? String(part.toolCallId),
4836
5042
  content,
4837
- ...part.result.ok ? {} : { isError: true }
5043
+ ...isError ? { isError: true } : {}
4838
5044
  };
4839
5045
  }),
4840
5046
  createdAt: message.createdAt,
@@ -4866,12 +5072,16 @@ function projectAssistantContent(content) {
4866
5072
  });
4867
5073
  }
4868
5074
  function projectToolContent(content) {
4869
- return content.map((part) => ({
4870
- type: "tool_result",
4871
- toolUseId: part.providerToolCallId ?? part.toolCallId,
4872
- content: jsonText(part.result.content),
4873
- ...part.result.ok ? {} : { isError: true }
4874
- }));
5075
+ return content.map((part) => {
5076
+ const isError = "isError" in part.result ? Boolean(part.result.isError) : !part.result.ok;
5077
+ const textContent = Array.isArray(part.result.content) ? part.result.content.map((block) => block?.type === "text" ? block.text : JSON.stringify(block)).join("\n\n") : jsonText(part.result.content);
5078
+ return {
5079
+ type: "tool_result",
5080
+ toolUseId: part.providerToolCallId ?? part.toolCallId,
5081
+ content: textContent,
5082
+ ...isError ? { isError: true } : {}
5083
+ };
5084
+ });
4875
5085
  }
4876
5086
  function inertRunInteraction(signal) {
4877
5087
  const controller = signal ? void 0 : new AbortController();
@@ -4900,6 +5110,7 @@ function projectTool(tool, agentId, runId) {
4900
5110
  parameters: tool.parameters,
4901
5111
  ...tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {},
4902
5112
  ...tool.promptGuidelines ? { promptGuidelines: [...tool.promptGuidelines] } : {},
5113
+ ...tool._meta !== void 0 ? { _meta: tool._meta } : {},
4903
5114
  execute: async (args, context, signal) => {
4904
5115
  const result = await tool.execute(args, {
4905
5116
  agentId,
@@ -4909,7 +5120,8 @@ function projectTool(tool, agentId, runId) {
4909
5120
  reportProgress: () => void 0,
4910
5121
  interaction: context.interaction ?? inertRunInteraction(signal)
4911
5122
  }, signal ?? new AbortController().signal);
4912
- return { toolCallId: context.toolCallId, toolName: tool.name, ...result };
5123
+ const ok = "ok" in result && typeof result.ok === "boolean" ? result.ok : !result.isError;
5124
+ return { toolCallId: context.toolCallId, toolName: tool.name, ok, ...result };
4913
5125
  }
4914
5126
  };
4915
5127
  }
@@ -5004,20 +5216,38 @@ function resolveDefinitionContext(snapshot, assembled) {
5004
5216
  systemPrompt: [snapshot.definition.prompt, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
5005
5217
  tools,
5006
5218
  skills,
5007
- phases: { phases, entryPhaseId }
5219
+ phases: { phases, entryPhaseId },
5220
+ ...snapshot.cwd ? { cwd: snapshot.cwd } : {}
5008
5221
  };
5009
5222
  }
5010
5223
  function toLoopResult(result, toolCallId, toolName) {
5224
+ const ok = "ok" in result && typeof result.ok === "boolean" ? result.ok : !result.isError;
5225
+ const error = "error" in result && typeof result.error === "string" ? result.error : void 0;
5011
5226
  return {
5012
5227
  toolCallId,
5013
5228
  toolName,
5014
- ok: result.ok,
5229
+ ok,
5015
5230
  content: result.content,
5016
- ...!result.ok ? { error: result.error } : {}
5231
+ ...!ok && error ? { error } : {},
5232
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {},
5233
+ ...result.isError !== void 0 ? { isError: result.isError } : {}
5017
5234
  };
5018
5235
  }
5019
5236
  function fromLoopResult(result) {
5020
- return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
5237
+ if (Array.isArray(result.content)) {
5238
+ return {
5239
+ content: result.content,
5240
+ ...result.ok !== void 0 ? { ok: result.ok } : {},
5241
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {},
5242
+ ...result.isError !== void 0 ? { isError: result.isError } : !result.ok ? { isError: true } : {},
5243
+ ...result.error !== void 0 ? { error: result.error } : {}
5244
+ };
5245
+ }
5246
+ return {
5247
+ ...result.ok ? { ok: true } : { ok: false, error: result.error ?? "Extension hook rejected the Tool result." },
5248
+ content: result.content,
5249
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
5250
+ };
5021
5251
  }
5022
5252
 
5023
5253
  // src/runtime/transient-run-events.ts
@@ -5053,6 +5283,29 @@ var TransientRunEventSubscription = class {
5053
5283
  previous?.kind === "tool_call_delta" && event.kind === "tool_call_delta" && previous.executionId === event.executionId && previous.providerToolCallId === event.providerToolCallId
5054
5284
  ) {
5055
5285
  this.queue[this.queue.length - 1] = event;
5286
+ } else if (event.kind === "tool_state_changed") {
5287
+ const toolCallId = event.toolCall.toolCallId;
5288
+ let index = -1;
5289
+ for (let i = this.queue.length - 1; i >= 0; i -= 1) {
5290
+ const existing = this.queue[i];
5291
+ if (existing && existing.kind === "tool_state_changed" && existing.runId === event.runId && existing.toolCall.toolCallId === toolCallId) {
5292
+ index = i;
5293
+ break;
5294
+ }
5295
+ }
5296
+ if (index !== -1) {
5297
+ const existing = this.queue[index];
5298
+ const mergedCall = { ...existing.toolCall };
5299
+ mergeToolCall(mergedCall, event.toolCall);
5300
+ this.queue[index] = {
5301
+ ...existing,
5302
+ transition: { from: existing.transition.from, to: event.transition.to },
5303
+ toolCall: mergedCall
5304
+ };
5305
+ } else {
5306
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
5307
+ this.queue.push(event);
5308
+ }
5056
5309
  } else {
5057
5310
  if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
5058
5311
  this.queue.push(event);
@@ -5068,7 +5321,7 @@ var TransientRunEventSubscription = class {
5068
5321
  else {
5069
5322
  for (let index = this.queue.length - 1; index >= 0; index -= 1) {
5070
5323
  const event = this.queue[index];
5071
- if (event?.executionId === executionId && !(preserveCompletedPhaseStatus && event.kind === "phase_status" && event.status.state === "completed")) {
5324
+ if (event && "executionId" in event && event.executionId === executionId && !(preserveCompletedPhaseStatus && event.kind === "phase_status" && event.status.state === "completed")) {
5072
5325
  this.queue.splice(index, 1);
5073
5326
  }
5074
5327
  }
@@ -5085,7 +5338,7 @@ var TransientRunEventSubscription = class {
5085
5338
  clearTool(toolCallId) {
5086
5339
  for (let index = this.queue.length - 1; index >= 0; index -= 1) {
5087
5340
  const event = this.queue[index];
5088
- if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
5341
+ if (event?.kind === "tool_state_changed" && event.toolCall.toolCallId === toolCallId) this.queue.splice(index, 1);
5089
5342
  }
5090
5343
  this.notify();
5091
5344
  }
@@ -5229,6 +5482,7 @@ var HooksManager = class {
5229
5482
  var InMemoryExtensionHost = class {
5230
5483
  configs = /* @__PURE__ */ new Map();
5231
5484
  agentState = /* @__PURE__ */ new Map();
5485
+ globalState = /* @__PURE__ */ new Map();
5232
5486
  listeners = /* @__PURE__ */ new Set();
5233
5487
  constructor(options = {}) {
5234
5488
  if (options.configs) {
@@ -5255,8 +5509,18 @@ var InMemoryExtensionHost = class {
5255
5509
  this.agentState.set(agentId, agentMap);
5256
5510
  }
5257
5511
  }
5512
+ if (options.globalState) {
5513
+ for (const [extId, stateMap] of Object.entries(options.globalState)) {
5514
+ const map = /* @__PURE__ */ new Map();
5515
+ for (const [key, val] of Object.entries(stateMap)) {
5516
+ assertJsonValue(val);
5517
+ map.set(key, structuredClone(val));
5518
+ }
5519
+ this.globalState.set(extId, map);
5520
+ }
5521
+ }
5258
5522
  }
5259
- setConfig(extensionId, config, scope = { kind: "global" }) {
5523
+ setConfig(extensionId, config, scope = []) {
5260
5524
  const scopeKey = scopeToKey(scope);
5261
5525
  if (!this.configs.has(scopeKey)) {
5262
5526
  this.configs.set(scopeKey, /* @__PURE__ */ new Map());
@@ -5270,19 +5534,18 @@ var InMemoryExtensionHost = class {
5270
5534
  }
5271
5535
  this.notifyConfigChanged(extensionId, scope);
5272
5536
  }
5273
- getConfig(extensionId, scope = { kind: "global" }) {
5537
+ getConfig(extensionId, scope = []) {
5538
+ let merged = null;
5274
5539
  const globalConfig = this.configs.get("global")?.get(extensionId);
5275
- let merged = globalConfig ? { ...globalConfig } : null;
5276
- if (scope.kind === "team" || scope.kind === "project") {
5277
- const teamConfig = this.configs.get(`team:${scope.teamId}`)?.get(extensionId);
5278
- if (teamConfig) {
5279
- merged = { ...merged ?? {}, ...teamConfig };
5280
- }
5540
+ if (globalConfig) {
5541
+ merged = { ...globalConfig };
5281
5542
  }
5282
- if (scope.kind === "project") {
5283
- const projectConfig = this.configs.get(`project:${scope.teamId}:${scope.projectId}`)?.get(extensionId);
5284
- if (projectConfig) {
5285
- merged = { ...merged ?? {}, ...projectConfig };
5543
+ for (let i = 1; i <= scope.length; i++) {
5544
+ const subScope = scope.slice(0, i);
5545
+ const key = scopeToKey(subScope);
5546
+ const layerConfig = this.configs.get(key)?.get(extensionId);
5547
+ if (layerConfig) {
5548
+ merged = { ...merged ?? {}, ...layerConfig };
5286
5549
  }
5287
5550
  }
5288
5551
  return merged ? structuredClone(merged) : null;
@@ -5291,7 +5554,7 @@ var InMemoryExtensionHost = class {
5291
5554
  this.listeners.add(listener);
5292
5555
  return () => this.listeners.delete(listener);
5293
5556
  }
5294
- notifyConfigChanged(extensionId, scope = { kind: "global" }) {
5557
+ notifyConfigChanged(extensionId, scope = []) {
5295
5558
  for (const listener of this.listeners) {
5296
5559
  try {
5297
5560
  listener(extensionId, scope);
@@ -5320,29 +5583,26 @@ var InMemoryExtensionHost = class {
5320
5583
  deleteAgentState(extensionId, agentId, key) {
5321
5584
  this.agentState.get(agentId)?.get(extensionId)?.delete(key);
5322
5585
  }
5323
- };
5324
- function scopeToKey(scope) {
5325
- switch (scope.kind) {
5326
- case "global":
5327
- return "global";
5328
- case "team":
5329
- return `team:${scope.teamId}`;
5330
- case "project":
5331
- return `project:${scope.teamId}:${scope.projectId}`;
5586
+ getGlobalState(extensionId, key) {
5587
+ const val = this.globalState.get(extensionId)?.get(key);
5588
+ return val !== void 0 ? structuredClone(val) : void 0;
5332
5589
  }
5333
- }
5334
- function resolveScopeFromMetadata(metadata) {
5335
- if (metadata) {
5336
- const teamId = typeof metadata.teamId === "string" ? metadata.teamId : void 0;
5337
- const projectId = typeof metadata.projectId === "string" ? metadata.projectId : void 0;
5338
- if (teamId && projectId) {
5339
- return { kind: "project", teamId, projectId };
5340
- }
5341
- if (teamId) {
5342
- return { kind: "team", teamId };
5590
+ setGlobalState(extensionId, key, value) {
5591
+ assertJsonValue(value);
5592
+ let extMap = this.globalState.get(extensionId);
5593
+ if (!extMap) {
5594
+ extMap = /* @__PURE__ */ new Map();
5595
+ this.globalState.set(extensionId, extMap);
5343
5596
  }
5597
+ extMap.set(key, structuredClone(value));
5598
+ }
5599
+ deleteGlobalState(extensionId, key) {
5600
+ this.globalState.get(extensionId)?.delete(key);
5344
5601
  }
5345
- return { kind: "global" };
5602
+ };
5603
+ function scopeToKey(scope) {
5604
+ if (scope.length === 0) return "global";
5605
+ return scope.map((layer) => `${layer.kind}:${layer.id}`).join("/");
5346
5606
  }
5347
5607
 
5348
5608
  // src/extensions/runner.ts
@@ -5456,8 +5716,9 @@ async function execCommand(command, args, cwd, options) {
5456
5716
  });
5457
5717
  }
5458
5718
  function applyProviderRegistration(config) {
5459
- if (config.streamSimple) {
5460
- registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
5719
+ unregisterProviderModels(config.id);
5720
+ if (config.stream) {
5721
+ registerApiProvider({ protocol: config.protocol, stream: config.stream });
5461
5722
  }
5462
5723
  for (const modelConfig of config.models) {
5463
5724
  registerModel({
@@ -5467,12 +5728,13 @@ function applyProviderRegistration(config) {
5467
5728
  provider: config.id,
5468
5729
  baseUrl: config.baseUrl,
5469
5730
  apiKey: config.apiKey,
5470
- reasoning: modelConfig.reasoning,
5731
+ reasoning: modelConfig.reasoning ?? false,
5471
5732
  ...modelConfig.thinkingLevel !== void 0 ? { thinkingLevel: modelConfig.thinkingLevel } : {},
5472
- input: modelConfig.input,
5473
- cost: modelConfig.cost,
5474
- contextWindow: modelConfig.contextWindow,
5475
- maxTokens: modelConfig.maxTokens,
5733
+ ...modelConfig.thinkingLevels !== void 0 ? { thinkingLevels: modelConfig.thinkingLevels } : {},
5734
+ input: modelConfig.input ?? ["text"],
5735
+ cost: modelConfig.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
5736
+ contextWindow: modelConfig.contextWindow ?? 128e3,
5737
+ maxTokens: modelConfig.maxTokens ?? 4096,
5476
5738
  ...config.headers || modelConfig.headers ? { headers: { ...config.headers, ...modelConfig.headers } } : {},
5477
5739
  ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
5478
5740
  ...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
@@ -5508,6 +5770,8 @@ var ExtensionRunner = class {
5508
5770
  errorListeners = /* @__PURE__ */ new Set();
5509
5771
  // Capability change listeners
5510
5772
  capabilityListeners = /* @__PURE__ */ new Set();
5773
+ // UI contribution change listeners
5774
+ uiContributionListeners = /* @__PURE__ */ new Set();
5511
5775
  /** Current agent context — set by the agent before each phase */
5512
5776
  currentContext;
5513
5777
  constructor(options) {
@@ -5611,6 +5875,7 @@ var ExtensionRunner = class {
5611
5875
  tools: /* @__PURE__ */ new Map(),
5612
5876
  phases: /* @__PURE__ */ new Set(),
5613
5877
  capabilities: /* @__PURE__ */ new Map(),
5878
+ uiContributions: /* @__PURE__ */ new Map(),
5614
5879
  cleanup: [],
5615
5880
  runtime: createExtensionRuntime()
5616
5881
  };
@@ -5623,6 +5888,9 @@ var ExtensionRunner = class {
5623
5888
  if (extension.capabilities.size > 0) {
5624
5889
  this.notifyCapabilitiesChanged();
5625
5890
  }
5891
+ if (extension.uiContributions.size > 0) {
5892
+ this.notifyUiContributionsChanged();
5893
+ }
5626
5894
  } catch (error) {
5627
5895
  await this.rollbackExtension(extension);
5628
5896
  const message = error instanceof Error ? error.message : String(error);
@@ -5751,6 +6019,27 @@ var ExtensionRunner = class {
5751
6019
  this.capabilityListeners.add(listener);
5752
6020
  return () => this.capabilityListeners.delete(listener);
5753
6021
  }
6022
+ /** Get all contributed UI elements across all extensions. */
6023
+ getUiContributions() {
6024
+ const result = [];
6025
+ for (const ext of this.extensions) {
6026
+ for (const contrib of ext.uiContributions.values()) {
6027
+ result.push(contrib);
6028
+ }
6029
+ }
6030
+ return Object.freeze(result);
6031
+ }
6032
+ /**
6033
+ * Subscribe to UI contribution changes (contributions added, removed, or extensions disposed).
6034
+ * Returns an unsubscribe function.
6035
+ */
6036
+ onUiContributionsChanged(listener) {
6037
+ this.uiContributionListeners.add(listener);
6038
+ return () => this.uiContributionListeners.delete(listener);
6039
+ }
6040
+ triggerUiAction(event) {
6041
+ this.events.emit("ui.action", event);
6042
+ }
5754
6043
  // ---------------------------------------------------------------------------
5755
6044
  // Phase management
5756
6045
  // ---------------------------------------------------------------------------
@@ -5868,7 +6157,7 @@ var ExtensionRunner = class {
5868
6157
  }
5869
6158
  }
5870
6159
  async emitBeforeToolCall(tool, args, context) {
5871
- const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
6160
+ const scope = context?.scope ?? [];
5872
6161
  const turn = context?.turn ?? {};
5873
6162
  const result = await this.emitHook("before_tool_call", {
5874
6163
  type: "before_tool_call",
@@ -5885,7 +6174,7 @@ var ExtensionRunner = class {
5885
6174
  return result ?? { allow: true };
5886
6175
  }
5887
6176
  async emitAfterToolCall(tool, result, context) {
5888
- const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
6177
+ const scope = context?.scope ?? [];
5889
6178
  const turn = context?.turn ?? {};
5890
6179
  const hookResult = await this.emitHook("after_tool_call", {
5891
6180
  type: "after_tool_call",
@@ -5965,12 +6254,13 @@ var ExtensionRunner = class {
5965
6254
  registerTool: (tool) => this.registerTool(extension, tool),
5966
6255
  unregisterTool: (toolName) => this.unregisterTool(extension, toolName),
5967
6256
  contributeCapability: (contribution) => this.contributeCapability(extension, contribution),
6257
+ contributeUi: (contribution) => this.contributeUi(extension, contribution),
5968
6258
  context: extContext,
5969
6259
  manifest,
5970
6260
  trackCleanup: (cleanup) => extension.cleanup.push(cleanup),
5971
6261
  config: {
5972
6262
  get: async (scope) => {
5973
- const effectiveScope = scope ?? { kind: "global" };
6263
+ const effectiveScope = scope ?? [];
5974
6264
  const res = await runner.host.getConfig(extension.id, effectiveScope);
5975
6265
  return res ?? null;
5976
6266
  },
@@ -5999,6 +6289,11 @@ var ExtensionRunner = class {
5999
6289
  get: async (key) => runner.host.getAgentState(extension.id, agentId, key),
6000
6290
  set: async (key, value) => runner.host.setAgentState(extension.id, agentId, key, value),
6001
6291
  delete: async (key) => runner.host.deleteAgentState(extension.id, agentId, key)
6292
+ }),
6293
+ global: () => ({
6294
+ get: async (key) => runner.host.getGlobalState(extension.id, key),
6295
+ set: async (key, value) => runner.host.setGlobalState(extension.id, key, value),
6296
+ delete: async (key) => runner.host.deleteGlobalState(extension.id, key)
6002
6297
  })
6003
6298
  }
6004
6299
  }, extension.runtime, this.events);
@@ -6069,6 +6364,44 @@ var ExtensionRunner = class {
6069
6364
  }
6070
6365
  }
6071
6366
  }
6367
+ contributeUi(extension, contribution) {
6368
+ const key = `${contribution.slot}:${contribution.id}`;
6369
+ const entry = {
6370
+ ...contribution
6371
+ };
6372
+ extension.uiContributions.set(key, entry);
6373
+ if (this.extensions.includes(extension)) {
6374
+ this.notifyUiContributionsChanged();
6375
+ }
6376
+ let disposed = false;
6377
+ const dispose = () => {
6378
+ if (disposed) return;
6379
+ disposed = true;
6380
+ if (extension.uiContributions.get(key) === entry) {
6381
+ extension.uiContributions.delete(key);
6382
+ if (this.extensions.includes(extension)) {
6383
+ this.notifyUiContributionsChanged();
6384
+ }
6385
+ }
6386
+ };
6387
+ extension.cleanup.push(dispose);
6388
+ return dispose;
6389
+ }
6390
+ notifyUiContributionsChanged() {
6391
+ const contributions = this.getUiContributions();
6392
+ for (const listener of this.uiContributionListeners) {
6393
+ try {
6394
+ listener(contributions);
6395
+ } catch (error) {
6396
+ this.emitError({
6397
+ extensionPath: "<runtime>",
6398
+ event: "ui_contributions_changed",
6399
+ error: error instanceof Error ? error.message : String(error),
6400
+ stack: error instanceof Error ? error.stack : void 0
6401
+ });
6402
+ }
6403
+ }
6404
+ }
6072
6405
  async registerPhase(extension, registration) {
6073
6406
  if (typeof registration === "string") {
6074
6407
  if (registration.length === 0) {
@@ -6157,6 +6490,8 @@ var ExtensionRunner = class {
6157
6490
  for (const name of extension.phases) this.phases.delete(name);
6158
6491
  const hadCapabilities = extension.capabilities.size > 0;
6159
6492
  extension.capabilities.clear();
6493
+ const hadUiContributions = extension.uiContributions.size > 0;
6494
+ extension.uiContributions.clear();
6160
6495
  for (const cleanup of [...extension.cleanup].reverse()) {
6161
6496
  await Promise.resolve().then(() => cleanup()).catch(() => void 0);
6162
6497
  }
@@ -6167,6 +6502,9 @@ var ExtensionRunner = class {
6167
6502
  if (hadCapabilities) {
6168
6503
  this.notifyCapabilitiesChanged();
6169
6504
  }
6505
+ if (hadUiContributions) {
6506
+ this.notifyUiContributionsChanged();
6507
+ }
6170
6508
  }
6171
6509
  };
6172
6510
  function createExtensionRunner(options) {
@@ -6859,11 +7197,11 @@ function adaptExtensionTool(input) {
6859
7197
  parameters: definition.parameters,
6860
7198
  ...definition.promptSnippet ? { promptSnippet: definition.promptSnippet } : {},
6861
7199
  ...definition.promptGuidelines ? { promptGuidelines: definition.promptGuidelines } : {},
6862
- execute: async (args, context, signal) => {
6863
- const result = await definition.execute(args, context, signal);
6864
- const content = JSON.parse(JSON.stringify(result.content));
6865
- return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
6866
- }
7200
+ ...definition.kind ? { kind: definition.kind } : {},
7201
+ ...definition.annotations ? { annotations: definition.annotations } : {},
7202
+ ...definition._meta !== void 0 ? { _meta: definition._meta } : {},
7203
+ ...definition.present ? { present: definition.present } : {},
7204
+ execute: async (args, context, signal) => definition.execute(args, context, signal)
6867
7205
  };
6868
7206
  }
6869
7207
 
@@ -6931,12 +7269,53 @@ async function spillLargeToolResult(result, archiveDir, toolName) {
6931
7269
  await writeFile2(path, serialized, { encoding: "utf8", mode: 384 });
6932
7270
  await chmod2(path, 384).catch(() => void 0);
6933
7271
  const preview = new TextDecoder().decode(bytes.subarray(0, MAX_INLINE_TOOL_RESULT_BYTES));
6934
- return {
6935
- ...result,
6936
- content: `${preview}
7272
+ const truncatedText = `${preview}
6937
7273
  [truncated]
6938
7274
 
6939
- [Full result: ${path}, offset 0]`
7275
+ [Full result: ${path}, offset 0]`;
7276
+ if (Array.isArray(result.content)) {
7277
+ return {
7278
+ ...result,
7279
+ content: [{ type: "text", text: truncatedText }]
7280
+ };
7281
+ }
7282
+ if (result.ok) {
7283
+ return {
7284
+ ok: true,
7285
+ content: truncatedText,
7286
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
7287
+ };
7288
+ }
7289
+ return {
7290
+ ok: false,
7291
+ content: truncatedText,
7292
+ error: result.error ?? "Tool execution failed.",
7293
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
7294
+ };
7295
+ }
7296
+ async function presentToolCall(tool, call) {
7297
+ if (!tool.present) return void 0;
7298
+ try {
7299
+ const res = await tool.present(call);
7300
+ if (!res) return void 0;
7301
+ if (!isValidPresentationOutput(res)) {
7302
+ console.warn(`Tool "${tool.name}" present() returned invalid shape for status "${call.status}":`, res);
7303
+ return void 0;
7304
+ }
7305
+ return res;
7306
+ } catch (err) {
7307
+ console.warn(`Tool "${tool.name}" present() threw for status "${call.status}":`, err);
7308
+ return void 0;
7309
+ }
7310
+ }
7311
+ async function presentToolPending(tool, args) {
7312
+ const pres = await presentToolCall(tool, { status: "pending", args });
7313
+ return {
7314
+ title: pres?.title ?? tool.annotations?.title ?? tool.name,
7315
+ kind: tool.kind ?? "other",
7316
+ ...pres?.locations !== void 0 ? { locations: pres.locations } : {},
7317
+ ...pres?.content !== void 0 ? { content: pres.content } : {},
7318
+ ...pres?._meta !== void 0 ? { _meta: pres._meta } : {}
6940
7319
  };
6941
7320
  }
6942
7321
  var AgentRuntime = class _AgentRuntime {
@@ -6955,6 +7334,7 @@ var AgentRuntime = class _AgentRuntime {
6955
7334
  host;
6956
7335
  resources;
6957
7336
  startedRuns = /* @__PURE__ */ new Set();
7337
+ liveInteractions = /* @__PURE__ */ new Map();
6958
7338
  heartbeat;
6959
7339
  pumping = false;
6960
7340
  closed = false;
@@ -6990,6 +7370,13 @@ var AgentRuntime = class _AgentRuntime {
6990
7370
  }
6991
7371
  }
6992
7372
  async handleRunEnd(runId, agentId, outcome) {
7373
+ const runLive = this.liveInteractions.get(runId);
7374
+ if (runLive) {
7375
+ for (const live of runLive.values()) {
7376
+ live.reject(new RunInteractionCancelledError("Run ended."));
7377
+ }
7378
+ this.liveInteractions.delete(runId);
7379
+ }
6993
7380
  if (this.startedRuns.has(runId)) {
6994
7381
  this.startedRuns.delete(runId);
6995
7382
  try {
@@ -7133,6 +7520,8 @@ var AgentRuntime = class _AgentRuntime {
7133
7520
  agentId,
7134
7521
  input,
7135
7522
  ...options.metadata === void 0 ? {} : { metadata: options.metadata },
7523
+ ...options.scope === void 0 ? {} : { scope: options.scope },
7524
+ ...options.cwd === void 0 ? {} : { cwd: options.cwd },
7136
7525
  ...phasePayload === void 0 ? {} : { phasePayload },
7137
7526
  ...options.entryPhases === void 0 ? {} : { entryPhases: options.entryPhases },
7138
7527
  ...pinnedConfigToken === agent.currentConfigToken ? {} : { pinnedConfigToken },
@@ -7221,12 +7610,72 @@ var AgentRuntime = class _AgentRuntime {
7221
7610
  this.assertOpen();
7222
7611
  return this.resources.extensionRunner.onCapabilitiesChanged(listener);
7223
7612
  }
7613
+ listUiContributions() {
7614
+ this.assertOpen();
7615
+ return this.resources.extensionRunner.getUiContributions();
7616
+ }
7617
+ onUiContributionsChanged(listener) {
7618
+ this.assertOpen();
7619
+ return this.resources.extensionRunner.onUiContributionsChanged(listener);
7620
+ }
7621
+ triggerUiAction(event) {
7622
+ this.assertOpen();
7623
+ this.resources.extensionRunner.triggerUiAction(event);
7624
+ }
7625
+ liveInteract(runId, request, signal) {
7626
+ if (signal.aborted) {
7627
+ return Promise.reject(signal.reason ?? new Error("Run aborted"));
7628
+ }
7629
+ const interactionId = request.id ?? createId("interaction");
7630
+ const interaction = {
7631
+ id: interactionId,
7632
+ phase: "default",
7633
+ kind: request.kind,
7634
+ prompt: request.prompt,
7635
+ payload: request.payload,
7636
+ toolCallId: request.toolCallId,
7637
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7638
+ status: "pending",
7639
+ result: request.result
7640
+ };
7641
+ let runLive = this.liveInteractions.get(runId);
7642
+ if (!runLive) {
7643
+ runLive = /* @__PURE__ */ new Map();
7644
+ this.liveInteractions.set(runId, runLive);
7645
+ }
7646
+ return new Promise((resolve9, reject) => {
7647
+ const onAbort = () => {
7648
+ runLive?.delete(interactionId);
7649
+ if (runLive && runLive.size === 0) this.liveInteractions.delete(runId);
7650
+ signal.removeEventListener("abort", onAbort);
7651
+ reject(signal.reason ?? new Error("Run aborted"));
7652
+ };
7653
+ signal.addEventListener("abort", onAbort, { once: true });
7654
+ runLive.set(interactionId, {
7655
+ interaction,
7656
+ resolve: (val) => {
7657
+ signal.removeEventListener("abort", onAbort);
7658
+ resolve9(val);
7659
+ },
7660
+ reject: (err) => {
7661
+ signal.removeEventListener("abort", onAbort);
7662
+ reject(err);
7663
+ }
7664
+ });
7665
+ });
7666
+ }
7224
7667
  async close() {
7225
7668
  if (this.closed) return;
7226
7669
  this.closed = true;
7227
7670
  if (this.heartbeat) clearInterval(this.heartbeat);
7228
7671
  for (const execution of this.executions.values()) execution.controller.abort();
7229
7672
  this.executions.clear();
7673
+ for (const runLive of this.liveInteractions.values()) {
7674
+ for (const live of runLive.values()) {
7675
+ live.reject(new RunInteractionCancelledError("Runtime closed."));
7676
+ }
7677
+ }
7678
+ this.liveInteractions.clear();
7230
7679
  this.transientEvents.close();
7231
7680
  for (const subscription of this.consumers.values()) subscription.controller.abort();
7232
7681
  await this.resources.closeExtensions();
@@ -7234,7 +7683,15 @@ var AgentRuntime = class _AgentRuntime {
7234
7683
  }
7235
7684
  async snapshot(runId) {
7236
7685
  this.assertOpen();
7237
- return this.owned.snapshotRun(runId);
7686
+ const snap = await this.owned.snapshotRun(runId);
7687
+ const runLive = this.liveInteractions.get(runId);
7688
+ if (runLive && runLive.size > 0 && snap.state === "running") {
7689
+ return {
7690
+ ...snap,
7691
+ interactions: [...runLive.values()].map((l) => l.interaction)
7692
+ };
7693
+ }
7694
+ return snap;
7238
7695
  }
7239
7696
  async history(agentId) {
7240
7697
  this.assertOpen();
@@ -7242,6 +7699,20 @@ var AgentRuntime = class _AgentRuntime {
7242
7699
  }
7243
7700
  async respondInteraction(runId, input) {
7244
7701
  this.assertOpen();
7702
+ const runLive = this.liveInteractions.get(runId);
7703
+ const live = runLive?.get(input.interactionId);
7704
+ if (live) {
7705
+ runLive.delete(input.interactionId);
7706
+ if (runLive.size === 0) this.liveInteractions.delete(runId);
7707
+ if (input.cancel) {
7708
+ live.reject(new RunInteractionCancelledError("Run interaction cancelled."));
7709
+ } else if (input.input !== void 0) {
7710
+ live.resolve(input.input);
7711
+ } else {
7712
+ live.reject(new TypeError("respondInteraction requires input or cancel: true"));
7713
+ }
7714
+ return;
7715
+ }
7245
7716
  const snapshot = await this.owned.snapshotRun(runId);
7246
7717
  if (snapshot.state !== "input_required" || !snapshot.interactions.some((interaction) => interaction.id === input.interactionId)) {
7247
7718
  throw new RuntimeError("input_request_conflict", { runId, interactionId: input.interactionId, reason: "not_found" });
@@ -7251,6 +7722,13 @@ var AgentRuntime = class _AgentRuntime {
7251
7722
  }
7252
7723
  async cancel(runId, reason) {
7253
7724
  this.assertOpen();
7725
+ const runLive = this.liveInteractions.get(runId);
7726
+ if (runLive) {
7727
+ for (const live of runLive.values()) {
7728
+ live.reject(new RunInteractionCancelledError(reason ?? "Agent run stopped."));
7729
+ }
7730
+ this.liveInteractions.delete(runId);
7731
+ }
7254
7732
  const done = this.executionDone.get(runId);
7255
7733
  if (done) {
7256
7734
  this.cancellationRequested.add(runId);
@@ -7309,6 +7787,7 @@ var AgentRuntime = class _AgentRuntime {
7309
7787
  async execute(run) {
7310
7788
  let claim;
7311
7789
  let executionRevision = run.revision;
7790
+ let toolQueue = Promise.resolve();
7312
7791
  try {
7313
7792
  const agent = await this.requireAgent(run.agentId);
7314
7793
  let token = run.pinnedConfigToken ?? agent.currentConfigToken;
@@ -7396,7 +7875,7 @@ var AgentRuntime = class _AgentRuntime {
7396
7875
  const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
7397
7876
  toolArchiveDir: this.archiveDirFor(run.agentId)
7398
7877
  });
7399
- let toolQueue = Promise.resolve();
7878
+ toolQueue = Promise.resolve();
7400
7879
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
7401
7880
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
7402
7881
  const phaseRegistry = normalizePhaseRegistry(assembly.context.phases);
@@ -7435,10 +7914,12 @@ var AgentRuntime = class _AgentRuntime {
7435
7914
  executionId: claim.execution.executionId,
7436
7915
  input: typeof run.input === "string" ? run.input : run.input.content,
7437
7916
  ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
7438
- ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
7917
+ ...run.metadata === void 0 ? {} : { runMetadata: run.metadata },
7918
+ ...run.scope === void 0 ? {} : { scope: run.scope },
7919
+ ...run.cwd === void 0 ? {} : { cwd: run.cwd }
7439
7920
  },
7440
7921
  model,
7441
- thinkingLevel: thinkingLevelFromMessages(context.messages),
7922
+ thinkingLevel: thinkingLevelFromUserInput(run.input),
7442
7923
  stream,
7443
7924
  maxAttempts: config.maxAttempts,
7444
7925
  checkpoint: claim.run.checkpoint,
@@ -7579,13 +8060,39 @@ var AgentRuntime = class _AgentRuntime {
7579
8060
  error: event.error
7580
8061
  });
7581
8062
  },
7582
- retryDelayMs: this.retryDelayMs,
7583
- onContext: assembly.setContext,
7584
- runtime: {
7585
- tools: ({ toolCall, contentBlocks, driver }) => {
7586
- const task = toolQueue.then(async () => {
7587
- const execution = await this.executeToolBatch({
7588
- run,
8063
+ onToolReport: (event) => {
8064
+ const active = this.executions.get(run.id);
8065
+ if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
8066
+ this.transientEvents.publish({
8067
+ kind: "tool_state_changed",
8068
+ durability: "transient",
8069
+ runId: event.runId,
8070
+ executionId: claim.execution.executionId,
8071
+ transition: { from: null, to: event.update.status ?? "in_progress" },
8072
+ toolCall: event.update,
8073
+ external: true
8074
+ });
8075
+ const task = toolQueue.then(async () => {
8076
+ const active2 = this.executions.get(run.id);
8077
+ if (this.closed || controller.signal.aborted || active2?.executionId !== claim.execution.executionId) return;
8078
+ const recorded = await this.owned.recordToolCall({
8079
+ runId: run.id,
8080
+ execution: claim.execution,
8081
+ update: event.update,
8082
+ external: true
8083
+ });
8084
+ executionRevision = recorded.runRevision;
8085
+ });
8086
+ toolQueue = task.then(() => void 0, () => void 0);
8087
+ },
8088
+ retryDelayMs: this.retryDelayMs,
8089
+ onContext: assembly.setContext,
8090
+ runtime: {
8091
+ interact: (request) => this.liveInteract(run.id, request, controller.signal),
8092
+ tools: ({ toolCall, contentBlocks, driver, options }) => {
8093
+ const task = toolQueue.then(async () => {
8094
+ const execution = await this.executeToolBatch({
8095
+ run,
7589
8096
  agentMetadata: agent.metadata,
7590
8097
  execution: claim.execution,
7591
8098
  expectedRevision: executionRevision,
@@ -7594,6 +8101,7 @@ var AgentRuntime = class _AgentRuntime {
7594
8101
  contentBlocks,
7595
8102
  signal: controller.signal,
7596
8103
  driver,
8104
+ options,
7597
8105
  onRevision: (rev) => {
7598
8106
  executionRevision = rev;
7599
8107
  }
@@ -7604,7 +8112,7 @@ var AgentRuntime = class _AgentRuntime {
7604
8112
  toolQueue = task.then(() => void 0, () => void 0);
7605
8113
  return task;
7606
8114
  },
7607
- toolsBatch: ({ toolCalls, contentBlocks, driver }) => {
8115
+ toolsBatch: ({ toolCalls, contentBlocks, driver, options }) => {
7608
8116
  const task = toolQueue.then(async () => {
7609
8117
  const execution = await this.executeToolBatch({
7610
8118
  run,
@@ -7616,6 +8124,7 @@ var AgentRuntime = class _AgentRuntime {
7616
8124
  contentBlocks,
7617
8125
  signal: controller.signal,
7618
8126
  driver,
8127
+ options,
7619
8128
  onRevision: (rev) => {
7620
8129
  executionRevision = rev;
7621
8130
  }
@@ -7639,6 +8148,7 @@ var AgentRuntime = class _AgentRuntime {
7639
8148
  result = await executeModel(executionContext);
7640
8149
  }
7641
8150
  }
8151
+ await toolQueue;
7642
8152
  if (this.cancellationRequested.has(run.id) || controller.signal.aborted) {
7643
8153
  const output2 = latestAssistant(run, result.messages, executionContext.messages.length, true);
7644
8154
  const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
@@ -7711,6 +8221,7 @@ var AgentRuntime = class _AgentRuntime {
7711
8221
  } catch (error) {
7712
8222
  if (!claim && error instanceof RuntimeError && ["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) return;
7713
8223
  if (claim) {
8224
+ await toolQueue;
7714
8225
  if (this.cancellationRequested.has(run.id)) {
7715
8226
  const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
7716
8227
  await this.owned.cancelRun({
@@ -7766,7 +8277,8 @@ var AgentRuntime = class _AgentRuntime {
7766
8277
  return { result: batch.results[0], revision: batch.revision };
7767
8278
  }
7768
8279
  async executeToolBatch(input) {
7769
- const scope = resolveScopeFromMetadata(input.run.metadata);
8280
+ const scope = input.run.scope ?? [];
8281
+ const cwd = input.run.cwd;
7770
8282
  const turn = typeof input.run.input === "object" && input.run.input !== null && !Array.isArray(input.run.input) ? input.run.input : {};
7771
8283
  const checkpoint = input.driver?.checkpoint();
7772
8284
  const isSuspensionResume = checkpoint && typeof checkpoint === "object" && checkpoint.kind === "tool_call_suspension";
@@ -7796,6 +8308,7 @@ var AgentRuntime = class _AgentRuntime {
7796
8308
  toolCallId,
7797
8309
  providerToolCallId: toolCall.id,
7798
8310
  scope,
8311
+ ...cwd ? { cwd } : {},
7799
8312
  turn,
7800
8313
  interaction: createRunInteractionDriver({
7801
8314
  currentPhase: "default",
@@ -7823,6 +8336,12 @@ var AgentRuntime = class _AgentRuntime {
7823
8336
  }
7824
8337
  }
7825
8338
  if (!hasInteraction) {
8339
+ const startPresentations = await Promise.all(
8340
+ input.toolCalls.map(async (tc) => {
8341
+ const t = input.toolConfig.tools.find((c) => c.name === tc.name);
8342
+ return t ? presentToolPending(t, toJsonValue(tc.args)) : void 0;
8343
+ })
8344
+ );
7826
8345
  const reserved = await this.owned.reserveToolCalls({
7827
8346
  runId: input.run.id,
7828
8347
  execution: input.execution,
@@ -7833,7 +8352,8 @@ var AgentRuntime = class _AgentRuntime {
7833
8352
  toolCallId: toolCallIds[i],
7834
8353
  providerToolCallId: tc.id,
7835
8354
  name: tc.name,
7836
- args: toJsonValue(tc.args)
8355
+ args: toJsonValue(tc.args),
8356
+ ...startPresentations[i] ?? {}
7837
8357
  }))
7838
8358
  });
7839
8359
  setRevision(reserved.run.revision);
@@ -7857,7 +8377,9 @@ var AgentRuntime = class _AgentRuntime {
7857
8377
  expectedRevision: revision,
7858
8378
  toolCallId,
7859
8379
  state: "failed",
7860
- result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` }
8380
+ result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` },
8381
+ title: toolCall.name,
8382
+ kind: "other"
7861
8383
  });
7862
8384
  setRevision(failed.run.revision);
7863
8385
  results2.push(protocolFailure(`Tool ${toolCall.name} is not available.`));
@@ -7871,117 +8393,64 @@ var AgentRuntime = class _AgentRuntime {
7871
8393
  expectedRevision: revision,
7872
8394
  toolCallId,
7873
8395
  state: "failed",
7874
- result: { ok: false, content: null, error: decision.reason }
8396
+ result: { ok: false, content: null, error: decision.reason },
8397
+ title: durableTool.annotations?.title ?? durableTool.name,
8398
+ kind: durableTool.kind ?? "other"
7875
8399
  });
7876
8400
  setRevision(failed.run.revision);
7877
8401
  results2.push(protocolFailure(decision.reason));
7878
8402
  continue;
7879
8403
  }
7880
- let progressActive = false;
7881
- const context = {
7882
- agentId: input.run.agentId,
7883
- runId: input.run.id,
7884
- ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
7885
- ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
7886
- toolCallId,
7887
- providerToolCallId,
7888
- scope,
7889
- turn,
7890
- interaction: createToolInteractionDriver(input.driver, {
7891
- toolCallId,
7892
- toolCallIds,
7893
- toolCalls: input.toolCalls,
7894
- pendingIndex: i,
7895
- completedResults: results2
7896
- }),
7897
- reportProgress: (progress) => {
7898
- const active = this.executions.get(input.run.id);
7899
- if (!progressActive || this.closed || input.signal.aborted || active?.executionId !== input.execution.executionId || !isJsonValue(progress)) return;
7900
- let copy;
7901
- try {
7902
- if (JSON.stringify(progress).length > 64 * 1024) return;
7903
- copy = structuredClone(progress);
7904
- } catch {
7905
- return;
7906
- }
7907
- this.transientEvents.publish({
7908
- kind: "tool_progress",
7909
- durability: "transient",
7910
- runId: input.run.id,
7911
- executionId: input.execution.executionId,
7912
- toolCallId,
7913
- progress: copy
7914
- });
7915
- }
8404
+ const currentToolState = {
8405
+ ...startPresentations[i] ?? {
8406
+ title: durableTool.annotations?.title ?? durableTool.name,
8407
+ kind: durableTool.kind ?? "other"
8408
+ },
8409
+ rawInput: toJsonValue(toolCall.args)
7916
8410
  };
8411
+ const inProgressPres = await presentToolCall(durableTool, {
8412
+ status: "in_progress",
8413
+ args: toJsonValue(toolCall.args)
8414
+ });
8415
+ if (inProgressPres) mergeToolCall(currentToolState, inProgressPres);
7917
8416
  const started = await this.owned.startToolCall({
7918
8417
  runId: input.run.id,
7919
8418
  execution: input.execution,
7920
8419
  expectedRevision: revision,
7921
- toolCallId
8420
+ toolCallId,
8421
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8422
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8423
+ ...currentToolState.locations !== void 0 ? { locations: currentToolState.locations } : {},
8424
+ ...currentToolState.content !== void 0 ? { content: currentToolState.content } : {},
8425
+ ...currentToolState._meta !== void 0 ? { _meta: currentToolState._meta } : {}
7922
8426
  });
7923
8427
  setRevision(started.run.revision);
7924
- progressActive = true;
7925
- try {
7926
- let result = await durableTool.execute(toJsonValue(toolCall.args), context, input.signal);
7927
- assertToolExecutionResult(result);
7928
- if (input.toolConfig.afterToolCall) {
7929
- result = await input.toolConfig.afterToolCall({
7930
- tool: durableTool,
7931
- result,
7932
- context,
7933
- signal: input.signal
7934
- });
7935
- }
7936
- assertToolExecutionResult(result);
7937
- result = await spillLargeToolResult(
7938
- result,
7939
- this.archiveDirFor(input.run.agentId),
7940
- toolCall.name
7941
- );
7942
- const committed = await this.owned.commitToolResult({
7943
- runId: input.run.id,
7944
- execution: input.execution,
7945
- expectedRevision: revision,
7946
- toolCallId,
7947
- state: result.ok ? "completed" : "failed",
7948
- result
7949
- });
7950
- setRevision(committed.run.revision);
7951
- const projectedResult = await this.foldToolInteractionRecords(result, toolCallId, input.run.agentId);
7952
- results2.push({
7953
- toolCallId: providerToolCallId,
7954
- toolName: toolCall.name,
7955
- ...projectedResult
7956
- });
7957
- } catch (error) {
7958
- if (error instanceof RunInteractionBoundary) {
7959
- const suspended = await this.owned.suspendToolCall({
7960
- runId: input.run.id,
7961
- execution: input.execution,
7962
- expectedRevision: revision,
7963
- toolCallId
7964
- });
7965
- setRevision(suspended.run.revision);
7966
- throw error;
7967
- }
7968
- if (error instanceof RunInteractionCancelledError) throw error;
7969
- const reason = error instanceof Error ? error.message : "Tool execution outcome is indeterminate.";
7970
- const indeterminate = await this.owned.commitToolResult({
7971
- runId: input.run.id,
7972
- execution: input.execution,
7973
- expectedRevision: revision,
7974
- toolCallId,
7975
- state: "indeterminate",
7976
- reason,
7977
- result: { ok: false, content: null, error: reason }
7978
- });
7979
- setRevision(indeterminate.run.revision);
7980
- results2.push(protocolFailure(reason));
7981
- } finally {
7982
- progressActive = false;
7983
- this.transientEvents.clearTool(input.run.id, toolCallId);
7984
- }
8428
+ const toolResult2 = await this.executeAndCommitTool({
8429
+ runId: input.run.id,
8430
+ agentId: input.run.agentId,
8431
+ agentMetadata: input.agentMetadata,
8432
+ runMetadata: input.run.metadata,
8433
+ execution: input.execution,
8434
+ revision,
8435
+ setRevision,
8436
+ toolCall,
8437
+ durableTool,
8438
+ toolCallId,
8439
+ providerToolCallId,
8440
+ scope,
8441
+ cwd,
8442
+ turn,
8443
+ driver: input.driver,
8444
+ toolCalls: input.toolCalls,
8445
+ toolCallIds,
8446
+ pendingIndex: i,
8447
+ completedResults: results2,
8448
+ signal: input.signal,
8449
+ afterToolCall: input.toolConfig.afterToolCall,
8450
+ currentToolState,
8451
+ options: input.options
8452
+ });
8453
+ results2.push(toolResult2);
7985
8454
  }
7986
8455
  return { results: results2, revision };
7987
8456
  }
@@ -8001,7 +8470,7 @@ var AgentRuntime = class _AgentRuntime {
8001
8470
  error
8002
8471
  });
8003
8472
  if (!durableTool) {
8004
- const reserved2 = await this.owned.reserveToolCalls({
8473
+ const reserved = await this.owned.reserveToolCalls({
8005
8474
  runId: input.run.id,
8006
8475
  execution: input.execution,
8007
8476
  expectedRevision: revision,
@@ -8014,27 +8483,58 @@ var AgentRuntime = class _AgentRuntime {
8014
8483
  args: toJsonValue(toolCall.args)
8015
8484
  }]
8016
8485
  });
8017
- setRevision(reserved2.run.revision);
8486
+ setRevision(reserved.run.revision);
8018
8487
  const failed = await this.owned.commitToolResult({
8019
8488
  runId: input.run.id,
8020
8489
  execution: input.execution,
8021
8490
  expectedRevision: revision,
8022
8491
  toolCallId,
8023
8492
  state: "failed",
8024
- result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` }
8493
+ result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` },
8494
+ title: toolCall.name,
8495
+ kind: "other"
8025
8496
  });
8026
8497
  setRevision(failed.run.revision);
8027
8498
  results.push(protocolFailure(`Tool ${toolCall.name} is not available.`));
8028
8499
  continue;
8029
8500
  }
8030
- let progressActive = false;
8031
- const resumedStart = suspension && index === suspension.pendingIndex ? await this.owned.startToolCall({
8032
- runId: input.run.id,
8033
- execution: input.execution,
8034
- expectedRevision: revision,
8035
- toolCallId
8036
- }) : void 0;
8501
+ const pendingPres = await presentToolPending(durableTool, toJsonValue(toolCall.args));
8502
+ const currentToolState = {
8503
+ ...pendingPres,
8504
+ rawInput: toJsonValue(toolCall.args)
8505
+ };
8506
+ const resumedStart = suspension && index === suspension.pendingIndex ? await (async () => {
8507
+ const inProgressPres = await presentToolCall(durableTool, {
8508
+ status: "in_progress",
8509
+ args: toJsonValue(toolCall.args)
8510
+ });
8511
+ if (inProgressPres) mergeToolCall(currentToolState, inProgressPres);
8512
+ return this.owned.startToolCall({
8513
+ runId: input.run.id,
8514
+ execution: input.execution,
8515
+ expectedRevision: revision,
8516
+ toolCallId,
8517
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8518
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8519
+ ...currentToolState.locations !== void 0 ? { locations: currentToolState.locations } : {},
8520
+ ...currentToolState.content !== void 0 ? { content: currentToolState.content } : {},
8521
+ ...currentToolState._meta !== void 0 ? { _meta: currentToolState._meta } : {}
8522
+ });
8523
+ })() : void 0;
8037
8524
  if (resumedStart) setRevision(resumedStart.run.revision);
8525
+ const baseDriver = input.driver ?? createRunInteractionDriver({
8526
+ currentPhase: "default",
8527
+ attempt: 0,
8528
+ status: "running",
8529
+ metrics: { iterations: 0, phaseTransitions: [], compactionCount: 0, retryCount: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), startedAtMs: Date.now() }
8530
+ }, "default", input.signal);
8531
+ const interactionDriver = createToolInteractionDriver(baseDriver, {
8532
+ toolCallId,
8533
+ ...suspension?.toolCallIds === void 0 ? {} : { toolCallIds: suspension.toolCallIds },
8534
+ toolCalls: input.toolCalls,
8535
+ pendingIndex: index,
8536
+ completedResults: results
8537
+ });
8038
8538
  const context = {
8039
8539
  agentId: input.run.agentId,
8040
8540
  runId: input.run.id,
@@ -8043,32 +8543,10 @@ var AgentRuntime = class _AgentRuntime {
8043
8543
  toolCallId,
8044
8544
  providerToolCallId,
8045
8545
  scope,
8546
+ ...cwd ? { cwd } : {},
8046
8547
  turn,
8047
- interaction: createToolInteractionDriver(input.driver, {
8048
- toolCallId,
8049
- ...suspension?.toolCallIds === void 0 ? {} : { toolCallIds: suspension.toolCallIds },
8050
- toolCalls: input.toolCalls,
8051
- pendingIndex: index,
8052
- completedResults: results
8053
- }),
8054
- reportProgress: (progress) => {
8055
- const active = this.executions.get(input.run.id);
8056
- if (!progressActive || this.closed || input.signal.aborted || active?.executionId !== input.execution.executionId || !isJsonValue(progress)) return;
8057
- let copy;
8058
- try {
8059
- if (JSON.stringify(progress).length > 64 * 1024) return;
8060
- copy = structuredClone(progress);
8061
- } catch {
8062
- return;
8063
- }
8064
- this.transientEvents.publish({
8065
- kind: "tool_progress",
8066
- durability: "transient",
8067
- runId: input.run.id,
8068
- executionId: input.execution.executionId,
8069
- toolCallId,
8070
- progress: copy
8071
- });
8548
+ interaction: interactionDriver,
8549
+ reportProgress: () => {
8072
8550
  }
8073
8551
  };
8074
8552
  const answer = suspension && index === suspension.pendingIndex ? suspension.interactionIds?.map((id) => input.driver?.answers().get(id)).find((value) => value !== void 0) : void 0;
@@ -8085,31 +8563,43 @@ var AgentRuntime = class _AgentRuntime {
8085
8563
  if (!input.driver) {
8086
8564
  throw new Error("Tool call requested interaction but no interaction driver is active.");
8087
8565
  }
8088
- context.interaction.request(decision.interaction);
8089
- if (!(suspension && index === suspension.pendingIndex)) {
8090
- const reserved2 = await this.owned.reserveToolCalls({
8566
+ interactionDriver.request(decision.interaction);
8567
+ if (!resumedStart) {
8568
+ const reserved = await this.owned.reserveToolCalls({
8091
8569
  runId: input.run.id,
8092
8570
  execution: input.execution,
8093
8571
  expectedRevision: revision,
8094
8572
  requestMessageId: createId("msg"),
8095
8573
  contentBlocks: input.contentBlocks,
8096
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8574
+ calls: [{
8575
+ toolCallId,
8576
+ providerToolCallId,
8577
+ name: toolCall.name,
8578
+ args: toJsonValue(toolCall.args),
8579
+ ...pendingPres
8580
+ }]
8097
8581
  });
8098
- setRevision(reserved2.run.revision);
8582
+ setRevision(reserved.run.revision);
8099
8583
  }
8100
- context.interaction.suspend();
8584
+ interactionDriver.suspend();
8101
8585
  }
8102
8586
  if ("allow" in decision && !decision.allow) {
8103
- if (!(suspension && index === suspension.pendingIndex)) {
8104
- const reserved2 = await this.owned.reserveToolCalls({
8587
+ if (!resumedStart) {
8588
+ const reserved = await this.owned.reserveToolCalls({
8105
8589
  runId: input.run.id,
8106
8590
  execution: input.execution,
8107
8591
  expectedRevision: revision,
8108
8592
  requestMessageId: createId("msg"),
8109
8593
  contentBlocks: input.contentBlocks,
8110
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8594
+ calls: [{
8595
+ toolCallId,
8596
+ providerToolCallId,
8597
+ name: toolCall.name,
8598
+ args: toJsonValue(toolCall.args),
8599
+ ...pendingPres
8600
+ }]
8111
8601
  });
8112
- setRevision(reserved2.run.revision);
8602
+ setRevision(reserved.run.revision);
8113
8603
  }
8114
8604
  const failed = await this.owned.commitToolResult({
8115
8605
  runId: input.run.id,
@@ -8117,9 +8607,27 @@ var AgentRuntime = class _AgentRuntime {
8117
8607
  expectedRevision: revision,
8118
8608
  toolCallId,
8119
8609
  state: "failed",
8120
- result: { ok: false, content: null, error: decision.reason }
8610
+ result: { ok: false, content: null, error: decision.reason },
8611
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8612
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8613
+ locations: currentToolState.locations,
8614
+ content: currentToolState.content,
8615
+ _meta: currentToolState._meta
8121
8616
  });
8122
8617
  setRevision(failed.run.revision);
8618
+ if (input.options?.onUpdate) {
8619
+ try {
8620
+ input.options.onUpdate(createMergedToolCall(
8621
+ toolCallId,
8622
+ "failed",
8623
+ currentToolState,
8624
+ durableTool.annotations?.title ?? durableTool.name,
8625
+ durableTool.kind ?? "other"
8626
+ ));
8627
+ } catch (err) {
8628
+ console.warn(`Error in tool call onUpdate callback:`, err);
8629
+ }
8630
+ }
8123
8631
  results.push(protocolFailure(decision.reason));
8124
8632
  continue;
8125
8633
  }
@@ -8129,16 +8637,22 @@ var AgentRuntime = class _AgentRuntime {
8129
8637
  throw error;
8130
8638
  }
8131
8639
  const reason = error instanceof Error ? error.message : "Tool policy rejected the call.";
8132
- if (!(suspension && index === suspension.pendingIndex)) {
8133
- const reserved2 = await this.owned.reserveToolCalls({
8640
+ if (!resumedStart) {
8641
+ const reserved = await this.owned.reserveToolCalls({
8134
8642
  runId: input.run.id,
8135
8643
  execution: input.execution,
8136
8644
  expectedRevision: revision,
8137
8645
  requestMessageId: createId("msg"),
8138
8646
  contentBlocks: input.contentBlocks,
8139
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8647
+ calls: [{
8648
+ toolCallId,
8649
+ providerToolCallId,
8650
+ name: toolCall.name,
8651
+ args: toJsonValue(toolCall.args),
8652
+ ...pendingPres
8653
+ }]
8140
8654
  });
8141
- setRevision(reserved2.run.revision);
8655
+ setRevision(reserved.run.revision);
8142
8656
  }
8143
8657
  const failed = await this.owned.commitToolResult({
8144
8658
  runId: input.run.id,
@@ -8146,94 +8660,316 @@ var AgentRuntime = class _AgentRuntime {
8146
8660
  expectedRevision: revision,
8147
8661
  toolCallId,
8148
8662
  state: "failed",
8149
- result: { ok: false, content: null, error: reason }
8663
+ result: { ok: false, content: null, error: reason },
8664
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8665
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8666
+ locations: currentToolState.locations,
8667
+ content: currentToolState.content,
8668
+ _meta: currentToolState._meta
8150
8669
  });
8151
8670
  setRevision(failed.run.revision);
8671
+ if (input.options?.onUpdate) {
8672
+ try {
8673
+ input.options.onUpdate(createMergedToolCall(
8674
+ toolCallId,
8675
+ "failed",
8676
+ currentToolState,
8677
+ durableTool.annotations?.title ?? durableTool.name,
8678
+ durableTool.kind ?? "other"
8679
+ ));
8680
+ } catch (err) {
8681
+ console.warn(`Error in tool call onUpdate callback:`, err);
8682
+ }
8683
+ }
8152
8684
  results.push(protocolFailure(reason));
8153
8685
  continue;
8154
8686
  }
8155
- const reserved = resumedStart ?? await (async () => {
8156
- const committed = await this.owned.reserveToolCalls({
8687
+ if (!resumedStart) {
8688
+ const reserved = await this.owned.reserveToolCalls({
8157
8689
  runId: input.run.id,
8158
8690
  execution: input.execution,
8159
8691
  expectedRevision: revision,
8160
8692
  requestMessageId: createId("msg"),
8161
8693
  contentBlocks: input.contentBlocks,
8162
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8694
+ calls: [{
8695
+ toolCallId,
8696
+ providerToolCallId,
8697
+ name: toolCall.name,
8698
+ args: toJsonValue(toolCall.args),
8699
+ ...pendingPres
8700
+ }]
8163
8701
  });
8164
- setRevision(committed.run.revision);
8165
- return this.owned.startToolCall({
8166
- runId: input.run.id,
8167
- execution: input.execution,
8168
- expectedRevision: revision,
8169
- toolCallId
8702
+ setRevision(reserved.run.revision);
8703
+ const inProgressPres = await presentToolCall(durableTool, {
8704
+ status: "in_progress",
8705
+ args: toJsonValue(toolCall.args)
8170
8706
  });
8171
- })();
8172
- if (reserved) setRevision(reserved.run.revision);
8173
- progressActive = true;
8174
- try {
8175
- let result = await durableTool.execute(toJsonValue(toolCall.args), context, input.signal);
8176
- assertToolExecutionResult(result);
8177
- if (input.toolConfig.afterToolCall) {
8178
- result = await input.toolConfig.afterToolCall({
8179
- tool: durableTool,
8180
- result,
8181
- context,
8182
- signal: input.signal
8183
- });
8184
- }
8185
- assertToolExecutionResult(result);
8186
- result = await spillLargeToolResult(
8187
- result,
8188
- this.archiveDirFor(input.run.agentId),
8189
- toolCall.name
8190
- );
8191
- const committed = await this.owned.commitToolResult({
8707
+ if (inProgressPres) mergeToolCall(currentToolState, inProgressPres);
8708
+ const started = await this.owned.startToolCall({
8192
8709
  runId: input.run.id,
8193
8710
  execution: input.execution,
8194
8711
  expectedRevision: revision,
8195
8712
  toolCallId,
8196
- state: result.ok ? "completed" : "failed",
8197
- result
8198
- });
8199
- setRevision(committed.run.revision);
8200
- const projectedResult = await this.foldToolInteractionRecords(result, toolCallId, input.run.agentId);
8201
- results.push({
8202
- toolCallId: providerToolCallId,
8203
- toolName: toolCall.name,
8204
- ...projectedResult
8713
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8714
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8715
+ ...currentToolState.locations !== void 0 ? { locations: currentToolState.locations } : {},
8716
+ ...currentToolState.content !== void 0 ? { content: currentToolState.content } : {},
8717
+ ...currentToolState._meta !== void 0 ? { _meta: currentToolState._meta } : {}
8205
8718
  });
8206
- } catch (error) {
8207
- if (error instanceof RunInteractionBoundary) {
8208
- const suspended = await this.owned.suspendToolCall({
8209
- runId: input.run.id,
8210
- execution: input.execution,
8211
- expectedRevision: revision,
8212
- toolCallId
8213
- });
8214
- setRevision(suspended.run.revision);
8215
- throw error;
8719
+ setRevision(started.run.revision);
8720
+ }
8721
+ const toolResult2 = await this.executeAndCommitTool({
8722
+ runId: input.run.id,
8723
+ agentId: input.run.agentId,
8724
+ agentMetadata: input.agentMetadata,
8725
+ runMetadata: input.run.metadata,
8726
+ execution: input.execution,
8727
+ revision,
8728
+ setRevision,
8729
+ toolCall,
8730
+ durableTool,
8731
+ toolCallId,
8732
+ providerToolCallId,
8733
+ scope,
8734
+ cwd,
8735
+ turn,
8736
+ driver: input.driver,
8737
+ toolCalls: input.toolCalls,
8738
+ toolCallIds: suspension?.toolCallIds,
8739
+ pendingIndex: index,
8740
+ completedResults: results,
8741
+ signal: input.signal,
8742
+ afterToolCall: input.toolConfig.afterToolCall,
8743
+ currentToolState,
8744
+ options: input.options
8745
+ });
8746
+ results.push(toolResult2);
8747
+ }
8748
+ input.driver?.clearCheckpoint();
8749
+ return { results, revision };
8750
+ }
8751
+ async executeAndCommitTool(input) {
8752
+ let progressActive = true;
8753
+ let progressQueue = Promise.resolve();
8754
+ const notifyUpdate = (toolCall, progress) => {
8755
+ if (input.options?.onUpdate) {
8756
+ try {
8757
+ input.options.onUpdate(toolCall, progress);
8758
+ } catch (err) {
8759
+ console.warn(`Error in tool call onUpdate callback:`, err);
8216
8760
  }
8217
- if (error instanceof RunInteractionCancelledError) throw error;
8218
- const reason = error instanceof Error ? error.message : "Tool execution outcome is indeterminate.";
8219
- const indeterminate = await this.owned.commitToolResult({
8220
- runId: input.run.id,
8761
+ }
8762
+ };
8763
+ const startToolCall = createMergedToolCall(
8764
+ input.toolCallId,
8765
+ "in_progress",
8766
+ input.currentToolState,
8767
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8768
+ input.durableTool.kind ?? "other"
8769
+ );
8770
+ notifyUpdate(startToolCall);
8771
+ const effectiveSignal = combineSignals(input.signal, input.options?.signal);
8772
+ const baseDriver = input.driver ?? createRunInteractionDriver({
8773
+ currentPhase: "default",
8774
+ attempt: 0,
8775
+ status: "running",
8776
+ metrics: { iterations: 0, phaseTransitions: [], compactionCount: 0, retryCount: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), startedAtMs: Date.now() }
8777
+ }, "default", effectiveSignal);
8778
+ const interactionDriver = createToolInteractionDriver(baseDriver, {
8779
+ toolCallId: input.toolCallId,
8780
+ ...input.toolCallIds === void 0 ? {} : { toolCallIds: input.toolCallIds },
8781
+ toolCalls: input.toolCalls,
8782
+ pendingIndex: input.pendingIndex,
8783
+ completedResults: input.completedResults
8784
+ });
8785
+ const context = {
8786
+ agentId: input.agentId,
8787
+ runId: input.runId,
8788
+ ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
8789
+ ...input.runMetadata === void 0 ? {} : { runMetadata: input.runMetadata },
8790
+ toolCallId: input.toolCallId,
8791
+ providerToolCallId: input.providerToolCallId,
8792
+ scope: input.scope,
8793
+ ...input.cwd ? { cwd: input.cwd } : {},
8794
+ turn: input.turn,
8795
+ interaction: interactionDriver,
8796
+ reportProgress: (progress) => {
8797
+ const active = this.executions.get(input.runId);
8798
+ if (!progressActive || this.closed || effectiveSignal.aborted || active?.executionId !== input.execution.executionId) return;
8799
+ if (!isValidToolProgress(progress)) {
8800
+ console.warn(`Invalid progress report for tool "${input.durableTool.name}": expected valid ToolProgress shape with finite numbers.`);
8801
+ return;
8802
+ }
8803
+ let copy;
8804
+ try {
8805
+ if (JSON.stringify(progress).length > 64 * 1024) return;
8806
+ copy = structuredClone(progress);
8807
+ } catch {
8808
+ return;
8809
+ }
8810
+ progressQueue = progressQueue.then(async () => {
8811
+ if (!progressActive || this.closed || effectiveSignal.aborted) return;
8812
+ const pres = await presentToolCall(input.durableTool, {
8813
+ status: "in_progress",
8814
+ args: toJsonValue(input.toolCall.args),
8815
+ progress: copy
8816
+ });
8817
+ if (pres) {
8818
+ mergeToolCall(input.currentToolState, pres);
8819
+ const progressToolCall = createMergedToolCall(
8820
+ input.toolCallId,
8821
+ "in_progress",
8822
+ input.currentToolState,
8823
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8824
+ input.durableTool.kind ?? "other"
8825
+ );
8826
+ this.transientEvents.publish({
8827
+ kind: "tool_state_changed",
8828
+ durability: "transient",
8829
+ runId: input.runId,
8830
+ executionId: input.execution.executionId,
8831
+ transition: { from: "in_progress", to: "in_progress" },
8832
+ toolCall: progressToolCall
8833
+ });
8834
+ notifyUpdate(progressToolCall, copy);
8835
+ }
8836
+ }).catch((err) => {
8837
+ console.warn(`Error handling progress report for tool "${input.durableTool.name}":`, err);
8838
+ });
8839
+ }
8840
+ };
8841
+ try {
8842
+ if (input.options?.signal?.aborted) {
8843
+ const error = input.options.signal.reason instanceof Error ? input.options.signal.reason : new DOMException(
8844
+ typeof input.options.signal.reason === "string" ? input.options.signal.reason : "Tool execution aborted.",
8845
+ "AbortError"
8846
+ );
8847
+ throw error;
8848
+ }
8849
+ let result = await withAbort(
8850
+ input.durableTool.execute(toJsonValue(input.toolCall.args), context, effectiveSignal),
8851
+ input.options?.signal
8852
+ );
8853
+ assertToolExecutionResult(result);
8854
+ if (input.afterToolCall) {
8855
+ result = await input.afterToolCall({
8856
+ tool: input.durableTool,
8857
+ result,
8858
+ context,
8859
+ signal: effectiveSignal
8860
+ });
8861
+ }
8862
+ assertToolExecutionResult(result);
8863
+ result = await spillLargeToolResult(
8864
+ result,
8865
+ this.archiveDirFor(input.agentId),
8866
+ input.toolCall.name
8867
+ );
8868
+ await progressQueue.catch(() => {
8869
+ });
8870
+ const finalState = result.ok ? "completed" : "failed";
8871
+ const completionPres = await presentToolCall(input.durableTool, {
8872
+ status: finalState,
8873
+ args: toJsonValue(input.toolCall.args),
8874
+ result
8875
+ });
8876
+ if (completionPres) {
8877
+ mergeToolCall(input.currentToolState, completionPres);
8878
+ }
8879
+ const rawOutput = "structuredContent" in result && result.structuredContent !== void 0 ? toJsonValue(result.structuredContent) : toJsonValue(result.content);
8880
+ if (input.currentToolState.rawOutput === void 0) {
8881
+ input.currentToolState.rawOutput = rawOutput;
8882
+ }
8883
+ const committed = await this.owned.commitToolResult({
8884
+ runId: input.runId,
8885
+ execution: input.execution,
8886
+ expectedRevision: input.revision,
8887
+ toolCallId: input.toolCallId,
8888
+ state: finalState,
8889
+ result,
8890
+ title: input.currentToolState.title ?? input.durableTool.annotations?.title ?? input.durableTool.name,
8891
+ kind: input.currentToolState.kind ?? input.durableTool.kind ?? "other",
8892
+ locations: input.currentToolState.locations,
8893
+ content: input.currentToolState.content,
8894
+ rawOutput: input.currentToolState.rawOutput,
8895
+ _meta: input.currentToolState._meta
8896
+ });
8897
+ input.setRevision(committed.run.revision);
8898
+ const finalToolCall = createMergedToolCall(
8899
+ input.toolCallId,
8900
+ finalState,
8901
+ input.currentToolState,
8902
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8903
+ input.durableTool.kind ?? "other"
8904
+ );
8905
+ notifyUpdate(finalToolCall);
8906
+ const projectedResult = await this.foldToolInteractionRecords(result, input.toolCallId, input.agentId);
8907
+ const ok = "ok" in projectedResult && typeof projectedResult.ok === "boolean" ? projectedResult.ok : !projectedResult.isError;
8908
+ return {
8909
+ toolCallId: input.providerToolCallId,
8910
+ toolName: input.toolCall.name,
8911
+ ...projectedResult,
8912
+ ok
8913
+ };
8914
+ } catch (error) {
8915
+ if (error instanceof RunInteractionBoundary) {
8916
+ const suspended = await this.owned.suspendToolCall({
8917
+ runId: input.runId,
8221
8918
  execution: input.execution,
8222
- expectedRevision: revision,
8223
- toolCallId,
8224
- state: "indeterminate",
8225
- reason,
8226
- result: { ok: false, content: null, error: reason }
8919
+ expectedRevision: input.revision,
8920
+ toolCallId: input.toolCallId
8227
8921
  });
8228
- setRevision(indeterminate.run.revision);
8229
- results.push(protocolFailure(reason));
8230
- } finally {
8231
- progressActive = false;
8232
- this.transientEvents.clearTool(input.run.id, toolCallId);
8922
+ input.setRevision(suspended.run.revision);
8923
+ throw error;
8233
8924
  }
8925
+ if (error instanceof RunInteractionCancelledError) throw error;
8926
+ const isPerCallAbort = Boolean(input.options?.signal?.aborted && !input.signal.aborted);
8927
+ const reason = error instanceof Error ? error.message : isPerCallAbort ? "Tool execution aborted." : "Tool execution outcome is indeterminate.";
8928
+ await progressQueue.catch(() => {
8929
+ });
8930
+ const failurePres = await presentToolCall(input.durableTool, {
8931
+ status: "failed",
8932
+ args: toJsonValue(input.toolCall.args),
8933
+ result: { ok: false, content: null, error: reason }
8934
+ });
8935
+ if (failurePres) {
8936
+ mergeToolCall(input.currentToolState, failurePres);
8937
+ }
8938
+ const commitState = isPerCallAbort ? "failed" : "indeterminate";
8939
+ const committed = await this.owned.commitToolResult({
8940
+ runId: input.runId,
8941
+ execution: input.execution,
8942
+ expectedRevision: input.revision,
8943
+ toolCallId: input.toolCallId,
8944
+ state: commitState,
8945
+ ...isPerCallAbort ? {} : { reason },
8946
+ result: { ok: false, content: null, error: reason },
8947
+ title: input.currentToolState.title ?? input.durableTool.annotations?.title ?? input.durableTool.name,
8948
+ kind: input.currentToolState.kind ?? input.durableTool.kind ?? "other",
8949
+ locations: input.currentToolState.locations,
8950
+ content: input.currentToolState.content,
8951
+ _meta: input.currentToolState._meta
8952
+ });
8953
+ input.setRevision(committed.run.revision);
8954
+ const finalToolCall = createMergedToolCall(
8955
+ input.toolCallId,
8956
+ "failed",
8957
+ input.currentToolState,
8958
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8959
+ input.durableTool.kind ?? "other"
8960
+ );
8961
+ notifyUpdate(finalToolCall);
8962
+ return {
8963
+ toolCallId: input.providerToolCallId,
8964
+ toolName: input.toolCall.name,
8965
+ ok: false,
8966
+ content: null,
8967
+ error: reason
8968
+ };
8969
+ } finally {
8970
+ progressActive = false;
8971
+ this.transientEvents.clearTool(input.runId, input.toolCallId);
8234
8972
  }
8235
- input.driver?.clearCheckpoint();
8236
- return { results, revision };
8237
8973
  }
8238
8974
  async foldToolInteractionRecords(result, toolCallId, agentId) {
8239
8975
  const interactionMessages = (await this.owned.history(agentId)).filter(
@@ -8242,11 +8978,27 @@ var AgentRuntime = class _AgentRuntime {
8242
8978
  if (interactionMessages.length === 0) return result;
8243
8979
  const interactionText = interactionMessages.filter((message) => message.role === "interaction").map(renderInteractionText).join("\n\n");
8244
8980
  const content = typeof result.content === "string" ? result.content : JSON.stringify(result.content) ?? "null";
8245
- return {
8246
- ...result,
8247
- content: content === "null" || content.length === 0 ? interactionText : `${interactionText}
8981
+ const appendedText = content === "null" || content.length === 0 ? interactionText : `${interactionText}
8248
8982
 
8249
- ${content}`
8983
+ ${content}`;
8984
+ if (Array.isArray(result.content)) {
8985
+ return {
8986
+ ...result,
8987
+ content: [{ type: "text", text: appendedText }]
8988
+ };
8989
+ }
8990
+ if (result.ok) {
8991
+ return {
8992
+ ok: true,
8993
+ content: appendedText,
8994
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
8995
+ };
8996
+ }
8997
+ return {
8998
+ ok: false,
8999
+ content: appendedText,
9000
+ error: result.error ?? "Tool execution failed.",
9001
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
8250
9002
  };
8251
9003
  }
8252
9004
  async requireAgent(agentId) {
@@ -8334,8 +9086,8 @@ ${content}`
8334
9086
  if (event.runId !== runId) continue;
8335
9087
  if (event.kind === "message_committed") {
8336
9088
  subscription.clearMessage(event.message.id);
8337
- } else if (event.kind === "tool_state_changed" && ["completed", "failed", "indeterminate"].includes(event.transition.to)) {
8338
- subscription.clearTool(event.toolCall.id);
9089
+ } else if (event.kind === "tool_state_changed" && ["completed", "failed"].includes(event.transition.to)) {
9090
+ subscription.clearTool(event.toolCall.toolCallId);
8339
9091
  }
8340
9092
  if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) {
8341
9093
  while (true) {
@@ -8533,6 +9285,19 @@ function toJsonValue(value) {
8533
9285
  assertJsonValue(value, "tool arguments");
8534
9286
  return value;
8535
9287
  }
9288
+ function createMergedToolCall(toolCallId, status, current, fallbackTitle, fallbackKind = "other") {
9289
+ return {
9290
+ toolCallId,
9291
+ title: current.title ?? fallbackTitle,
9292
+ kind: current.kind ?? fallbackKind,
9293
+ status,
9294
+ ...current.locations !== void 0 ? { locations: current.locations } : {},
9295
+ ...current.content !== void 0 ? { content: current.content } : {},
9296
+ ...current.rawInput !== void 0 ? { rawInput: current.rawInput } : {},
9297
+ ...current.rawOutput !== void 0 ? { rawOutput: current.rawOutput } : {},
9298
+ ...current._meta !== void 0 ? { _meta: current._meta } : {}
9299
+ };
9300
+ }
8536
9301
  function deferred2() {
8537
9302
  let resolve9;
8538
9303
  let reject;
@@ -8545,6 +9310,24 @@ function deferred2() {
8545
9310
 
8546
9311
  // src/runtime/durable-store.ts
8547
9312
  import { createHash } from "crypto";
9313
+ function defaultToolCallContent(result) {
9314
+ if ("content" in result && Array.isArray(result.content)) {
9315
+ return result.content.map((block) => {
9316
+ if (typeof block === "object" && block !== null && "type" in block && (block.type === "content" || block.type === "diff" || block.type === "terminal")) {
9317
+ return block;
9318
+ }
9319
+ return {
9320
+ type: "content",
9321
+ content: block
9322
+ };
9323
+ });
9324
+ }
9325
+ const content = result.content;
9326
+ if (typeof content === "string") {
9327
+ return [{ type: "content", content: { type: "text", text: content } }];
9328
+ }
9329
+ return [{ type: "content", content: { type: "text", text: JSON.stringify(content ?? null) } }];
9330
+ }
8548
9331
  var InMemoryStore = class _InMemoryStore {
8549
9332
  incarnation;
8550
9333
  agents = /* @__PURE__ */ new Map();
@@ -8663,7 +9446,7 @@ var InMemoryStore = class _InMemoryStore {
8663
9446
  }
8664
9447
  }
8665
9448
  for (const message of state.messages) store.messages.set(message.id, clone(message));
8666
- for (const toolCall of state.toolCalls ?? []) store.toolCalls.set(toolCall.id, clone(toolCall));
9449
+ for (const toolCall of state.toolCalls ?? []) store.toolCalls.set(toolCall.id ?? toolCall.toolCallId, clone(toolCall));
8667
9450
  store.events.push(...clone(state.events));
8668
9451
  for (const [key, receipt] of state.idempotency) store.idempotency.set(key, clone(receipt));
8669
9452
  for (const [key, receipt] of state.operationReceipts) {
@@ -8728,10 +9511,10 @@ var InMemoryStore = class _InMemoryStore {
8728
9511
  this.ownerEpoch = Math.max(this.ownerEpoch, ownerEpoch);
8729
9512
  for (const run of this.runs.values()) {
8730
9513
  if (run.state !== "running" || run.execution?.ownerEpoch !== ownerEpoch) continue;
8731
- const activeToolCalls = [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.state === "pending" || toolCall.state === "running"));
9514
+ const activeToolCalls = [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.status === "pending" || toolCall.status === "in_progress" || toolCall.state === "pending" || toolCall.state === "in_progress"));
8732
9515
  const indeterminateToolCallIds = [];
8733
9516
  for (const toolCall of activeToolCalls) {
8734
- if (toolCall.state === "running") indeterminateToolCallIds.push(toolCall.id);
9517
+ if (toolCall.status === "in_progress" || toolCall.state === "in_progress") indeterminateToolCallIds.push(toolCall.id);
8735
9518
  this.interruptToolCall(run, toolCall, message);
8736
9519
  }
8737
9520
  const failure = indeterminateToolCallIds.length > 0 ? { code: "tool_indeterminate", message, toolCallIds: indeterminateToolCallIds } : { code: "runtime_interrupted", message, ownerEpoch };
@@ -8991,7 +9774,7 @@ var InMemoryStore = class _InMemoryStore {
8991
9774
  run.invalidatedBy = { messageId: original.id, messageRevision: actualRevision + 1 };
8992
9775
  if (run.state === "running" || run.state === "queued" || run.state === "input_required") {
8993
9776
  if (run.state === "running") {
8994
- const openToolCalls = [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.state === "pending" || toolCall.state === "running"));
9777
+ const openToolCalls = [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.status === "pending" || toolCall.status === "in_progress" || toolCall.state === "pending" || toolCall.state === "in_progress"));
8995
9778
  for (const toolCall of openToolCalls) this.interruptToolCall(run, toolCall, "Run superseded by Message revision.");
8996
9779
  } else {
8997
9780
  delete run.openInteractions;
@@ -9087,7 +9870,7 @@ var InMemoryStore = class _InMemoryStore {
9087
9870
  if (input.phasePayload !== void 0) assertJsonValue(input.phasePayload, "run.phasePayload");
9088
9871
  if (input.entryPhases !== void 0) assertEntryPhases(input.entryPhases, "run.entryPhases");
9089
9872
  const scope = createIdempotencyScope("start_run", input.agentId, input.idempotencyKey);
9090
- const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken, input.entryPhases);
9873
+ const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken, input.entryPhases, input.scope, input.cwd);
9091
9874
  const replay = this.replay(scope, payload);
9092
9875
  if (replay) return clone(replay);
9093
9876
  const waitingRun = isControlMetadata(input.metadata) ? void 0 : [...this.runs.values()].find((candidate) => candidate.agentId === input.agentId && candidate.state === "input_required");
@@ -9143,6 +9926,8 @@ var InMemoryStore = class _InMemoryStore {
9143
9926
  state: "queued",
9144
9927
  input: clone(normalizedInput),
9145
9928
  ...input.metadata ? { metadata: clone(input.metadata) } : {},
9929
+ ...input.scope !== void 0 ? { scope: clone(input.scope) } : {},
9930
+ ...input.cwd !== void 0 ? { cwd: input.cwd } : {},
9146
9931
  ...input.phasePayload === void 0 ? {} : { phasePayload: clone(input.phasePayload) },
9147
9932
  ...input.entryPhases === void 0 ? {} : { entryPhases: clone(input.entryPhases) },
9148
9933
  ...input.pinnedConfigToken === void 0 ? {} : { pinnedConfigToken: input.pinnedConfigToken },
@@ -9418,19 +10203,30 @@ var InMemoryStore = class _InMemoryStore {
9418
10203
  this.assertExecution(run, input.execution, input.expectedRevision);
9419
10204
  if (this.messages.has(input.requestMessageId)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9420
10205
  const timestamp = createTimestamp();
9421
- const toolCalls = input.calls.map((call) => ({
9422
- id: call.toolCallId ?? createId("tool"),
9423
- providerToolCallId: call.providerToolCallId,
9424
- agentId: run.agentId,
9425
- runId: run.id,
9426
- executionId: input.execution.executionId,
9427
- requestMessageId: input.requestMessageId,
9428
- name: call.name,
9429
- args: clone(call.args),
9430
- state: "pending",
9431
- createdAt: timestamp,
9432
- updatedAt: timestamp
9433
- }));
10206
+ const toolCalls = input.calls.map((call) => {
10207
+ const toolCallId = call.providerToolCallId || (call.toolCallId ?? createId("tool"));
10208
+ return {
10209
+ id: call.toolCallId ?? createId("tool"),
10210
+ toolCallId,
10211
+ ...call.providerToolCallId ? { providerToolCallId: call.providerToolCallId } : {},
10212
+ agentId: run.agentId,
10213
+ runId: run.id,
10214
+ executionId: input.execution.executionId,
10215
+ requestMessageId: input.requestMessageId,
10216
+ name: call.name,
10217
+ title: call.title ?? call.name,
10218
+ kind: call.kind ?? "other",
10219
+ status: "pending",
10220
+ state: "pending",
10221
+ ...call.content ? { content: clone(call.content) } : {},
10222
+ ...call.locations ? { locations: clone(call.locations) } : {},
10223
+ rawInput: clone(call.args),
10224
+ ...call._meta ? { _meta: clone(call._meta) } : {},
10225
+ args: clone(call.args),
10226
+ createdAt: timestamp,
10227
+ updatedAt: timestamp
10228
+ };
10229
+ });
9434
10230
  for (const toolCall of toolCalls) {
9435
10231
  if (this.toolCalls.has(toolCall.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9436
10232
  }
@@ -9466,15 +10262,27 @@ var InMemoryStore = class _InMemoryStore {
9466
10262
  const run = this.requireRun(input.runId);
9467
10263
  this.assertExecution(run, input.execution, input.expectedRevision);
9468
10264
  const toolCall = this.requireToolCall(input.toolCallId, run);
9469
- if (toolCall.state !== "pending") throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9470
- toolCall.state = "running";
10265
+ if (toolCall.status !== "pending" && toolCall.state !== "pending") throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
10266
+ const from = toolCall.status ?? "pending";
10267
+ toolCall.status = "in_progress";
10268
+ toolCall.state = "in_progress";
9471
10269
  toolCall.executionId = input.execution.executionId;
10270
+ if (input.title !== void 0) toolCall.title = input.title;
10271
+ if (input.kind !== void 0) toolCall.kind = input.kind;
10272
+ if (input.locations !== void 0) toolCall.locations = clone(input.locations);
10273
+ if (input.content !== void 0) toolCall.content = clone(input.content);
10274
+ if (input._meta !== void 0) {
10275
+ toolCall._meta = {
10276
+ ...toolCall._meta ?? {},
10277
+ ...clone(input._meta)
10278
+ };
10279
+ }
9472
10280
  toolCall.updatedAt = createTimestamp();
9473
10281
  this.storeToolCall(toolCall);
9474
10282
  run.revision += 1;
9475
10283
  run.updatedAt = toolCall.updatedAt;
9476
10284
  this.touchRun(run);
9477
- this.appendToolTransition(run, { from: "pending", to: "running" }, toolCall);
10285
+ this.appendToolTransition(run, { from, to: "in_progress" }, toolCall);
9478
10286
  const result = { run: clone(run), toolCall: clone(toolCall) };
9479
10287
  this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
9480
10288
  return result;
@@ -9488,16 +10296,18 @@ var InMemoryStore = class _InMemoryStore {
9488
10296
  const run = this.requireRun(input.runId);
9489
10297
  this.assertExecution(run, input.execution, input.expectedRevision);
9490
10298
  const toolCall = this.requireToolCall(input.toolCallId, run);
9491
- if (toolCall.state !== "running" || toolCall.executionId !== input.execution.executionId) {
10299
+ if (toolCall.status !== "in_progress" && toolCall.state !== "in_progress" || toolCall.executionId !== input.execution.executionId) {
9492
10300
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9493
10301
  }
10302
+ const from = toolCall.status ?? "in_progress";
10303
+ toolCall.status = "pending";
9494
10304
  toolCall.state = "pending";
9495
10305
  toolCall.updatedAt = createTimestamp();
9496
10306
  this.storeToolCall(toolCall);
9497
10307
  run.revision += 1;
9498
10308
  run.updatedAt = toolCall.updatedAt;
9499
10309
  this.touchRun(run);
9500
- this.appendToolTransition(run, { from: "running", to: "pending" }, toolCall);
10310
+ this.appendToolTransition(run, { from, to: "pending" }, toolCall);
9501
10311
  const result = { run: clone(run), toolCall: clone(toolCall) };
9502
10312
  this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
9503
10313
  return result;
@@ -9506,8 +10316,9 @@ var InMemoryStore = class _InMemoryStore {
9506
10316
  this.assertOwner(lease);
9507
10317
  assertToolExecutionResult(input.result);
9508
10318
  assertToolValue(input.result, "tool.result");
9509
- if (input.state === "completed" && !input.result.ok) throw new TypeError("completed ToolCall requires a successful result");
9510
- if (input.state !== "completed" && input.result.ok) throw new TypeError(`${input.state} ToolCall requires a failed result`);
10319
+ const isCompleted = "ok" in input.result ? input.result.ok : !input.result.isError;
10320
+ if (input.state === "completed" && !isCompleted) throw new TypeError("completed ToolCall requires a successful result");
10321
+ if (input.state !== "completed" && isCompleted) throw new TypeError(`${input.state} ToolCall requires a failed result`);
9511
10322
  if (input.state === "indeterminate" && (!input.reason || input.reason.trim().length === 0)) throw new TypeError("indeterminate ToolCall requires a reason");
9512
10323
  const operationKey = `tool_commit:${input.toolCallId}`;
9513
10324
  const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.execution.executionId, input.state, input.result, input.reason ?? null]);
@@ -9517,10 +10328,9 @@ var InMemoryStore = class _InMemoryStore {
9517
10328
  this.assertExecution(run, input.execution, input.expectedRevision);
9518
10329
  const toolCall = this.requireToolCall(input.toolCallId, run);
9519
10330
  if (toolCall.executionId !== input.execution.executionId) throw new RuntimeError("runtime_ownership_lost", { reason: "epoch_advanced", expectedEpoch: input.execution.ownerEpoch, actualEpoch: this.ownerEpoch });
9520
- const from = toolCall.state;
9521
- if (from === "pending" && input.state !== "failed") throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9522
- if (from !== "pending" && from !== "running") throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9523
- const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name, ...clone(input.result) };
10331
+ const fromStatus = toolCall.status ?? (toolCall.state === "in_progress" ? "in_progress" : toolCall.state);
10332
+ const toStatus = input.state === "completed" ? "completed" : "failed";
10333
+ const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name ?? toolCall.title, ...clone(input.result) };
9524
10334
  const resultMessageId = createId("msg");
9525
10335
  const message = {
9526
10336
  id: resultMessageId,
@@ -9532,7 +10342,21 @@ var InMemoryStore = class _InMemoryStore {
9532
10342
  createdAt: createTimestamp()
9533
10343
  };
9534
10344
  this.storeMessage(message);
9535
- toolCall.state = input.state;
10345
+ toolCall.status = toStatus;
10346
+ toolCall.state = toStatus;
10347
+ if (input.title !== void 0) toolCall.title = input.title;
10348
+ if (input.kind !== void 0) toolCall.kind = input.kind;
10349
+ if (input.locations !== void 0) toolCall.locations = clone(input.locations);
10350
+ if (input.content !== void 0) toolCall.content = clone(input.content);
10351
+ else if (toolCall.content === void 0) toolCall.content = defaultToolCallContent(input.result);
10352
+ if (input.rawOutput !== void 0) toolCall.rawOutput = clone(input.rawOutput);
10353
+ else if (toolCall.rawOutput === void 0) toolCall.rawOutput = "structuredContent" in input.result && input.result.structuredContent !== void 0 ? clone(input.result.structuredContent) : clone(input.result.content);
10354
+ if (input._meta !== void 0) {
10355
+ toolCall._meta = {
10356
+ ...toolCall._meta ?? {},
10357
+ ...clone(input._meta)
10358
+ };
10359
+ }
9536
10360
  toolCall.result = durableResult;
9537
10361
  toolCall.resultMessageId = resultMessageId;
9538
10362
  if (input.state === "indeterminate") toolCall.reason = input.reason;
@@ -9542,13 +10366,13 @@ var InMemoryStore = class _InMemoryStore {
9542
10366
  run.revision += 1;
9543
10367
  run.updatedAt = message.createdAt;
9544
10368
  this.touchRun(run);
9545
- this.appendToolTransition(run, { from, to: input.state }, toolCall);
10369
+ this.appendToolTransition(run, { from: fromStatus, to: toStatus }, toolCall);
9546
10370
  this.appendMessage(run, message);
9547
10371
  if (input.state === "indeterminate") {
9548
- const openToolCalls = [...this.toolCalls.values()].filter((candidate) => candidate.runId === run.id && candidate.executionId === input.execution.executionId && candidate.id !== toolCall.id && (candidate.state === "pending" || candidate.state === "running"));
10372
+ const openToolCalls = [...this.toolCalls.values()].filter((candidate) => candidate.runId === run.id && candidate.executionId === input.execution.executionId && candidate.id !== toolCall.id && (candidate.status === "pending" || candidate.status === "in_progress" || candidate.state === "pending" || candidate.state === "in_progress"));
9549
10373
  const indeterminateToolCallIds = [toolCall.id];
9550
10374
  for (const candidate of openToolCalls) {
9551
- if (candidate.state === "running") indeterminateToolCallIds.push(candidate.id);
10375
+ if (candidate.status === "in_progress" || candidate.state === "in_progress") indeterminateToolCallIds.push(candidate.id);
9552
10376
  this.interruptToolCall(run, candidate, input.reason);
9553
10377
  }
9554
10378
  const failure = {
@@ -9627,14 +10451,14 @@ var InMemoryStore = class _InMemoryStore {
9627
10451
  return result2;
9628
10452
  }
9629
10453
  const from = run.state;
9630
- const activeToolCalls = run.execution ? [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.state === "pending" || toolCall.state === "running")) : run.state === "input_required" && run.checkpoint?.data && typeof run.checkpoint.data === "object" && !Array.isArray(run.checkpoint.data) ? (() => {
10454
+ const activeToolCalls = run.execution ? [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.executionId === run.execution?.executionId && (toolCall.status === "pending" || toolCall.status === "in_progress" || toolCall.state === "pending" || toolCall.state === "in_progress")) : run.state === "input_required" && run.checkpoint?.data && typeof run.checkpoint.data === "object" && !Array.isArray(run.checkpoint.data) ? (() => {
9631
10455
  const interactionState = run.checkpoint.data.runInteractions;
9632
10456
  const interactionCheckpoint = interactionState && typeof interactionState === "object" && !Array.isArray(interactionState) ? interactionState.checkpoint : void 0;
9633
10457
  const ids = interactionCheckpoint && typeof interactionCheckpoint === "object" && !Array.isArray(interactionCheckpoint) ? interactionCheckpoint.toolCallIds : void 0;
9634
10458
  const allowed = new Set(Array.isArray(ids) ? ids : []);
9635
- return [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && toolCall.state === "pending" && allowed.has(toolCall.id));
10459
+ return [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && (toolCall.status === "pending" || toolCall.state === "pending") && allowed.has(toolCall.id));
9636
10460
  })() : [];
9637
- const indeterminateToolCallIds = activeToolCalls.filter((toolCall) => toolCall.state === "running").map((toolCall) => toolCall.id);
10461
+ const indeterminateToolCallIds = activeToolCalls.filter((toolCall) => toolCall.status === "in_progress" || toolCall.state === "in_progress").map((toolCall) => toolCall.id);
9638
10462
  for (const toolCall of activeToolCalls) this.interruptToolCall(run, toolCall, input.reason ?? "The Run was cancelled.");
9639
10463
  if (input.output && (typeof input.output.content === "string" ? input.output.content.length > 0 : input.output.content.length > 0)) {
9640
10464
  const output = { ...clone(input.output), sequenceWithinRun: this.nextMessageSequence(run.id) };
@@ -9681,6 +10505,74 @@ var InMemoryStore = class _InMemoryStore {
9681
10505
  const result = clone(run);
9682
10506
  return result;
9683
10507
  }
10508
+ recordToolCall(lease, input) {
10509
+ this.assertOwner(lease);
10510
+ const run = this.requireRun(input.runId);
10511
+ if (input.execution) {
10512
+ if (run.state !== "running" || !run.execution || run.execution.executionId !== input.execution.executionId || run.execution.ownerEpoch !== input.execution.ownerEpoch) {
10513
+ throw new RuntimeError("runtime_ownership_lost", {
10514
+ reason: "epoch_advanced",
10515
+ expectedEpoch: input.execution.ownerEpoch,
10516
+ actualEpoch: this.ownerEpoch
10517
+ });
10518
+ }
10519
+ }
10520
+ const toolCallId = input.update.toolCallId;
10521
+ const existing = this.toolCalls.get(toolCallId);
10522
+ const fromStatus = existing ? existing.status : null;
10523
+ const toStatus = input.update.status ?? (existing ? existing.status : "in_progress");
10524
+ const timestamp = createTimestamp();
10525
+ let stored;
10526
+ if (existing) {
10527
+ mergeToolCall(existing, input.update);
10528
+ existing.status = toStatus;
10529
+ existing.state = toStatus;
10530
+ if (input.external !== void 0) existing.external = input.external;
10531
+ existing.updatedAt = timestamp;
10532
+ stored = existing;
10533
+ } else {
10534
+ stored = {
10535
+ id: toolCallId,
10536
+ toolCallId,
10537
+ providerToolCallId: toolCallId,
10538
+ agentId: run.agentId,
10539
+ runId: run.id,
10540
+ executionId: input.execution?.executionId,
10541
+ title: "title" in input.update && input.update.title ? input.update.title : "external_tool",
10542
+ kind: "kind" in input.update && input.update.kind ? input.update.kind : "other",
10543
+ status: toStatus,
10544
+ state: toStatus,
10545
+ external: input.external ?? false,
10546
+ createdAt: timestamp,
10547
+ updatedAt: timestamp
10548
+ };
10549
+ mergeToolCall(stored, input.update);
10550
+ this.toolCalls.set(stored.id, stored);
10551
+ }
10552
+ this.storeToolCall(stored);
10553
+ run.revision += 1;
10554
+ run.updatedAt = timestamp;
10555
+ this.touchRun(run);
10556
+ const acpToolCall = {
10557
+ toolCallId: stored.toolCallId,
10558
+ title: stored.title,
10559
+ kind: stored.kind,
10560
+ status: stored.status,
10561
+ ...stored.content !== void 0 ? { content: clone(stored.content) } : {},
10562
+ ...stored.locations !== void 0 ? { locations: clone(stored.locations) } : {},
10563
+ ...stored.rawInput !== void 0 ? { rawInput: clone(stored.rawInput) } : {},
10564
+ ...stored.rawOutput !== void 0 ? { rawOutput: clone(stored.rawOutput) } : {},
10565
+ ...stored._meta !== void 0 ? { _meta: clone(stored._meta) } : {}
10566
+ };
10567
+ const event = this.baseEvent(run, {
10568
+ kind: "tool_state_changed",
10569
+ transition: { from: fromStatus, to: toStatus },
10570
+ toolCall: acpToolCall,
10571
+ ...input.external ? { external: true } : {}
10572
+ });
10573
+ this.appendEvent(event);
10574
+ return event;
10575
+ }
9684
10576
  snapshotRun(lease, runId) {
9685
10577
  this.assertOwner(lease);
9686
10578
  const run = this.requireRun(runId);
@@ -9691,6 +10583,8 @@ var InMemoryStore = class _InMemoryStore {
9691
10583
  revision: run.revision,
9692
10584
  input: clone(run.input),
9693
10585
  ...run.metadata ? { metadata: clone(run.metadata) } : {},
10586
+ ...run.scope !== void 0 ? { scope: clone(run.scope) } : {},
10587
+ ...run.cwd !== void 0 ? { cwd: run.cwd } : {},
9694
10588
  ...run.phasePayload === void 0 ? {} : { phasePayload: clone(run.phasePayload) },
9695
10589
  ...run.entryPhases === void 0 ? {} : { entryPhases: clone(run.entryPhases) },
9696
10590
  messageCount: this.messagesForRun(run.id).length,
@@ -9777,7 +10671,7 @@ var InMemoryStore = class _InMemoryStore {
9777
10671
  if (run.state === "running") blockedRunIds.add(run.id);
9778
10672
  }
9779
10673
  for (const tool of this.toolCalls.values()) {
9780
- if (tool.state === "pending" || tool.state === "running" || tool.state === "indeterminate") {
10674
+ if (tool.status === "pending" || tool.status === "in_progress" || tool.state === "pending" || tool.state === "in_progress") {
9781
10675
  blockedRunIds.add(tool.runId);
9782
10676
  }
9783
10677
  }
@@ -9918,13 +10812,13 @@ var InMemoryStore = class _InMemoryStore {
9918
10812
  }
9919
10813
  assertNoOpenTools(run, allowedPendingToolCallIds = []) {
9920
10814
  const allowed = new Set(allowedPendingToolCallIds);
9921
- if ([...this.toolCalls.values()].some((toolCall) => toolCall.runId === run.id && (toolCall.state === "running" || toolCall.state === "pending" && !allowed.has(toolCall.id)))) {
10815
+ if ([...this.toolCalls.values()].some((toolCall) => toolCall.runId === run.id && (toolCall.status === "in_progress" || toolCall.state === "in_progress" || (toolCall.status === "pending" || toolCall.state === "pending") && !allowed.has(toolCall.id)))) {
9922
10816
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9923
10817
  }
9924
10818
  }
9925
10819
  interruptToolCall(run, toolCall, reason) {
9926
10820
  const result = { ok: false, content: null, error: reason };
9927
- const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name, ...result };
10821
+ const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name ?? toolCall.title, ...result };
9928
10822
  const message = {
9929
10823
  id: createId("msg"),
9930
10824
  agentId: run.agentId,
@@ -9934,13 +10828,14 @@ var InMemoryStore = class _InMemoryStore {
9934
10828
  sequenceWithinRun: this.nextMessageSequence(run.id),
9935
10829
  createdAt: createTimestamp()
9936
10830
  };
9937
- const from = toolCall.state;
9938
- const to = from === "running" ? "indeterminate" : "failed";
10831
+ const from = toolCall.status ?? toolCall.state ?? "in_progress";
10832
+ const to = "failed";
9939
10833
  this.storeMessage(message);
9940
10834
  toolCall.state = to;
10835
+ toolCall.status = to;
9941
10836
  toolCall.result = durableResult;
9942
10837
  toolCall.resultMessageId = message.id;
9943
- if (to === "indeterminate") toolCall.reason = reason;
10838
+ toolCall.reason = reason;
9944
10839
  toolCall.updatedAt = message.createdAt;
9945
10840
  this.storeToolCall(toolCall);
9946
10841
  run.revision += 1;
@@ -10101,11 +10996,24 @@ ${record.summary}`,
10101
10996
  this.appendEvent(transition);
10102
10997
  }
10103
10998
  appendToolTransition(run, transition, toolCall) {
10104
- this.appendEvent(this.baseEvent(run, {
10999
+ const acpToolCall = {
11000
+ toolCallId: toolCall.toolCallId ?? toolCall.id,
11001
+ title: toolCall.title ?? toolCall.name ?? toolCall.toolCallId,
11002
+ kind: toolCall.kind ?? "other",
11003
+ status: toolCall.status ?? toolCall.state,
11004
+ ...toolCall.content ? { content: clone(toolCall.content) } : {},
11005
+ ...toolCall.locations ? { locations: clone(toolCall.locations) } : {},
11006
+ ...toolCall.rawInput !== void 0 ? { rawInput: clone(toolCall.rawInput) } : {},
11007
+ ...toolCall.rawOutput !== void 0 ? { rawOutput: clone(toolCall.rawOutput) } : {},
11008
+ ...toolCall._meta ? { _meta: clone(toolCall._meta) } : {}
11009
+ };
11010
+ this.appendEvent({
11011
+ ...this.baseEvent(run),
10105
11012
  kind: "tool_state_changed",
10106
11013
  transition,
10107
- toolCall: clone(toolCall)
10108
- }));
11014
+ toolCall: acpToolCall,
11015
+ ...toolCall.external ? { external: true } : {}
11016
+ });
10109
11017
  }
10110
11018
  baseEvent(run, extra) {
10111
11019
  this.eventSequence += 1;
@@ -10207,6 +11115,9 @@ var MemoryOwnedStore = class {
10207
11115
  async cancelRun(input) {
10208
11116
  return this.store.cancelRun(this.lease, input);
10209
11117
  }
11118
+ async recordToolCall(input) {
11119
+ return this.store.recordToolCall(this.lease, input);
11120
+ }
10210
11121
  async snapshotRun(runId) {
10211
11122
  return this.store.snapshotRun(this.lease, runId);
10212
11123
  }
@@ -10350,19 +11261,19 @@ function requestMessageContent(toolCalls, contentBlocks) {
10350
11261
  const toolUse = (toolCall) => ({
10351
11262
  type: "tool_use",
10352
11263
  toolCallId: toolCall.id,
10353
- providerToolCallId: toolCall.providerToolCallId,
10354
- name: toolCall.name,
10355
- input: clone(toolCall.args)
11264
+ ...toolCall.providerToolCallId !== void 0 ? { providerToolCallId: toolCall.providerToolCallId } : {},
11265
+ name: toolCall.name ?? toolCall.title ?? "tool",
11266
+ input: clone(toolCall.args !== void 0 ? toolCall.args : toolCall.rawInput !== void 0 ? toolCall.rawInput : {})
10356
11267
  });
10357
11268
  if (!contentBlocks) return toolCalls.map(toolUse);
10358
- const reserved = new Map(toolCalls.map((toolCall) => [toolCall.providerToolCallId, toolCall]));
11269
+ const reserved = new Map(toolCalls.map((toolCall) => [toolCall.providerToolCallId ?? toolCall.id, toolCall]));
10359
11270
  const parts = [];
10360
11271
  for (const block of contentBlocks) {
10361
11272
  if (block.type === "text") {
10362
11273
  parts.push({ type: "text", text: block.text });
10363
11274
  } else if (block.type === "thinking") {
10364
11275
  parts.push({ type: "thinking", thinking: block.thinking, ...block.signature ? { signature: block.signature } : {} });
10365
- } else {
11276
+ } else if (block.type === "tool_call") {
10366
11277
  const toolCall = reserved.get(block.id);
10367
11278
  if (!toolCall) continue;
10368
11279
  reserved.delete(block.id);
@@ -10759,6 +11670,9 @@ var SqliteStore = class {
10759
11670
  async cancelRun(lease, input) {
10760
11671
  return this.invoke(lease, (store, current) => store.cancelRun(current, input));
10761
11672
  }
11673
+ async recordToolCall(lease, input) {
11674
+ return this.invoke(lease, (store, current) => store.recordToolCall(current, input));
11675
+ }
10762
11676
  async snapshotRun(lease, runId) {
10763
11677
  return this.invoke(lease, (store, current) => store.snapshotRun(current, runId), false);
10764
11678
  }
@@ -11059,7 +11973,9 @@ var SqliteStore = class {
11059
11973
  }
11060
11974
  getStmtInsertEvent() {
11061
11975
  return this.stmtInsertEvent ??= this.database.prepare(
11062
- "INSERT INTO run_events (id, run_id, payload_json, created_at) VALUES (?, ?, ?, ?)"
11976
+ `INSERT INTO run_events (id, run_id, payload_json, created_at)
11977
+ VALUES (?, ?, ?, ?)
11978
+ ON CONFLICT(id) DO UPDATE SET payload_json = excluded.payload_json`
11063
11979
  );
11064
11980
  }
11065
11981
  getStmtDeleteEvent() {
@@ -11142,7 +12058,7 @@ var SqliteStore = class {
11142
12058
  toolCall.id,
11143
12059
  toolCall.runId,
11144
12060
  toolCall.executionId,
11145
- toolCall.state,
12061
+ toolCall.status ?? toolCall.state ?? "in_progress",
11146
12062
  JSON.stringify(toolCall)
11147
12063
  );
11148
12064
  }
@@ -11530,6 +12446,9 @@ var SqliteOwnedStore = class {
11530
12446
  cancelRun(input) {
11531
12447
  return this.store.cancelRun(this.lease, input);
11532
12448
  }
12449
+ recordToolCall(input) {
12450
+ return this.store.recordToolCall(this.lease, input);
12451
+ }
11533
12452
  snapshotRun(runId) {
11534
12453
  return this.store.snapshotRun(this.lease, runId);
11535
12454
  }
@@ -11615,10 +12534,10 @@ export {
11615
12534
  isRuntimeError,
11616
12535
  loadExtensionsFromPath as loadExtensions,
11617
12536
  loadPhase,
11618
- loadPhaseSettings,
11619
12537
  loadPhases,
11620
12538
  loadSkill,
11621
12539
  loadSkills,
12540
+ mergeToolCall,
11622
12541
  parseAgentDefinition,
11623
12542
  parseFrontmatter,
11624
12543
  resolveConfigurationSnapshot