@rowan-agent/agent 0.17.0 → 0.19.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,25 @@ 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.");
1701
- }
1702
- if (settingsProvider) {
1703
- throw new Error("A Phase may register only one Settings provider.");
1704
- }
1705
- settingsProvider = provider;
1706
- options?.registerSettings?.(provider);
1801
+ getMessage: () => outputMessage
1802
+ },
1803
+ host: {
1804
+ contribute: (contribution) => {
1805
+ assertActive();
1806
+ if (!contribution || typeof contribution !== "object" || Array.isArray(contribution)) {
1807
+ throw new TypeError("Host contribution must be an object.");
1808
+ }
1809
+ if (typeof contribution.kind !== "string" || contribution.kind.trim() === "") {
1810
+ throw new TypeError("Host contribution requires a non-empty kind.");
1707
1811
  }
1812
+ if (typeof contribution.id !== "string" || contribution.id.trim() === "") {
1813
+ throw new TypeError("Host contribution requires a non-empty id.");
1814
+ }
1815
+ assertJsonValue(contribution, "Host contribution");
1816
+ const dispose = options?.contributeToHost ? options.contributeToHost(contribution) : () => {
1817
+ };
1818
+ trackCleanup?.(dispose);
1819
+ return dispose;
1708
1820
  }
1709
1821
  }
1710
1822
  };
@@ -1713,8 +1825,8 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1713
1825
  // src/harness/phases/interactions.ts
1714
1826
  var RunInteractionCancelledError = class extends Error {
1715
1827
  code = "run_interaction_cancelled";
1716
- constructor() {
1717
- super("Run interaction work was cancelled.");
1828
+ constructor(message = "Run interaction work was cancelled.") {
1829
+ super(message);
1718
1830
  this.name = "RunInteractionCancelledError";
1719
1831
  }
1720
1832
  };
@@ -2185,17 +2297,6 @@ async function loadPhase(targetPath) {
2185
2297
  }
2186
2298
  return phase;
2187
2299
  }
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
2300
  async function loadPhaseSkills(baseDir) {
2200
2301
  const entries = await readdir2(baseDir, { withFileTypes: true });
2201
2302
  const skills = [];
@@ -2637,9 +2738,11 @@ function extractToolResults(transcript) {
2637
2738
  }
2638
2739
  for (const part of msg.content) {
2639
2740
  if (part.type !== "tool_result") continue;
2640
- const result = parseToolResult(part.content);
2641
- if (result) {
2642
- results.push(result);
2741
+ if (typeof part.content === "string") {
2742
+ const result = parseToolResult(part.content);
2743
+ if (result) {
2744
+ results.push(result);
2745
+ }
2643
2746
  }
2644
2747
  }
2645
2748
  }
@@ -3488,10 +3591,10 @@ async function executePhase(ctx) {
3488
3591
  });
3489
3592
  await phase.factory(api);
3490
3593
  return {
3491
- message: api.phase.getMessage() ?? `${phase.name} phase completed.`,
3492
- route: api.phase.getNextPhase() || "stop",
3594
+ message: api.phases.getMessage() ?? `${phase.name} phase completed.`,
3595
+ route: api.phases.getNextPhase() || "stop",
3493
3596
  phase: phase.name,
3494
- payload: api.phase.getPayload()
3597
+ payload: api.phases.getPayload()
3495
3598
  };
3496
3599
  }
3497
3600
  return executePhaseWithModel(ctx);
@@ -3503,7 +3606,8 @@ async function executeToolCall(input) {
3503
3606
  config: input.config,
3504
3607
  toolCall: input.toolCall,
3505
3608
  contentBlocks: input.contentBlocks,
3506
- driver: input.driver
3609
+ driver: input.driver,
3610
+ options: input.options
3507
3611
  });
3508
3612
  } else {
3509
3613
  const toolContext = {
@@ -3516,7 +3620,7 @@ async function executeToolCall(input) {
3516
3620
  toolContext,
3517
3621
  beforeToolCall: input.config.beforeToolCall,
3518
3622
  afterToolCall: input.config.afterToolCall,
3519
- signal: input.config.signal
3623
+ signal: combineSignals(input.config.signal, input.options?.signal)
3520
3624
  });
3521
3625
  }
3522
3626
  return {
@@ -3548,6 +3652,28 @@ async function executeToolCalls(input) {
3548
3652
  }
3549
3653
  function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
3550
3654
  const interaction = createRunInteractionDriver(state, phase.name, config.signal);
3655
+ const executePhaseTool = async (phaseContext, toolCall, options) => {
3656
+ return runTurn(async () => {
3657
+ await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
3658
+ const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
3659
+ const result = await executeToolCall({
3660
+ config: {
3661
+ ...config,
3662
+ context: {
3663
+ ...config.context,
3664
+ tools,
3665
+ skills: phaseContext.skills
3666
+ }
3667
+ },
3668
+ tools,
3669
+ toolCall,
3670
+ driver: interaction,
3671
+ options
3672
+ });
3673
+ await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
3674
+ return result;
3675
+ });
3676
+ };
3551
3677
  return {
3552
3678
  interaction,
3553
3679
  messages: messageManager,
@@ -3591,6 +3717,51 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3591
3717
  }));
3592
3718
  }
3593
3719
  }
3720
+ const scope = config.execution.scope ?? [];
3721
+ const cwd = config.execution.cwd ?? config.context.cwd ?? "";
3722
+ const callContext = {
3723
+ signal: config.signal ?? new AbortController().signal,
3724
+ run: {
3725
+ id: config.execution.runId,
3726
+ agentId: config.execution.agentId,
3727
+ scope,
3728
+ cwd
3729
+ },
3730
+ interact: async (interactionReq) => {
3731
+ const handler = config.runtime?.interact ?? config.interact;
3732
+ if (!handler) {
3733
+ throw new Error("Run interactions are not supported in this runtime.");
3734
+ }
3735
+ return handler(interactionReq);
3736
+ },
3737
+ tools: {
3738
+ list: () => {
3739
+ const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
3740
+ return tools.map((t) => ({
3741
+ name: t.name,
3742
+ description: t.description,
3743
+ parameters: t.parameters,
3744
+ ...t._meta !== void 0 ? { _meta: t._meta } : {}
3745
+ }));
3746
+ },
3747
+ call: async (name, args, options2) => {
3748
+ const toolCallId = createId("call");
3749
+ const toolCall = {
3750
+ id: toolCallId,
3751
+ name,
3752
+ args
3753
+ };
3754
+ const res = await executePhaseTool(phaseContext, toolCall, options2);
3755
+ return res.ok ? { ok: true, content: res.content } : { ok: false, error: res.error ?? "Tool execution failed", ...res.content !== void 0 ? { content: res.content } : {} };
3756
+ },
3757
+ report: (update) => {
3758
+ config.onToolReport?.({
3759
+ runId: config.execution.runId,
3760
+ update
3761
+ });
3762
+ }
3763
+ }
3764
+ };
3594
3765
  const result = await runTurn(
3595
3766
  () => withRetry(
3596
3767
  () => invokeModel({
@@ -3600,7 +3771,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3600
3771
  phaseId: phase.name,
3601
3772
  output: options.output,
3602
3773
  onThinkingDelta: config.onThinkingDelta,
3603
- onToolCallDelta: config.onToolCallDelta
3774
+ onToolCallDelta: config.onToolCallDelta,
3775
+ callContext
3604
3776
  }),
3605
3777
  {
3606
3778
  signal: config.signal,
@@ -3623,27 +3795,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3623
3795
  async reportStatus(status) {
3624
3796
  await config.onPhaseStatus?.(phase.name, status);
3625
3797
  },
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
- },
3798
+ executeTool: executePhaseTool,
3647
3799
  async executeTools(phaseContext, toolCalls, contentBlocks) {
3648
3800
  return runTurn(async () => {
3649
3801
  const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
@@ -3913,6 +4065,7 @@ async function executeOnce(input) {
3913
4065
  onModelTranscript: input.onModelTranscript,
3914
4066
  onPhaseEntered: input.onPhaseEntered,
3915
4067
  onPhaseStatus: input.onPhaseStatus,
4068
+ onToolReport: input.onToolReport,
3916
4069
  runtime: input.runtime,
3917
4070
  waitForInput: async (nextState, request) => {
3918
4071
  if (!nextState || !request) throw new Error("Input boundary did not include execution state.");
@@ -4003,6 +4156,46 @@ function isContinuation(value) {
4003
4156
  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
4157
  }
4005
4158
 
4159
+ // src/runtime/tool-call-merge.ts
4160
+ function isValidPresentationOutput(output) {
4161
+ if (typeof output !== "object" || output === null || Array.isArray(output)) return false;
4162
+ const o = output;
4163
+ if (o.title !== void 0 && typeof o.title !== "string") return false;
4164
+ if (o.locations !== void 0) {
4165
+ if (!Array.isArray(o.locations)) return false;
4166
+ for (const loc of o.locations) {
4167
+ if (typeof loc !== "object" || loc === null || typeof loc.path !== "string") return false;
4168
+ if (loc.line !== void 0 && typeof loc.line !== "number") return false;
4169
+ }
4170
+ }
4171
+ if (o.content !== void 0) {
4172
+ if (!Array.isArray(o.content)) return false;
4173
+ for (const c of o.content) {
4174
+ if (typeof c !== "object" || c === null || typeof c.type !== "string") return false;
4175
+ }
4176
+ }
4177
+ if (o._meta !== void 0) {
4178
+ if (typeof o._meta !== "object" || o._meta === null || Array.isArray(o._meta)) return false;
4179
+ }
4180
+ return true;
4181
+ }
4182
+ function mergeToolCall(target, patch) {
4183
+ if (patch.title !== void 0) target.title = patch.title;
4184
+ if (patch.kind !== void 0) target.kind = patch.kind;
4185
+ if (patch.status !== void 0) target.status = patch.status;
4186
+ if (patch.locations !== void 0) target.locations = structuredClone(patch.locations);
4187
+ if (patch.content !== void 0) target.content = structuredClone(patch.content);
4188
+ if (patch.rawInput !== void 0) target.rawInput = structuredClone(patch.rawInput);
4189
+ if (patch.rawOutput !== void 0) target.rawOutput = structuredClone(patch.rawOutput);
4190
+ if (patch._meta !== void 0) {
4191
+ target._meta = {
4192
+ ...target._meta ?? {},
4193
+ ...structuredClone(patch._meta)
4194
+ };
4195
+ }
4196
+ return target;
4197
+ }
4198
+
4006
4199
  // src/runtime/contracts.ts
4007
4200
  var THINKING_LEVELS = [
4008
4201
  "off",
@@ -4013,23 +4206,8 @@ var THINKING_LEVELS = [
4013
4206
  "xhigh",
4014
4207
  "max"
4015
4208
  ];
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;
4209
+ function thinkingLevelFromUserInput(input) {
4210
+ return typeof input === "string" ? void 0 : input.thinkingLevel;
4033
4211
  }
4034
4212
  var METADATA_LIMIT = 64 * 1024;
4035
4213
  var IDENTITY_LIMIT = 256;
@@ -4085,8 +4263,13 @@ function isAssistantContent(value) {
4085
4263
  }
4086
4264
  function normalizeUserInput(input) {
4087
4265
  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");
4266
+ if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata", "thinkingLevel"]) || !isUserContent(normalized.content)) {
4267
+ throw new TypeError("input must contain only valid UserContent");
4268
+ }
4089
4269
  if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
4270
+ if (normalized.thinkingLevel !== void 0 && !THINKING_LEVELS.includes(normalized.thinkingLevel)) {
4271
+ throw new TypeError("input.thinkingLevel must be a valid ThinkingLevel");
4272
+ }
4090
4273
  assertJsonValue(normalized, "input");
4091
4274
  return normalized;
4092
4275
  }
@@ -4094,9 +4277,16 @@ function isAssistantMessage(value) {
4094
4277
  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
4278
  }
4096
4279
  function isToolResult(value) {
4097
- if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
4280
+ if (!isRecord3(value)) return false;
4281
+ if (value.structuredContent !== void 0 && !isJsonValue(value.structuredContent)) return false;
4282
+ if (Array.isArray(value.content)) {
4283
+ if (value.isError !== void 0 && typeof value.isError !== "boolean") return false;
4284
+ return true;
4285
+ }
4286
+ if (!isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
4287
+ const keys = Object.keys(value).filter((k) => k !== "structuredContent").sort();
4098
4288
  const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
4099
- if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
4289
+ if (JSON.stringify(keys) !== JSON.stringify(expected)) return false;
4100
4290
  return value.ok || typeof value.error === "string";
4101
4291
  }
4102
4292
  function assertAgentConfiguration(config) {
@@ -4269,17 +4459,20 @@ function encodeIdempotencyScope(storeIncarnation, scope) {
4269
4459
  if (typeof storeIncarnation !== "string" || storeIncarnation.length === 0) throw new TypeError("storeIncarnation must be non-empty");
4270
4460
  return canonicalJson([storeIncarnation, ...scope]);
4271
4461
  }
4272
- function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken, entryPhases) {
4462
+ function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken, entryPhases, scope, cwd) {
4273
4463
  const normalized = normalizeUserInput(input);
4274
4464
  if (metadata !== void 0) assertUtf8ByteLimit(canonicalJson(metadata), METADATA_JSON_BYTES, "run.metadata");
4275
4465
  if (phasePayload !== void 0) assertUtf8ByteLimit(canonicalJson(phasePayload), MESSAGE_CONTENT_JSON_BYTES, "run.phasePayload");
4276
4466
  if (entryPhases !== void 0) assertUtf8ByteLimit(canonicalJson(entryPhases), MESSAGE_CONTENT_JSON_BYTES, "run.entryPhases");
4467
+ if (cwd !== void 0 && typeof cwd !== "string") throw new TypeError("run.cwd must be a string");
4277
4468
  return canonicalJson({
4278
4469
  input: normalized,
4279
4470
  ...metadata === void 0 ? {} : { metadata },
4280
4471
  ...phasePayload === void 0 ? {} : { phasePayload },
4281
4472
  ...entryPhases === void 0 ? {} : { entryPhases },
4282
- ...pinnedConfigToken === void 0 ? {} : { pinnedConfigToken }
4473
+ ...pinnedConfigToken === void 0 ? {} : { pinnedConfigToken },
4474
+ ...scope === void 0 ? {} : { scope },
4475
+ ...cwd === void 0 ? {} : { cwd }
4283
4476
  });
4284
4477
  }
4285
4478
 
@@ -4821,7 +5014,8 @@ function projectToolMessage(message, toolInteractionTexts) {
4821
5014
  id: message.id,
4822
5015
  role: message.role,
4823
5016
  content: message.content.map((part) => {
4824
- const rawResult = jsonText(part.result.content);
5017
+ const isError = "isError" in part.result ? Boolean(part.result.isError) : !part.result.ok;
5018
+ 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
5019
  const interactionTexts = toolInteractionTexts.get(String(part.toolCallId));
4826
5020
  let content = rawResult;
4827
5021
  if (interactionTexts && interactionTexts.length > 0) {
@@ -4834,7 +5028,7 @@ ${rawResult}`;
4834
5028
  type: "tool_result",
4835
5029
  toolUseId: part.providerToolCallId ?? String(part.toolCallId),
4836
5030
  content,
4837
- ...part.result.ok ? {} : { isError: true }
5031
+ ...isError ? { isError: true } : {}
4838
5032
  };
4839
5033
  }),
4840
5034
  createdAt: message.createdAt,
@@ -4866,12 +5060,16 @@ function projectAssistantContent(content) {
4866
5060
  });
4867
5061
  }
4868
5062
  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
- }));
5063
+ return content.map((part) => {
5064
+ const isError = "isError" in part.result ? Boolean(part.result.isError) : !part.result.ok;
5065
+ 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);
5066
+ return {
5067
+ type: "tool_result",
5068
+ toolUseId: part.providerToolCallId ?? part.toolCallId,
5069
+ content: textContent,
5070
+ ...isError ? { isError: true } : {}
5071
+ };
5072
+ });
4875
5073
  }
4876
5074
  function inertRunInteraction(signal) {
4877
5075
  const controller = signal ? void 0 : new AbortController();
@@ -4900,6 +5098,7 @@ function projectTool(tool, agentId, runId) {
4900
5098
  parameters: tool.parameters,
4901
5099
  ...tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {},
4902
5100
  ...tool.promptGuidelines ? { promptGuidelines: [...tool.promptGuidelines] } : {},
5101
+ ...tool._meta !== void 0 ? { _meta: tool._meta } : {},
4903
5102
  execute: async (args, context, signal) => {
4904
5103
  const result = await tool.execute(args, {
4905
5104
  agentId,
@@ -4909,7 +5108,8 @@ function projectTool(tool, agentId, runId) {
4909
5108
  reportProgress: () => void 0,
4910
5109
  interaction: context.interaction ?? inertRunInteraction(signal)
4911
5110
  }, signal ?? new AbortController().signal);
4912
- return { toolCallId: context.toolCallId, toolName: tool.name, ...result };
5111
+ const ok = "ok" in result && typeof result.ok === "boolean" ? result.ok : !result.isError;
5112
+ return { toolCallId: context.toolCallId, toolName: tool.name, ok, ...result };
4913
5113
  }
4914
5114
  };
4915
5115
  }
@@ -5004,20 +5204,38 @@ function resolveDefinitionContext(snapshot, assembled) {
5004
5204
  systemPrompt: [snapshot.definition.prompt, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
5005
5205
  tools,
5006
5206
  skills,
5007
- phases: { phases, entryPhaseId }
5207
+ phases: { phases, entryPhaseId },
5208
+ ...snapshot.cwd ? { cwd: snapshot.cwd } : {}
5008
5209
  };
5009
5210
  }
5010
5211
  function toLoopResult(result, toolCallId, toolName) {
5212
+ const ok = "ok" in result && typeof result.ok === "boolean" ? result.ok : !result.isError;
5213
+ const error = "error" in result && typeof result.error === "string" ? result.error : void 0;
5011
5214
  return {
5012
5215
  toolCallId,
5013
5216
  toolName,
5014
- ok: result.ok,
5217
+ ok,
5015
5218
  content: result.content,
5016
- ...!result.ok ? { error: result.error } : {}
5219
+ ...!ok && error ? { error } : {},
5220
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {},
5221
+ ...result.isError !== void 0 ? { isError: result.isError } : {}
5017
5222
  };
5018
5223
  }
5019
5224
  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." };
5225
+ if (Array.isArray(result.content)) {
5226
+ return {
5227
+ content: result.content,
5228
+ ...result.ok !== void 0 ? { ok: result.ok } : {},
5229
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {},
5230
+ ...result.isError !== void 0 ? { isError: result.isError } : !result.ok ? { isError: true } : {},
5231
+ ...result.error !== void 0 ? { error: result.error } : {}
5232
+ };
5233
+ }
5234
+ return {
5235
+ ...result.ok ? { ok: true } : { ok: false, error: result.error ?? "Extension hook rejected the Tool result." },
5236
+ content: result.content,
5237
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
5238
+ };
5021
5239
  }
5022
5240
 
5023
5241
  // src/runtime/transient-run-events.ts
@@ -5053,6 +5271,29 @@ var TransientRunEventSubscription = class {
5053
5271
  previous?.kind === "tool_call_delta" && event.kind === "tool_call_delta" && previous.executionId === event.executionId && previous.providerToolCallId === event.providerToolCallId
5054
5272
  ) {
5055
5273
  this.queue[this.queue.length - 1] = event;
5274
+ } else if (event.kind === "tool_state_changed") {
5275
+ const toolCallId = event.toolCall.toolCallId;
5276
+ let index = -1;
5277
+ for (let i = this.queue.length - 1; i >= 0; i -= 1) {
5278
+ const existing = this.queue[i];
5279
+ if (existing && existing.kind === "tool_state_changed" && existing.runId === event.runId && existing.toolCall.toolCallId === toolCallId) {
5280
+ index = i;
5281
+ break;
5282
+ }
5283
+ }
5284
+ if (index !== -1) {
5285
+ const existing = this.queue[index];
5286
+ const mergedCall = { ...existing.toolCall };
5287
+ mergeToolCall(mergedCall, event.toolCall);
5288
+ this.queue[index] = {
5289
+ ...existing,
5290
+ transition: { from: existing.transition.from, to: event.transition.to },
5291
+ toolCall: mergedCall
5292
+ };
5293
+ } else {
5294
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
5295
+ this.queue.push(event);
5296
+ }
5056
5297
  } else {
5057
5298
  if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
5058
5299
  this.queue.push(event);
@@ -5068,7 +5309,7 @@ var TransientRunEventSubscription = class {
5068
5309
  else {
5069
5310
  for (let index = this.queue.length - 1; index >= 0; index -= 1) {
5070
5311
  const event = this.queue[index];
5071
- if (event?.executionId === executionId && !(preserveCompletedPhaseStatus && event.kind === "phase_status" && event.status.state === "completed")) {
5312
+ if (event && "executionId" in event && event.executionId === executionId && !(preserveCompletedPhaseStatus && event.kind === "phase_status" && event.status.state === "completed")) {
5072
5313
  this.queue.splice(index, 1);
5073
5314
  }
5074
5315
  }
@@ -5085,7 +5326,7 @@ var TransientRunEventSubscription = class {
5085
5326
  clearTool(toolCallId) {
5086
5327
  for (let index = this.queue.length - 1; index >= 0; index -= 1) {
5087
5328
  const event = this.queue[index];
5088
- if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
5329
+ if (event?.kind === "tool_state_changed" && event.toolCall.toolCallId === toolCallId) this.queue.splice(index, 1);
5089
5330
  }
5090
5331
  this.notify();
5091
5332
  }
@@ -5229,6 +5470,7 @@ var HooksManager = class {
5229
5470
  var InMemoryExtensionHost = class {
5230
5471
  configs = /* @__PURE__ */ new Map();
5231
5472
  agentState = /* @__PURE__ */ new Map();
5473
+ globalState = /* @__PURE__ */ new Map();
5232
5474
  listeners = /* @__PURE__ */ new Set();
5233
5475
  constructor(options = {}) {
5234
5476
  if (options.configs) {
@@ -5255,8 +5497,18 @@ var InMemoryExtensionHost = class {
5255
5497
  this.agentState.set(agentId, agentMap);
5256
5498
  }
5257
5499
  }
5500
+ if (options.globalState) {
5501
+ for (const [extId, stateMap] of Object.entries(options.globalState)) {
5502
+ const map = /* @__PURE__ */ new Map();
5503
+ for (const [key, val] of Object.entries(stateMap)) {
5504
+ assertJsonValue(val);
5505
+ map.set(key, structuredClone(val));
5506
+ }
5507
+ this.globalState.set(extId, map);
5508
+ }
5509
+ }
5258
5510
  }
5259
- setConfig(extensionId, config, scope = { kind: "global" }) {
5511
+ setConfig(extensionId, config, scope = []) {
5260
5512
  const scopeKey = scopeToKey(scope);
5261
5513
  if (!this.configs.has(scopeKey)) {
5262
5514
  this.configs.set(scopeKey, /* @__PURE__ */ new Map());
@@ -5270,19 +5522,18 @@ var InMemoryExtensionHost = class {
5270
5522
  }
5271
5523
  this.notifyConfigChanged(extensionId, scope);
5272
5524
  }
5273
- getConfig(extensionId, scope = { kind: "global" }) {
5525
+ getConfig(extensionId, scope = []) {
5526
+ let merged = null;
5274
5527
  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
- }
5528
+ if (globalConfig) {
5529
+ merged = { ...globalConfig };
5281
5530
  }
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 };
5531
+ for (let i = 1; i <= scope.length; i++) {
5532
+ const subScope = scope.slice(0, i);
5533
+ const key = scopeToKey(subScope);
5534
+ const layerConfig = this.configs.get(key)?.get(extensionId);
5535
+ if (layerConfig) {
5536
+ merged = { ...merged ?? {}, ...layerConfig };
5286
5537
  }
5287
5538
  }
5288
5539
  return merged ? structuredClone(merged) : null;
@@ -5291,7 +5542,7 @@ var InMemoryExtensionHost = class {
5291
5542
  this.listeners.add(listener);
5292
5543
  return () => this.listeners.delete(listener);
5293
5544
  }
5294
- notifyConfigChanged(extensionId, scope = { kind: "global" }) {
5545
+ notifyConfigChanged(extensionId, scope = []) {
5295
5546
  for (const listener of this.listeners) {
5296
5547
  try {
5297
5548
  listener(extensionId, scope);
@@ -5320,29 +5571,26 @@ var InMemoryExtensionHost = class {
5320
5571
  deleteAgentState(extensionId, agentId, key) {
5321
5572
  this.agentState.get(agentId)?.get(extensionId)?.delete(key);
5322
5573
  }
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}`;
5574
+ getGlobalState(extensionId, key) {
5575
+ const val = this.globalState.get(extensionId)?.get(key);
5576
+ return val !== void 0 ? structuredClone(val) : void 0;
5332
5577
  }
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 };
5578
+ setGlobalState(extensionId, key, value) {
5579
+ assertJsonValue(value);
5580
+ let extMap = this.globalState.get(extensionId);
5581
+ if (!extMap) {
5582
+ extMap = /* @__PURE__ */ new Map();
5583
+ this.globalState.set(extensionId, extMap);
5343
5584
  }
5585
+ extMap.set(key, structuredClone(value));
5586
+ }
5587
+ deleteGlobalState(extensionId, key) {
5588
+ this.globalState.get(extensionId)?.delete(key);
5344
5589
  }
5345
- return { kind: "global" };
5590
+ };
5591
+ function scopeToKey(scope) {
5592
+ if (scope.length === 0) return "global";
5593
+ return scope.map((layer) => `${layer.kind}:${layer.id}`).join("/");
5346
5594
  }
5347
5595
 
5348
5596
  // src/extensions/runner.ts
@@ -5456,8 +5704,9 @@ async function execCommand(command, args, cwd, options) {
5456
5704
  });
5457
5705
  }
5458
5706
  function applyProviderRegistration(config) {
5459
- if (config.streamSimple) {
5460
- registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
5707
+ unregisterProviderModels(config.id);
5708
+ if (config.stream) {
5709
+ registerApiProvider({ protocol: config.protocol, stream: config.stream });
5461
5710
  }
5462
5711
  for (const modelConfig of config.models) {
5463
5712
  registerModel({
@@ -5467,12 +5716,13 @@ function applyProviderRegistration(config) {
5467
5716
  provider: config.id,
5468
5717
  baseUrl: config.baseUrl,
5469
5718
  apiKey: config.apiKey,
5470
- reasoning: modelConfig.reasoning,
5719
+ reasoning: modelConfig.reasoning ?? false,
5471
5720
  ...modelConfig.thinkingLevel !== void 0 ? { thinkingLevel: modelConfig.thinkingLevel } : {},
5472
- input: modelConfig.input,
5473
- cost: modelConfig.cost,
5474
- contextWindow: modelConfig.contextWindow,
5475
- maxTokens: modelConfig.maxTokens,
5721
+ ...modelConfig.thinkingLevels !== void 0 ? { thinkingLevels: modelConfig.thinkingLevels } : {},
5722
+ input: modelConfig.input ?? ["text"],
5723
+ cost: modelConfig.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
5724
+ contextWindow: modelConfig.contextWindow ?? 128e3,
5725
+ maxTokens: modelConfig.maxTokens ?? 4096,
5476
5726
  ...config.headers || modelConfig.headers ? { headers: { ...config.headers, ...modelConfig.headers } } : {},
5477
5727
  ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
5478
5728
  ...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
@@ -5508,6 +5758,8 @@ var ExtensionRunner = class {
5508
5758
  errorListeners = /* @__PURE__ */ new Set();
5509
5759
  // Capability change listeners
5510
5760
  capabilityListeners = /* @__PURE__ */ new Set();
5761
+ // Host contribution change listeners
5762
+ hostContributionListeners = /* @__PURE__ */ new Set();
5511
5763
  /** Current agent context — set by the agent before each phase */
5512
5764
  currentContext;
5513
5765
  constructor(options) {
@@ -5611,6 +5863,7 @@ var ExtensionRunner = class {
5611
5863
  tools: /* @__PURE__ */ new Map(),
5612
5864
  phases: /* @__PURE__ */ new Set(),
5613
5865
  capabilities: /* @__PURE__ */ new Map(),
5866
+ hostContributions: /* @__PURE__ */ new Map(),
5614
5867
  cleanup: [],
5615
5868
  runtime: createExtensionRuntime()
5616
5869
  };
@@ -5623,6 +5876,9 @@ var ExtensionRunner = class {
5623
5876
  if (extension.capabilities.size > 0) {
5624
5877
  this.notifyCapabilitiesChanged();
5625
5878
  }
5879
+ if (extension.hostContributions.size > 0) {
5880
+ this.notifyHostContributionsChanged();
5881
+ }
5626
5882
  } catch (error) {
5627
5883
  await this.rollbackExtension(extension);
5628
5884
  const message = error instanceof Error ? error.message : String(error);
@@ -5751,6 +6007,27 @@ var ExtensionRunner = class {
5751
6007
  this.capabilityListeners.add(listener);
5752
6008
  return () => this.capabilityListeners.delete(listener);
5753
6009
  }
6010
+ /** Get all contributed host elements across all extensions. */
6011
+ getHostContributions() {
6012
+ const result = [];
6013
+ for (const ext of this.extensions) {
6014
+ for (const contrib of ext.hostContributions.values()) {
6015
+ result.push(contrib);
6016
+ }
6017
+ }
6018
+ return Object.freeze(result);
6019
+ }
6020
+ /**
6021
+ * Subscribe to host contribution changes (contributions added, removed, or extensions disposed).
6022
+ * Returns an unsubscribe function.
6023
+ */
6024
+ onHostContributionsChanged(listener) {
6025
+ this.hostContributionListeners.add(listener);
6026
+ return () => this.hostContributionListeners.delete(listener);
6027
+ }
6028
+ triggerUiAction(event) {
6029
+ this.events.emit("ui.action", event);
6030
+ }
5754
6031
  // ---------------------------------------------------------------------------
5755
6032
  // Phase management
5756
6033
  // ---------------------------------------------------------------------------
@@ -5868,7 +6145,7 @@ var ExtensionRunner = class {
5868
6145
  }
5869
6146
  }
5870
6147
  async emitBeforeToolCall(tool, args, context) {
5871
- const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
6148
+ const scope = context?.scope ?? [];
5872
6149
  const turn = context?.turn ?? {};
5873
6150
  const result = await this.emitHook("before_tool_call", {
5874
6151
  type: "before_tool_call",
@@ -5885,7 +6162,7 @@ var ExtensionRunner = class {
5885
6162
  return result ?? { allow: true };
5886
6163
  }
5887
6164
  async emitAfterToolCall(tool, result, context) {
5888
- const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
6165
+ const scope = context?.scope ?? [];
5889
6166
  const turn = context?.turn ?? {};
5890
6167
  const hookResult = await this.emitHook("after_tool_call", {
5891
6168
  type: "after_tool_call",
@@ -5965,12 +6242,13 @@ var ExtensionRunner = class {
5965
6242
  registerTool: (tool) => this.registerTool(extension, tool),
5966
6243
  unregisterTool: (toolName) => this.unregisterTool(extension, toolName),
5967
6244
  contributeCapability: (contribution) => this.contributeCapability(extension, contribution),
6245
+ contributeToHost: (contribution) => this.contributeToHost(extension, contribution),
5968
6246
  context: extContext,
5969
6247
  manifest,
5970
6248
  trackCleanup: (cleanup) => extension.cleanup.push(cleanup),
5971
6249
  config: {
5972
6250
  get: async (scope) => {
5973
- const effectiveScope = scope ?? { kind: "global" };
6251
+ const effectiveScope = scope ?? [];
5974
6252
  const res = await runner.host.getConfig(extension.id, effectiveScope);
5975
6253
  return res ?? null;
5976
6254
  },
@@ -5999,6 +6277,11 @@ var ExtensionRunner = class {
5999
6277
  get: async (key) => runner.host.getAgentState(extension.id, agentId, key),
6000
6278
  set: async (key, value) => runner.host.setAgentState(extension.id, agentId, key, value),
6001
6279
  delete: async (key) => runner.host.deleteAgentState(extension.id, agentId, key)
6280
+ }),
6281
+ global: () => ({
6282
+ get: async (key) => runner.host.getGlobalState(extension.id, key),
6283
+ set: async (key, value) => runner.host.setGlobalState(extension.id, key, value),
6284
+ delete: async (key) => runner.host.deleteGlobalState(extension.id, key)
6002
6285
  })
6003
6286
  }
6004
6287
  }, extension.runtime, this.events);
@@ -6069,6 +6352,44 @@ var ExtensionRunner = class {
6069
6352
  }
6070
6353
  }
6071
6354
  }
6355
+ contributeToHost(extension, contribution) {
6356
+ const key = `${contribution.kind}:${contribution.id}`;
6357
+ const entry = {
6358
+ ...contribution
6359
+ };
6360
+ extension.hostContributions.set(key, entry);
6361
+ if (this.extensions.includes(extension)) {
6362
+ this.notifyHostContributionsChanged();
6363
+ }
6364
+ let disposed = false;
6365
+ const dispose = () => {
6366
+ if (disposed) return;
6367
+ disposed = true;
6368
+ if (extension.hostContributions.get(key) === entry) {
6369
+ extension.hostContributions.delete(key);
6370
+ if (this.extensions.includes(extension)) {
6371
+ this.notifyHostContributionsChanged();
6372
+ }
6373
+ }
6374
+ };
6375
+ extension.cleanup.push(dispose);
6376
+ return dispose;
6377
+ }
6378
+ notifyHostContributionsChanged() {
6379
+ const contributions = this.getHostContributions();
6380
+ for (const listener of this.hostContributionListeners) {
6381
+ try {
6382
+ listener(contributions);
6383
+ } catch (error) {
6384
+ this.emitError({
6385
+ extensionPath: "<runtime>",
6386
+ event: "host_contributions_changed",
6387
+ error: error instanceof Error ? error.message : String(error),
6388
+ stack: error instanceof Error ? error.stack : void 0
6389
+ });
6390
+ }
6391
+ }
6392
+ }
6072
6393
  async registerPhase(extension, registration) {
6073
6394
  if (typeof registration === "string") {
6074
6395
  if (registration.length === 0) {
@@ -6157,6 +6478,8 @@ var ExtensionRunner = class {
6157
6478
  for (const name of extension.phases) this.phases.delete(name);
6158
6479
  const hadCapabilities = extension.capabilities.size > 0;
6159
6480
  extension.capabilities.clear();
6481
+ const hadHostContributions = extension.hostContributions.size > 0;
6482
+ extension.hostContributions.clear();
6160
6483
  for (const cleanup of [...extension.cleanup].reverse()) {
6161
6484
  await Promise.resolve().then(() => cleanup()).catch(() => void 0);
6162
6485
  }
@@ -6167,6 +6490,9 @@ var ExtensionRunner = class {
6167
6490
  if (hadCapabilities) {
6168
6491
  this.notifyCapabilitiesChanged();
6169
6492
  }
6493
+ if (hadHostContributions) {
6494
+ this.notifyHostContributionsChanged();
6495
+ }
6170
6496
  }
6171
6497
  };
6172
6498
  function createExtensionRunner(options) {
@@ -6859,11 +7185,11 @@ function adaptExtensionTool(input) {
6859
7185
  parameters: definition.parameters,
6860
7186
  ...definition.promptSnippet ? { promptSnippet: definition.promptSnippet } : {},
6861
7187
  ...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
- }
7188
+ ...definition.kind ? { kind: definition.kind } : {},
7189
+ ...definition.annotations ? { annotations: definition.annotations } : {},
7190
+ ...definition._meta !== void 0 ? { _meta: definition._meta } : {},
7191
+ ...definition.present ? { present: definition.present } : {},
7192
+ execute: async (args, context, signal) => definition.execute(args, context, signal)
6867
7193
  };
6868
7194
  }
6869
7195
 
@@ -6931,12 +7257,53 @@ async function spillLargeToolResult(result, archiveDir, toolName) {
6931
7257
  await writeFile2(path, serialized, { encoding: "utf8", mode: 384 });
6932
7258
  await chmod2(path, 384).catch(() => void 0);
6933
7259
  const preview = new TextDecoder().decode(bytes.subarray(0, MAX_INLINE_TOOL_RESULT_BYTES));
6934
- return {
6935
- ...result,
6936
- content: `${preview}
7260
+ const truncatedText = `${preview}
6937
7261
  [truncated]
6938
7262
 
6939
- [Full result: ${path}, offset 0]`
7263
+ [Full result: ${path}, offset 0]`;
7264
+ if (Array.isArray(result.content)) {
7265
+ return {
7266
+ ...result,
7267
+ content: [{ type: "text", text: truncatedText }]
7268
+ };
7269
+ }
7270
+ if (result.ok) {
7271
+ return {
7272
+ ok: true,
7273
+ content: truncatedText,
7274
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
7275
+ };
7276
+ }
7277
+ return {
7278
+ ok: false,
7279
+ content: truncatedText,
7280
+ error: result.error ?? "Tool execution failed.",
7281
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
7282
+ };
7283
+ }
7284
+ async function presentToolCall(tool, call) {
7285
+ if (!tool.present) return void 0;
7286
+ try {
7287
+ const res = await tool.present(call);
7288
+ if (!res) return void 0;
7289
+ if (!isValidPresentationOutput(res)) {
7290
+ console.warn(`Tool "${tool.name}" present() returned invalid shape for status "${call.status}":`, res);
7291
+ return void 0;
7292
+ }
7293
+ return res;
7294
+ } catch (err) {
7295
+ console.warn(`Tool "${tool.name}" present() threw for status "${call.status}":`, err);
7296
+ return void 0;
7297
+ }
7298
+ }
7299
+ async function presentToolPending(tool, args) {
7300
+ const pres = await presentToolCall(tool, { status: "pending", args });
7301
+ return {
7302
+ title: pres?.title ?? tool.annotations?.title ?? tool.name,
7303
+ kind: tool.kind ?? "other",
7304
+ ...pres?.locations !== void 0 ? { locations: pres.locations } : {},
7305
+ ...pres?.content !== void 0 ? { content: pres.content } : {},
7306
+ ...pres?._meta !== void 0 ? { _meta: pres._meta } : {}
6940
7307
  };
6941
7308
  }
6942
7309
  var AgentRuntime = class _AgentRuntime {
@@ -6955,6 +7322,7 @@ var AgentRuntime = class _AgentRuntime {
6955
7322
  host;
6956
7323
  resources;
6957
7324
  startedRuns = /* @__PURE__ */ new Set();
7325
+ liveInteractions = /* @__PURE__ */ new Map();
6958
7326
  heartbeat;
6959
7327
  pumping = false;
6960
7328
  closed = false;
@@ -6990,6 +7358,13 @@ var AgentRuntime = class _AgentRuntime {
6990
7358
  }
6991
7359
  }
6992
7360
  async handleRunEnd(runId, agentId, outcome) {
7361
+ const runLive = this.liveInteractions.get(runId);
7362
+ if (runLive) {
7363
+ for (const live of runLive.values()) {
7364
+ live.reject(new RunInteractionCancelledError("Run ended."));
7365
+ }
7366
+ this.liveInteractions.delete(runId);
7367
+ }
6993
7368
  if (this.startedRuns.has(runId)) {
6994
7369
  this.startedRuns.delete(runId);
6995
7370
  try {
@@ -7133,6 +7508,8 @@ var AgentRuntime = class _AgentRuntime {
7133
7508
  agentId,
7134
7509
  input,
7135
7510
  ...options.metadata === void 0 ? {} : { metadata: options.metadata },
7511
+ ...options.scope === void 0 ? {} : { scope: options.scope },
7512
+ ...options.cwd === void 0 ? {} : { cwd: options.cwd },
7136
7513
  ...phasePayload === void 0 ? {} : { phasePayload },
7137
7514
  ...options.entryPhases === void 0 ? {} : { entryPhases: options.entryPhases },
7138
7515
  ...pinnedConfigToken === agent.currentConfigToken ? {} : { pinnedConfigToken },
@@ -7221,12 +7598,72 @@ var AgentRuntime = class _AgentRuntime {
7221
7598
  this.assertOpen();
7222
7599
  return this.resources.extensionRunner.onCapabilitiesChanged(listener);
7223
7600
  }
7601
+ listHostContributions() {
7602
+ this.assertOpen();
7603
+ return this.resources.extensionRunner.getHostContributions();
7604
+ }
7605
+ onHostContributionsChanged(listener) {
7606
+ this.assertOpen();
7607
+ return this.resources.extensionRunner.onHostContributionsChanged(listener);
7608
+ }
7609
+ triggerUiAction(event) {
7610
+ this.assertOpen();
7611
+ this.resources.extensionRunner.triggerUiAction(event);
7612
+ }
7613
+ liveInteract(runId, request, signal) {
7614
+ if (signal.aborted) {
7615
+ return Promise.reject(signal.reason ?? new Error("Run aborted"));
7616
+ }
7617
+ const interactionId = request.id ?? createId("interaction");
7618
+ const interaction = {
7619
+ id: interactionId,
7620
+ phase: "default",
7621
+ kind: request.kind,
7622
+ prompt: request.prompt,
7623
+ payload: request.payload,
7624
+ toolCallId: request.toolCallId,
7625
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7626
+ status: "pending",
7627
+ result: request.result
7628
+ };
7629
+ let runLive = this.liveInteractions.get(runId);
7630
+ if (!runLive) {
7631
+ runLive = /* @__PURE__ */ new Map();
7632
+ this.liveInteractions.set(runId, runLive);
7633
+ }
7634
+ return new Promise((resolve9, reject) => {
7635
+ const onAbort = () => {
7636
+ runLive?.delete(interactionId);
7637
+ if (runLive && runLive.size === 0) this.liveInteractions.delete(runId);
7638
+ signal.removeEventListener("abort", onAbort);
7639
+ reject(signal.reason ?? new Error("Run aborted"));
7640
+ };
7641
+ signal.addEventListener("abort", onAbort, { once: true });
7642
+ runLive.set(interactionId, {
7643
+ interaction,
7644
+ resolve: (val) => {
7645
+ signal.removeEventListener("abort", onAbort);
7646
+ resolve9(val);
7647
+ },
7648
+ reject: (err) => {
7649
+ signal.removeEventListener("abort", onAbort);
7650
+ reject(err);
7651
+ }
7652
+ });
7653
+ });
7654
+ }
7224
7655
  async close() {
7225
7656
  if (this.closed) return;
7226
7657
  this.closed = true;
7227
7658
  if (this.heartbeat) clearInterval(this.heartbeat);
7228
7659
  for (const execution of this.executions.values()) execution.controller.abort();
7229
7660
  this.executions.clear();
7661
+ for (const runLive of this.liveInteractions.values()) {
7662
+ for (const live of runLive.values()) {
7663
+ live.reject(new RunInteractionCancelledError("Runtime closed."));
7664
+ }
7665
+ }
7666
+ this.liveInteractions.clear();
7230
7667
  this.transientEvents.close();
7231
7668
  for (const subscription of this.consumers.values()) subscription.controller.abort();
7232
7669
  await this.resources.closeExtensions();
@@ -7234,7 +7671,15 @@ var AgentRuntime = class _AgentRuntime {
7234
7671
  }
7235
7672
  async snapshot(runId) {
7236
7673
  this.assertOpen();
7237
- return this.owned.snapshotRun(runId);
7674
+ const snap = await this.owned.snapshotRun(runId);
7675
+ const runLive = this.liveInteractions.get(runId);
7676
+ if (runLive && runLive.size > 0 && snap.state === "running") {
7677
+ return {
7678
+ ...snap,
7679
+ interactions: [...runLive.values()].map((l) => l.interaction)
7680
+ };
7681
+ }
7682
+ return snap;
7238
7683
  }
7239
7684
  async history(agentId) {
7240
7685
  this.assertOpen();
@@ -7242,6 +7687,20 @@ var AgentRuntime = class _AgentRuntime {
7242
7687
  }
7243
7688
  async respondInteraction(runId, input) {
7244
7689
  this.assertOpen();
7690
+ const runLive = this.liveInteractions.get(runId);
7691
+ const live = runLive?.get(input.interactionId);
7692
+ if (live) {
7693
+ runLive.delete(input.interactionId);
7694
+ if (runLive.size === 0) this.liveInteractions.delete(runId);
7695
+ if (input.cancel) {
7696
+ live.reject(new RunInteractionCancelledError("Run interaction cancelled."));
7697
+ } else if (input.input !== void 0) {
7698
+ live.resolve(input.input);
7699
+ } else {
7700
+ live.reject(new TypeError("respondInteraction requires input or cancel: true"));
7701
+ }
7702
+ return;
7703
+ }
7245
7704
  const snapshot = await this.owned.snapshotRun(runId);
7246
7705
  if (snapshot.state !== "input_required" || !snapshot.interactions.some((interaction) => interaction.id === input.interactionId)) {
7247
7706
  throw new RuntimeError("input_request_conflict", { runId, interactionId: input.interactionId, reason: "not_found" });
@@ -7251,6 +7710,13 @@ var AgentRuntime = class _AgentRuntime {
7251
7710
  }
7252
7711
  async cancel(runId, reason) {
7253
7712
  this.assertOpen();
7713
+ const runLive = this.liveInteractions.get(runId);
7714
+ if (runLive) {
7715
+ for (const live of runLive.values()) {
7716
+ live.reject(new RunInteractionCancelledError(reason ?? "Agent run stopped."));
7717
+ }
7718
+ this.liveInteractions.delete(runId);
7719
+ }
7254
7720
  const done = this.executionDone.get(runId);
7255
7721
  if (done) {
7256
7722
  this.cancellationRequested.add(runId);
@@ -7309,6 +7775,7 @@ var AgentRuntime = class _AgentRuntime {
7309
7775
  async execute(run) {
7310
7776
  let claim;
7311
7777
  let executionRevision = run.revision;
7778
+ let toolQueue = Promise.resolve();
7312
7779
  try {
7313
7780
  const agent = await this.requireAgent(run.agentId);
7314
7781
  let token = run.pinnedConfigToken ?? agent.currentConfigToken;
@@ -7396,7 +7863,7 @@ var AgentRuntime = class _AgentRuntime {
7396
7863
  const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
7397
7864
  toolArchiveDir: this.archiveDirFor(run.agentId)
7398
7865
  });
7399
- let toolQueue = Promise.resolve();
7866
+ toolQueue = Promise.resolve();
7400
7867
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
7401
7868
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
7402
7869
  const phaseRegistry = normalizePhaseRegistry(assembly.context.phases);
@@ -7435,10 +7902,12 @@ var AgentRuntime = class _AgentRuntime {
7435
7902
  executionId: claim.execution.executionId,
7436
7903
  input: typeof run.input === "string" ? run.input : run.input.content,
7437
7904
  ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
7438
- ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
7905
+ ...run.metadata === void 0 ? {} : { runMetadata: run.metadata },
7906
+ ...run.scope === void 0 ? {} : { scope: run.scope },
7907
+ ...run.cwd === void 0 ? {} : { cwd: run.cwd }
7439
7908
  },
7440
7909
  model,
7441
- thinkingLevel: thinkingLevelFromMessages(context.messages),
7910
+ thinkingLevel: thinkingLevelFromUserInput(run.input),
7442
7911
  stream,
7443
7912
  maxAttempts: config.maxAttempts,
7444
7913
  checkpoint: claim.run.checkpoint,
@@ -7579,21 +8048,48 @@ var AgentRuntime = class _AgentRuntime {
7579
8048
  error: event.error
7580
8049
  });
7581
8050
  },
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,
7589
- agentMetadata: agent.metadata,
7590
- execution: claim.execution,
8051
+ onToolReport: (event) => {
8052
+ const active = this.executions.get(run.id);
8053
+ if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
8054
+ this.transientEvents.publish({
8055
+ kind: "tool_state_changed",
8056
+ durability: "transient",
8057
+ runId: event.runId,
8058
+ executionId: claim.execution.executionId,
8059
+ transition: { from: null, to: event.update.status ?? "in_progress" },
8060
+ toolCall: event.update,
8061
+ external: true
8062
+ });
8063
+ const task = toolQueue.then(async () => {
8064
+ const active2 = this.executions.get(run.id);
8065
+ if (this.closed || controller.signal.aborted || active2?.executionId !== claim.execution.executionId) return;
8066
+ const recorded = await this.owned.recordToolCall({
8067
+ runId: run.id,
8068
+ execution: claim.execution,
8069
+ update: event.update,
8070
+ external: true
8071
+ });
8072
+ executionRevision = recorded.runRevision;
8073
+ });
8074
+ toolQueue = task.then(() => void 0, () => void 0);
8075
+ },
8076
+ retryDelayMs: this.retryDelayMs,
8077
+ onContext: assembly.setContext,
8078
+ runtime: {
8079
+ interact: (request) => this.liveInteract(run.id, request, controller.signal),
8080
+ tools: ({ toolCall, contentBlocks, driver, options }) => {
8081
+ const task = toolQueue.then(async () => {
8082
+ const execution = await this.executeToolBatch({
8083
+ run,
8084
+ agentMetadata: agent.metadata,
8085
+ execution: claim.execution,
7591
8086
  expectedRevision: executionRevision,
7592
8087
  toolConfig: executionTools,
7593
8088
  toolCalls: [toolCall],
7594
8089
  contentBlocks,
7595
8090
  signal: controller.signal,
7596
8091
  driver,
8092
+ options,
7597
8093
  onRevision: (rev) => {
7598
8094
  executionRevision = rev;
7599
8095
  }
@@ -7604,7 +8100,7 @@ var AgentRuntime = class _AgentRuntime {
7604
8100
  toolQueue = task.then(() => void 0, () => void 0);
7605
8101
  return task;
7606
8102
  },
7607
- toolsBatch: ({ toolCalls, contentBlocks, driver }) => {
8103
+ toolsBatch: ({ toolCalls, contentBlocks, driver, options }) => {
7608
8104
  const task = toolQueue.then(async () => {
7609
8105
  const execution = await this.executeToolBatch({
7610
8106
  run,
@@ -7616,6 +8112,7 @@ var AgentRuntime = class _AgentRuntime {
7616
8112
  contentBlocks,
7617
8113
  signal: controller.signal,
7618
8114
  driver,
8115
+ options,
7619
8116
  onRevision: (rev) => {
7620
8117
  executionRevision = rev;
7621
8118
  }
@@ -7639,6 +8136,7 @@ var AgentRuntime = class _AgentRuntime {
7639
8136
  result = await executeModel(executionContext);
7640
8137
  }
7641
8138
  }
8139
+ await toolQueue;
7642
8140
  if (this.cancellationRequested.has(run.id) || controller.signal.aborted) {
7643
8141
  const output2 = latestAssistant(run, result.messages, executionContext.messages.length, true);
7644
8142
  const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
@@ -7711,6 +8209,7 @@ var AgentRuntime = class _AgentRuntime {
7711
8209
  } catch (error) {
7712
8210
  if (!claim && error instanceof RuntimeError && ["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) return;
7713
8211
  if (claim) {
8212
+ await toolQueue;
7714
8213
  if (this.cancellationRequested.has(run.id)) {
7715
8214
  const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
7716
8215
  await this.owned.cancelRun({
@@ -7766,7 +8265,8 @@ var AgentRuntime = class _AgentRuntime {
7766
8265
  return { result: batch.results[0], revision: batch.revision };
7767
8266
  }
7768
8267
  async executeToolBatch(input) {
7769
- const scope = resolveScopeFromMetadata(input.run.metadata);
8268
+ const scope = input.run.scope ?? [];
8269
+ const cwd = input.run.cwd;
7770
8270
  const turn = typeof input.run.input === "object" && input.run.input !== null && !Array.isArray(input.run.input) ? input.run.input : {};
7771
8271
  const checkpoint = input.driver?.checkpoint();
7772
8272
  const isSuspensionResume = checkpoint && typeof checkpoint === "object" && checkpoint.kind === "tool_call_suspension";
@@ -7796,6 +8296,7 @@ var AgentRuntime = class _AgentRuntime {
7796
8296
  toolCallId,
7797
8297
  providerToolCallId: toolCall.id,
7798
8298
  scope,
8299
+ ...cwd ? { cwd } : {},
7799
8300
  turn,
7800
8301
  interaction: createRunInteractionDriver({
7801
8302
  currentPhase: "default",
@@ -7823,6 +8324,12 @@ var AgentRuntime = class _AgentRuntime {
7823
8324
  }
7824
8325
  }
7825
8326
  if (!hasInteraction) {
8327
+ const startPresentations = await Promise.all(
8328
+ input.toolCalls.map(async (tc) => {
8329
+ const t = input.toolConfig.tools.find((c) => c.name === tc.name);
8330
+ return t ? presentToolPending(t, toJsonValue(tc.args)) : void 0;
8331
+ })
8332
+ );
7826
8333
  const reserved = await this.owned.reserveToolCalls({
7827
8334
  runId: input.run.id,
7828
8335
  execution: input.execution,
@@ -7833,7 +8340,8 @@ var AgentRuntime = class _AgentRuntime {
7833
8340
  toolCallId: toolCallIds[i],
7834
8341
  providerToolCallId: tc.id,
7835
8342
  name: tc.name,
7836
- args: toJsonValue(tc.args)
8343
+ args: toJsonValue(tc.args),
8344
+ ...startPresentations[i] ?? {}
7837
8345
  }))
7838
8346
  });
7839
8347
  setRevision(reserved.run.revision);
@@ -7857,7 +8365,9 @@ var AgentRuntime = class _AgentRuntime {
7857
8365
  expectedRevision: revision,
7858
8366
  toolCallId,
7859
8367
  state: "failed",
7860
- result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` }
8368
+ result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` },
8369
+ title: toolCall.name,
8370
+ kind: "other"
7861
8371
  });
7862
8372
  setRevision(failed.run.revision);
7863
8373
  results2.push(protocolFailure(`Tool ${toolCall.name} is not available.`));
@@ -7871,117 +8381,64 @@ var AgentRuntime = class _AgentRuntime {
7871
8381
  expectedRevision: revision,
7872
8382
  toolCallId,
7873
8383
  state: "failed",
7874
- result: { ok: false, content: null, error: decision.reason }
8384
+ result: { ok: false, content: null, error: decision.reason },
8385
+ title: durableTool.annotations?.title ?? durableTool.name,
8386
+ kind: durableTool.kind ?? "other"
7875
8387
  });
7876
8388
  setRevision(failed.run.revision);
7877
8389
  results2.push(protocolFailure(decision.reason));
7878
8390
  continue;
7879
8391
  }
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
- }
8392
+ const currentToolState = {
8393
+ ...startPresentations[i] ?? {
8394
+ title: durableTool.annotations?.title ?? durableTool.name,
8395
+ kind: durableTool.kind ?? "other"
8396
+ },
8397
+ rawInput: toJsonValue(toolCall.args)
7916
8398
  };
8399
+ const inProgressPres = await presentToolCall(durableTool, {
8400
+ status: "in_progress",
8401
+ args: toJsonValue(toolCall.args)
8402
+ });
8403
+ if (inProgressPres) mergeToolCall(currentToolState, inProgressPres);
7917
8404
  const started = await this.owned.startToolCall({
7918
8405
  runId: input.run.id,
7919
8406
  execution: input.execution,
7920
8407
  expectedRevision: revision,
7921
- toolCallId
8408
+ toolCallId,
8409
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8410
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8411
+ ...currentToolState.locations !== void 0 ? { locations: currentToolState.locations } : {},
8412
+ ...currentToolState.content !== void 0 ? { content: currentToolState.content } : {},
8413
+ ...currentToolState._meta !== void 0 ? { _meta: currentToolState._meta } : {}
7922
8414
  });
7923
8415
  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
- }
8416
+ const toolResult2 = await this.executeAndCommitTool({
8417
+ runId: input.run.id,
8418
+ agentId: input.run.agentId,
8419
+ agentMetadata: input.agentMetadata,
8420
+ runMetadata: input.run.metadata,
8421
+ execution: input.execution,
8422
+ revision,
8423
+ setRevision,
8424
+ toolCall,
8425
+ durableTool,
8426
+ toolCallId,
8427
+ providerToolCallId,
8428
+ scope,
8429
+ cwd,
8430
+ turn,
8431
+ driver: input.driver,
8432
+ toolCalls: input.toolCalls,
8433
+ toolCallIds,
8434
+ pendingIndex: i,
8435
+ completedResults: results2,
8436
+ signal: input.signal,
8437
+ afterToolCall: input.toolConfig.afterToolCall,
8438
+ currentToolState,
8439
+ options: input.options
8440
+ });
8441
+ results2.push(toolResult2);
7985
8442
  }
7986
8443
  return { results: results2, revision };
7987
8444
  }
@@ -8001,7 +8458,7 @@ var AgentRuntime = class _AgentRuntime {
8001
8458
  error
8002
8459
  });
8003
8460
  if (!durableTool) {
8004
- const reserved2 = await this.owned.reserveToolCalls({
8461
+ const reserved = await this.owned.reserveToolCalls({
8005
8462
  runId: input.run.id,
8006
8463
  execution: input.execution,
8007
8464
  expectedRevision: revision,
@@ -8014,27 +8471,58 @@ var AgentRuntime = class _AgentRuntime {
8014
8471
  args: toJsonValue(toolCall.args)
8015
8472
  }]
8016
8473
  });
8017
- setRevision(reserved2.run.revision);
8474
+ setRevision(reserved.run.revision);
8018
8475
  const failed = await this.owned.commitToolResult({
8019
8476
  runId: input.run.id,
8020
8477
  execution: input.execution,
8021
8478
  expectedRevision: revision,
8022
8479
  toolCallId,
8023
8480
  state: "failed",
8024
- result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` }
8481
+ result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` },
8482
+ title: toolCall.name,
8483
+ kind: "other"
8025
8484
  });
8026
8485
  setRevision(failed.run.revision);
8027
8486
  results.push(protocolFailure(`Tool ${toolCall.name} is not available.`));
8028
8487
  continue;
8029
8488
  }
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;
8489
+ const pendingPres = await presentToolPending(durableTool, toJsonValue(toolCall.args));
8490
+ const currentToolState = {
8491
+ ...pendingPres,
8492
+ rawInput: toJsonValue(toolCall.args)
8493
+ };
8494
+ const resumedStart = suspension && index === suspension.pendingIndex ? await (async () => {
8495
+ const inProgressPres = await presentToolCall(durableTool, {
8496
+ status: "in_progress",
8497
+ args: toJsonValue(toolCall.args)
8498
+ });
8499
+ if (inProgressPres) mergeToolCall(currentToolState, inProgressPres);
8500
+ return this.owned.startToolCall({
8501
+ runId: input.run.id,
8502
+ execution: input.execution,
8503
+ expectedRevision: revision,
8504
+ toolCallId,
8505
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8506
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8507
+ ...currentToolState.locations !== void 0 ? { locations: currentToolState.locations } : {},
8508
+ ...currentToolState.content !== void 0 ? { content: currentToolState.content } : {},
8509
+ ...currentToolState._meta !== void 0 ? { _meta: currentToolState._meta } : {}
8510
+ });
8511
+ })() : void 0;
8037
8512
  if (resumedStart) setRevision(resumedStart.run.revision);
8513
+ const baseDriver = input.driver ?? createRunInteractionDriver({
8514
+ currentPhase: "default",
8515
+ attempt: 0,
8516
+ status: "running",
8517
+ metrics: { iterations: 0, phaseTransitions: [], compactionCount: 0, retryCount: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), startedAtMs: Date.now() }
8518
+ }, "default", input.signal);
8519
+ const interactionDriver = createToolInteractionDriver(baseDriver, {
8520
+ toolCallId,
8521
+ ...suspension?.toolCallIds === void 0 ? {} : { toolCallIds: suspension.toolCallIds },
8522
+ toolCalls: input.toolCalls,
8523
+ pendingIndex: index,
8524
+ completedResults: results
8525
+ });
8038
8526
  const context = {
8039
8527
  agentId: input.run.agentId,
8040
8528
  runId: input.run.id,
@@ -8043,32 +8531,10 @@ var AgentRuntime = class _AgentRuntime {
8043
8531
  toolCallId,
8044
8532
  providerToolCallId,
8045
8533
  scope,
8534
+ ...cwd ? { cwd } : {},
8046
8535
  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
- });
8536
+ interaction: interactionDriver,
8537
+ reportProgress: () => {
8072
8538
  }
8073
8539
  };
8074
8540
  const answer = suspension && index === suspension.pendingIndex ? suspension.interactionIds?.map((id) => input.driver?.answers().get(id)).find((value) => value !== void 0) : void 0;
@@ -8085,31 +8551,43 @@ var AgentRuntime = class _AgentRuntime {
8085
8551
  if (!input.driver) {
8086
8552
  throw new Error("Tool call requested interaction but no interaction driver is active.");
8087
8553
  }
8088
- context.interaction.request(decision.interaction);
8089
- if (!(suspension && index === suspension.pendingIndex)) {
8090
- const reserved2 = await this.owned.reserveToolCalls({
8554
+ interactionDriver.request(decision.interaction);
8555
+ if (!resumedStart) {
8556
+ const reserved = await this.owned.reserveToolCalls({
8091
8557
  runId: input.run.id,
8092
8558
  execution: input.execution,
8093
8559
  expectedRevision: revision,
8094
8560
  requestMessageId: createId("msg"),
8095
8561
  contentBlocks: input.contentBlocks,
8096
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8562
+ calls: [{
8563
+ toolCallId,
8564
+ providerToolCallId,
8565
+ name: toolCall.name,
8566
+ args: toJsonValue(toolCall.args),
8567
+ ...pendingPres
8568
+ }]
8097
8569
  });
8098
- setRevision(reserved2.run.revision);
8570
+ setRevision(reserved.run.revision);
8099
8571
  }
8100
- context.interaction.suspend();
8572
+ interactionDriver.suspend();
8101
8573
  }
8102
8574
  if ("allow" in decision && !decision.allow) {
8103
- if (!(suspension && index === suspension.pendingIndex)) {
8104
- const reserved2 = await this.owned.reserveToolCalls({
8575
+ if (!resumedStart) {
8576
+ const reserved = await this.owned.reserveToolCalls({
8105
8577
  runId: input.run.id,
8106
8578
  execution: input.execution,
8107
8579
  expectedRevision: revision,
8108
8580
  requestMessageId: createId("msg"),
8109
8581
  contentBlocks: input.contentBlocks,
8110
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8582
+ calls: [{
8583
+ toolCallId,
8584
+ providerToolCallId,
8585
+ name: toolCall.name,
8586
+ args: toJsonValue(toolCall.args),
8587
+ ...pendingPres
8588
+ }]
8111
8589
  });
8112
- setRevision(reserved2.run.revision);
8590
+ setRevision(reserved.run.revision);
8113
8591
  }
8114
8592
  const failed = await this.owned.commitToolResult({
8115
8593
  runId: input.run.id,
@@ -8117,9 +8595,27 @@ var AgentRuntime = class _AgentRuntime {
8117
8595
  expectedRevision: revision,
8118
8596
  toolCallId,
8119
8597
  state: "failed",
8120
- result: { ok: false, content: null, error: decision.reason }
8598
+ result: { ok: false, content: null, error: decision.reason },
8599
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8600
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8601
+ locations: currentToolState.locations,
8602
+ content: currentToolState.content,
8603
+ _meta: currentToolState._meta
8121
8604
  });
8122
8605
  setRevision(failed.run.revision);
8606
+ if (input.options?.onUpdate) {
8607
+ try {
8608
+ input.options.onUpdate(createMergedToolCall(
8609
+ toolCallId,
8610
+ "failed",
8611
+ currentToolState,
8612
+ durableTool.annotations?.title ?? durableTool.name,
8613
+ durableTool.kind ?? "other"
8614
+ ));
8615
+ } catch (err) {
8616
+ console.warn(`Error in tool call onUpdate callback:`, err);
8617
+ }
8618
+ }
8123
8619
  results.push(protocolFailure(decision.reason));
8124
8620
  continue;
8125
8621
  }
@@ -8129,16 +8625,22 @@ var AgentRuntime = class _AgentRuntime {
8129
8625
  throw error;
8130
8626
  }
8131
8627
  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({
8628
+ if (!resumedStart) {
8629
+ const reserved = await this.owned.reserveToolCalls({
8134
8630
  runId: input.run.id,
8135
8631
  execution: input.execution,
8136
8632
  expectedRevision: revision,
8137
8633
  requestMessageId: createId("msg"),
8138
8634
  contentBlocks: input.contentBlocks,
8139
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8635
+ calls: [{
8636
+ toolCallId,
8637
+ providerToolCallId,
8638
+ name: toolCall.name,
8639
+ args: toJsonValue(toolCall.args),
8640
+ ...pendingPres
8641
+ }]
8140
8642
  });
8141
- setRevision(reserved2.run.revision);
8643
+ setRevision(reserved.run.revision);
8142
8644
  }
8143
8645
  const failed = await this.owned.commitToolResult({
8144
8646
  runId: input.run.id,
@@ -8146,94 +8648,316 @@ var AgentRuntime = class _AgentRuntime {
8146
8648
  expectedRevision: revision,
8147
8649
  toolCallId,
8148
8650
  state: "failed",
8149
- result: { ok: false, content: null, error: reason }
8651
+ result: { ok: false, content: null, error: reason },
8652
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8653
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8654
+ locations: currentToolState.locations,
8655
+ content: currentToolState.content,
8656
+ _meta: currentToolState._meta
8150
8657
  });
8151
8658
  setRevision(failed.run.revision);
8659
+ if (input.options?.onUpdate) {
8660
+ try {
8661
+ input.options.onUpdate(createMergedToolCall(
8662
+ toolCallId,
8663
+ "failed",
8664
+ currentToolState,
8665
+ durableTool.annotations?.title ?? durableTool.name,
8666
+ durableTool.kind ?? "other"
8667
+ ));
8668
+ } catch (err) {
8669
+ console.warn(`Error in tool call onUpdate callback:`, err);
8670
+ }
8671
+ }
8152
8672
  results.push(protocolFailure(reason));
8153
8673
  continue;
8154
8674
  }
8155
- const reserved = resumedStart ?? await (async () => {
8156
- const committed = await this.owned.reserveToolCalls({
8675
+ if (!resumedStart) {
8676
+ const reserved = await this.owned.reserveToolCalls({
8157
8677
  runId: input.run.id,
8158
8678
  execution: input.execution,
8159
8679
  expectedRevision: revision,
8160
8680
  requestMessageId: createId("msg"),
8161
8681
  contentBlocks: input.contentBlocks,
8162
- calls: [{ toolCallId, providerToolCallId, name: toolCall.name, args: toJsonValue(toolCall.args) }]
8682
+ calls: [{
8683
+ toolCallId,
8684
+ providerToolCallId,
8685
+ name: toolCall.name,
8686
+ args: toJsonValue(toolCall.args),
8687
+ ...pendingPres
8688
+ }]
8163
8689
  });
8164
- setRevision(committed.run.revision);
8165
- return this.owned.startToolCall({
8166
- runId: input.run.id,
8167
- execution: input.execution,
8168
- expectedRevision: revision,
8169
- toolCallId
8690
+ setRevision(reserved.run.revision);
8691
+ const inProgressPres = await presentToolCall(durableTool, {
8692
+ status: "in_progress",
8693
+ args: toJsonValue(toolCall.args)
8170
8694
  });
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({
8695
+ if (inProgressPres) mergeToolCall(currentToolState, inProgressPres);
8696
+ const started = await this.owned.startToolCall({
8192
8697
  runId: input.run.id,
8193
8698
  execution: input.execution,
8194
8699
  expectedRevision: revision,
8195
8700
  toolCallId,
8196
- state: result.ok ? "completed" : "failed",
8197
- result
8701
+ title: currentToolState.title ?? durableTool.annotations?.title ?? durableTool.name,
8702
+ kind: currentToolState.kind ?? durableTool.kind ?? "other",
8703
+ ...currentToolState.locations !== void 0 ? { locations: currentToolState.locations } : {},
8704
+ ...currentToolState.content !== void 0 ? { content: currentToolState.content } : {},
8705
+ ...currentToolState._meta !== void 0 ? { _meta: currentToolState._meta } : {}
8198
8706
  });
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
8205
- });
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;
8707
+ setRevision(started.run.revision);
8708
+ }
8709
+ const toolResult2 = await this.executeAndCommitTool({
8710
+ runId: input.run.id,
8711
+ agentId: input.run.agentId,
8712
+ agentMetadata: input.agentMetadata,
8713
+ runMetadata: input.run.metadata,
8714
+ execution: input.execution,
8715
+ revision,
8716
+ setRevision,
8717
+ toolCall,
8718
+ durableTool,
8719
+ toolCallId,
8720
+ providerToolCallId,
8721
+ scope,
8722
+ cwd,
8723
+ turn,
8724
+ driver: input.driver,
8725
+ toolCalls: input.toolCalls,
8726
+ toolCallIds: suspension?.toolCallIds,
8727
+ pendingIndex: index,
8728
+ completedResults: results,
8729
+ signal: input.signal,
8730
+ afterToolCall: input.toolConfig.afterToolCall,
8731
+ currentToolState,
8732
+ options: input.options
8733
+ });
8734
+ results.push(toolResult2);
8735
+ }
8736
+ input.driver?.clearCheckpoint();
8737
+ return { results, revision };
8738
+ }
8739
+ async executeAndCommitTool(input) {
8740
+ let progressActive = true;
8741
+ let progressQueue = Promise.resolve();
8742
+ const notifyUpdate = (toolCall, progress) => {
8743
+ if (input.options?.onUpdate) {
8744
+ try {
8745
+ input.options.onUpdate(toolCall, progress);
8746
+ } catch (err) {
8747
+ console.warn(`Error in tool call onUpdate callback:`, err);
8216
8748
  }
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,
8749
+ }
8750
+ };
8751
+ const startToolCall = createMergedToolCall(
8752
+ input.toolCallId,
8753
+ "in_progress",
8754
+ input.currentToolState,
8755
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8756
+ input.durableTool.kind ?? "other"
8757
+ );
8758
+ notifyUpdate(startToolCall);
8759
+ const effectiveSignal = combineSignals(input.signal, input.options?.signal);
8760
+ const baseDriver = input.driver ?? createRunInteractionDriver({
8761
+ currentPhase: "default",
8762
+ attempt: 0,
8763
+ status: "running",
8764
+ metrics: { iterations: 0, phaseTransitions: [], compactionCount: 0, retryCount: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), startedAtMs: Date.now() }
8765
+ }, "default", effectiveSignal);
8766
+ const interactionDriver = createToolInteractionDriver(baseDriver, {
8767
+ toolCallId: input.toolCallId,
8768
+ ...input.toolCallIds === void 0 ? {} : { toolCallIds: input.toolCallIds },
8769
+ toolCalls: input.toolCalls,
8770
+ pendingIndex: input.pendingIndex,
8771
+ completedResults: input.completedResults
8772
+ });
8773
+ const context = {
8774
+ agentId: input.agentId,
8775
+ runId: input.runId,
8776
+ ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
8777
+ ...input.runMetadata === void 0 ? {} : { runMetadata: input.runMetadata },
8778
+ toolCallId: input.toolCallId,
8779
+ providerToolCallId: input.providerToolCallId,
8780
+ scope: input.scope,
8781
+ ...input.cwd ? { cwd: input.cwd } : {},
8782
+ turn: input.turn,
8783
+ interaction: interactionDriver,
8784
+ reportProgress: (progress) => {
8785
+ const active = this.executions.get(input.runId);
8786
+ if (!progressActive || this.closed || effectiveSignal.aborted || active?.executionId !== input.execution.executionId) return;
8787
+ if (!isValidToolProgress(progress)) {
8788
+ console.warn(`Invalid progress report for tool "${input.durableTool.name}": expected valid ToolProgress shape with finite numbers.`);
8789
+ return;
8790
+ }
8791
+ let copy;
8792
+ try {
8793
+ if (JSON.stringify(progress).length > 64 * 1024) return;
8794
+ copy = structuredClone(progress);
8795
+ } catch {
8796
+ return;
8797
+ }
8798
+ progressQueue = progressQueue.then(async () => {
8799
+ if (!progressActive || this.closed || effectiveSignal.aborted) return;
8800
+ const pres = await presentToolCall(input.durableTool, {
8801
+ status: "in_progress",
8802
+ args: toJsonValue(input.toolCall.args),
8803
+ progress: copy
8804
+ });
8805
+ if (pres) {
8806
+ mergeToolCall(input.currentToolState, pres);
8807
+ const progressToolCall = createMergedToolCall(
8808
+ input.toolCallId,
8809
+ "in_progress",
8810
+ input.currentToolState,
8811
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8812
+ input.durableTool.kind ?? "other"
8813
+ );
8814
+ this.transientEvents.publish({
8815
+ kind: "tool_state_changed",
8816
+ durability: "transient",
8817
+ runId: input.runId,
8818
+ executionId: input.execution.executionId,
8819
+ transition: { from: "in_progress", to: "in_progress" },
8820
+ toolCall: progressToolCall
8821
+ });
8822
+ notifyUpdate(progressToolCall, copy);
8823
+ }
8824
+ }).catch((err) => {
8825
+ console.warn(`Error handling progress report for tool "${input.durableTool.name}":`, err);
8826
+ });
8827
+ }
8828
+ };
8829
+ try {
8830
+ if (input.options?.signal?.aborted) {
8831
+ const error = input.options.signal.reason instanceof Error ? input.options.signal.reason : new DOMException(
8832
+ typeof input.options.signal.reason === "string" ? input.options.signal.reason : "Tool execution aborted.",
8833
+ "AbortError"
8834
+ );
8835
+ throw error;
8836
+ }
8837
+ let result = await withAbort(
8838
+ input.durableTool.execute(toJsonValue(input.toolCall.args), context, effectiveSignal),
8839
+ input.options?.signal
8840
+ );
8841
+ assertToolExecutionResult(result);
8842
+ if (input.afterToolCall) {
8843
+ result = await input.afterToolCall({
8844
+ tool: input.durableTool,
8845
+ result,
8846
+ context,
8847
+ signal: effectiveSignal
8848
+ });
8849
+ }
8850
+ assertToolExecutionResult(result);
8851
+ result = await spillLargeToolResult(
8852
+ result,
8853
+ this.archiveDirFor(input.agentId),
8854
+ input.toolCall.name
8855
+ );
8856
+ await progressQueue.catch(() => {
8857
+ });
8858
+ const finalState = result.ok ? "completed" : "failed";
8859
+ const completionPres = await presentToolCall(input.durableTool, {
8860
+ status: finalState,
8861
+ args: toJsonValue(input.toolCall.args),
8862
+ result
8863
+ });
8864
+ if (completionPres) {
8865
+ mergeToolCall(input.currentToolState, completionPres);
8866
+ }
8867
+ const rawOutput = "structuredContent" in result && result.structuredContent !== void 0 ? toJsonValue(result.structuredContent) : toJsonValue(result.content);
8868
+ if (input.currentToolState.rawOutput === void 0) {
8869
+ input.currentToolState.rawOutput = rawOutput;
8870
+ }
8871
+ const committed = await this.owned.commitToolResult({
8872
+ runId: input.runId,
8873
+ execution: input.execution,
8874
+ expectedRevision: input.revision,
8875
+ toolCallId: input.toolCallId,
8876
+ state: finalState,
8877
+ result,
8878
+ title: input.currentToolState.title ?? input.durableTool.annotations?.title ?? input.durableTool.name,
8879
+ kind: input.currentToolState.kind ?? input.durableTool.kind ?? "other",
8880
+ locations: input.currentToolState.locations,
8881
+ content: input.currentToolState.content,
8882
+ rawOutput: input.currentToolState.rawOutput,
8883
+ _meta: input.currentToolState._meta
8884
+ });
8885
+ input.setRevision(committed.run.revision);
8886
+ const finalToolCall = createMergedToolCall(
8887
+ input.toolCallId,
8888
+ finalState,
8889
+ input.currentToolState,
8890
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8891
+ input.durableTool.kind ?? "other"
8892
+ );
8893
+ notifyUpdate(finalToolCall);
8894
+ const projectedResult = await this.foldToolInteractionRecords(result, input.toolCallId, input.agentId);
8895
+ const ok = "ok" in projectedResult && typeof projectedResult.ok === "boolean" ? projectedResult.ok : !projectedResult.isError;
8896
+ return {
8897
+ toolCallId: input.providerToolCallId,
8898
+ toolName: input.toolCall.name,
8899
+ ...projectedResult,
8900
+ ok
8901
+ };
8902
+ } catch (error) {
8903
+ if (error instanceof RunInteractionBoundary) {
8904
+ const suspended = await this.owned.suspendToolCall({
8905
+ runId: input.runId,
8221
8906
  execution: input.execution,
8222
- expectedRevision: revision,
8223
- toolCallId,
8224
- state: "indeterminate",
8225
- reason,
8226
- result: { ok: false, content: null, error: reason }
8907
+ expectedRevision: input.revision,
8908
+ toolCallId: input.toolCallId
8227
8909
  });
8228
- setRevision(indeterminate.run.revision);
8229
- results.push(protocolFailure(reason));
8230
- } finally {
8231
- progressActive = false;
8232
- this.transientEvents.clearTool(input.run.id, toolCallId);
8910
+ input.setRevision(suspended.run.revision);
8911
+ throw error;
8233
8912
  }
8913
+ if (error instanceof RunInteractionCancelledError) throw error;
8914
+ const isPerCallAbort = Boolean(input.options?.signal?.aborted && !input.signal.aborted);
8915
+ const reason = error instanceof Error ? error.message : isPerCallAbort ? "Tool execution aborted." : "Tool execution outcome is indeterminate.";
8916
+ await progressQueue.catch(() => {
8917
+ });
8918
+ const failurePres = await presentToolCall(input.durableTool, {
8919
+ status: "failed",
8920
+ args: toJsonValue(input.toolCall.args),
8921
+ result: { ok: false, content: null, error: reason }
8922
+ });
8923
+ if (failurePres) {
8924
+ mergeToolCall(input.currentToolState, failurePres);
8925
+ }
8926
+ const commitState = isPerCallAbort ? "failed" : "indeterminate";
8927
+ const committed = await this.owned.commitToolResult({
8928
+ runId: input.runId,
8929
+ execution: input.execution,
8930
+ expectedRevision: input.revision,
8931
+ toolCallId: input.toolCallId,
8932
+ state: commitState,
8933
+ ...isPerCallAbort ? {} : { reason },
8934
+ result: { ok: false, content: null, error: reason },
8935
+ title: input.currentToolState.title ?? input.durableTool.annotations?.title ?? input.durableTool.name,
8936
+ kind: input.currentToolState.kind ?? input.durableTool.kind ?? "other",
8937
+ locations: input.currentToolState.locations,
8938
+ content: input.currentToolState.content,
8939
+ _meta: input.currentToolState._meta
8940
+ });
8941
+ input.setRevision(committed.run.revision);
8942
+ const finalToolCall = createMergedToolCall(
8943
+ input.toolCallId,
8944
+ "failed",
8945
+ input.currentToolState,
8946
+ input.durableTool.annotations?.title ?? input.durableTool.name,
8947
+ input.durableTool.kind ?? "other"
8948
+ );
8949
+ notifyUpdate(finalToolCall);
8950
+ return {
8951
+ toolCallId: input.providerToolCallId,
8952
+ toolName: input.toolCall.name,
8953
+ ok: false,
8954
+ content: null,
8955
+ error: reason
8956
+ };
8957
+ } finally {
8958
+ progressActive = false;
8959
+ this.transientEvents.clearTool(input.runId, input.toolCallId);
8234
8960
  }
8235
- input.driver?.clearCheckpoint();
8236
- return { results, revision };
8237
8961
  }
8238
8962
  async foldToolInteractionRecords(result, toolCallId, agentId) {
8239
8963
  const interactionMessages = (await this.owned.history(agentId)).filter(
@@ -8242,11 +8966,27 @@ var AgentRuntime = class _AgentRuntime {
8242
8966
  if (interactionMessages.length === 0) return result;
8243
8967
  const interactionText = interactionMessages.filter((message) => message.role === "interaction").map(renderInteractionText).join("\n\n");
8244
8968
  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}
8969
+ const appendedText = content === "null" || content.length === 0 ? interactionText : `${interactionText}
8248
8970
 
8249
- ${content}`
8971
+ ${content}`;
8972
+ if (Array.isArray(result.content)) {
8973
+ return {
8974
+ ...result,
8975
+ content: [{ type: "text", text: appendedText }]
8976
+ };
8977
+ }
8978
+ if (result.ok) {
8979
+ return {
8980
+ ok: true,
8981
+ content: appendedText,
8982
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
8983
+ };
8984
+ }
8985
+ return {
8986
+ ok: false,
8987
+ content: appendedText,
8988
+ error: result.error ?? "Tool execution failed.",
8989
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
8250
8990
  };
8251
8991
  }
8252
8992
  async requireAgent(agentId) {
@@ -8334,8 +9074,8 @@ ${content}`
8334
9074
  if (event.runId !== runId) continue;
8335
9075
  if (event.kind === "message_committed") {
8336
9076
  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);
9077
+ } else if (event.kind === "tool_state_changed" && ["completed", "failed"].includes(event.transition.to)) {
9078
+ subscription.clearTool(event.toolCall.toolCallId);
8339
9079
  }
8340
9080
  if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) {
8341
9081
  while (true) {
@@ -8533,6 +9273,19 @@ function toJsonValue(value) {
8533
9273
  assertJsonValue(value, "tool arguments");
8534
9274
  return value;
8535
9275
  }
9276
+ function createMergedToolCall(toolCallId, status, current, fallbackTitle, fallbackKind = "other") {
9277
+ return {
9278
+ toolCallId,
9279
+ title: current.title ?? fallbackTitle,
9280
+ kind: current.kind ?? fallbackKind,
9281
+ status,
9282
+ ...current.locations !== void 0 ? { locations: current.locations } : {},
9283
+ ...current.content !== void 0 ? { content: current.content } : {},
9284
+ ...current.rawInput !== void 0 ? { rawInput: current.rawInput } : {},
9285
+ ...current.rawOutput !== void 0 ? { rawOutput: current.rawOutput } : {},
9286
+ ...current._meta !== void 0 ? { _meta: current._meta } : {}
9287
+ };
9288
+ }
8536
9289
  function deferred2() {
8537
9290
  let resolve9;
8538
9291
  let reject;
@@ -8545,6 +9298,24 @@ function deferred2() {
8545
9298
 
8546
9299
  // src/runtime/durable-store.ts
8547
9300
  import { createHash } from "crypto";
9301
+ function defaultToolCallContent(result) {
9302
+ if ("content" in result && Array.isArray(result.content)) {
9303
+ return result.content.map((block) => {
9304
+ if (typeof block === "object" && block !== null && "type" in block && (block.type === "content" || block.type === "diff" || block.type === "terminal")) {
9305
+ return block;
9306
+ }
9307
+ return {
9308
+ type: "content",
9309
+ content: block
9310
+ };
9311
+ });
9312
+ }
9313
+ const content = result.content;
9314
+ if (typeof content === "string") {
9315
+ return [{ type: "content", content: { type: "text", text: content } }];
9316
+ }
9317
+ return [{ type: "content", content: { type: "text", text: JSON.stringify(content ?? null) } }];
9318
+ }
8548
9319
  var InMemoryStore = class _InMemoryStore {
8549
9320
  incarnation;
8550
9321
  agents = /* @__PURE__ */ new Map();
@@ -8663,7 +9434,7 @@ var InMemoryStore = class _InMemoryStore {
8663
9434
  }
8664
9435
  }
8665
9436
  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));
9437
+ for (const toolCall of state.toolCalls ?? []) store.toolCalls.set(toolCall.id ?? toolCall.toolCallId, clone(toolCall));
8667
9438
  store.events.push(...clone(state.events));
8668
9439
  for (const [key, receipt] of state.idempotency) store.idempotency.set(key, clone(receipt));
8669
9440
  for (const [key, receipt] of state.operationReceipts) {
@@ -8728,10 +9499,10 @@ var InMemoryStore = class _InMemoryStore {
8728
9499
  this.ownerEpoch = Math.max(this.ownerEpoch, ownerEpoch);
8729
9500
  for (const run of this.runs.values()) {
8730
9501
  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"));
9502
+ 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
9503
  const indeterminateToolCallIds = [];
8733
9504
  for (const toolCall of activeToolCalls) {
8734
- if (toolCall.state === "running") indeterminateToolCallIds.push(toolCall.id);
9505
+ if (toolCall.status === "in_progress" || toolCall.state === "in_progress") indeterminateToolCallIds.push(toolCall.id);
8735
9506
  this.interruptToolCall(run, toolCall, message);
8736
9507
  }
8737
9508
  const failure = indeterminateToolCallIds.length > 0 ? { code: "tool_indeterminate", message, toolCallIds: indeterminateToolCallIds } : { code: "runtime_interrupted", message, ownerEpoch };
@@ -8991,7 +9762,7 @@ var InMemoryStore = class _InMemoryStore {
8991
9762
  run.invalidatedBy = { messageId: original.id, messageRevision: actualRevision + 1 };
8992
9763
  if (run.state === "running" || run.state === "queued" || run.state === "input_required") {
8993
9764
  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"));
9765
+ 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
9766
  for (const toolCall of openToolCalls) this.interruptToolCall(run, toolCall, "Run superseded by Message revision.");
8996
9767
  } else {
8997
9768
  delete run.openInteractions;
@@ -9087,7 +9858,7 @@ var InMemoryStore = class _InMemoryStore {
9087
9858
  if (input.phasePayload !== void 0) assertJsonValue(input.phasePayload, "run.phasePayload");
9088
9859
  if (input.entryPhases !== void 0) assertEntryPhases(input.entryPhases, "run.entryPhases");
9089
9860
  const scope = createIdempotencyScope("start_run", input.agentId, input.idempotencyKey);
9090
- const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken, input.entryPhases);
9861
+ const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken, input.entryPhases, input.scope, input.cwd);
9091
9862
  const replay = this.replay(scope, payload);
9092
9863
  if (replay) return clone(replay);
9093
9864
  const waitingRun = isControlMetadata(input.metadata) ? void 0 : [...this.runs.values()].find((candidate) => candidate.agentId === input.agentId && candidate.state === "input_required");
@@ -9143,6 +9914,8 @@ var InMemoryStore = class _InMemoryStore {
9143
9914
  state: "queued",
9144
9915
  input: clone(normalizedInput),
9145
9916
  ...input.metadata ? { metadata: clone(input.metadata) } : {},
9917
+ ...input.scope !== void 0 ? { scope: clone(input.scope) } : {},
9918
+ ...input.cwd !== void 0 ? { cwd: input.cwd } : {},
9146
9919
  ...input.phasePayload === void 0 ? {} : { phasePayload: clone(input.phasePayload) },
9147
9920
  ...input.entryPhases === void 0 ? {} : { entryPhases: clone(input.entryPhases) },
9148
9921
  ...input.pinnedConfigToken === void 0 ? {} : { pinnedConfigToken: input.pinnedConfigToken },
@@ -9418,19 +10191,30 @@ var InMemoryStore = class _InMemoryStore {
9418
10191
  this.assertExecution(run, input.execution, input.expectedRevision);
9419
10192
  if (this.messages.has(input.requestMessageId)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9420
10193
  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
- }));
10194
+ const toolCalls = input.calls.map((call) => {
10195
+ const toolCallId = call.providerToolCallId || (call.toolCallId ?? createId("tool"));
10196
+ return {
10197
+ id: call.toolCallId ?? createId("tool"),
10198
+ toolCallId,
10199
+ ...call.providerToolCallId ? { providerToolCallId: call.providerToolCallId } : {},
10200
+ agentId: run.agentId,
10201
+ runId: run.id,
10202
+ executionId: input.execution.executionId,
10203
+ requestMessageId: input.requestMessageId,
10204
+ name: call.name,
10205
+ title: call.title ?? call.name,
10206
+ kind: call.kind ?? "other",
10207
+ status: "pending",
10208
+ state: "pending",
10209
+ ...call.content ? { content: clone(call.content) } : {},
10210
+ ...call.locations ? { locations: clone(call.locations) } : {},
10211
+ rawInput: clone(call.args),
10212
+ ...call._meta ? { _meta: clone(call._meta) } : {},
10213
+ args: clone(call.args),
10214
+ createdAt: timestamp,
10215
+ updatedAt: timestamp
10216
+ };
10217
+ });
9434
10218
  for (const toolCall of toolCalls) {
9435
10219
  if (this.toolCalls.has(toolCall.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9436
10220
  }
@@ -9466,15 +10250,27 @@ var InMemoryStore = class _InMemoryStore {
9466
10250
  const run = this.requireRun(input.runId);
9467
10251
  this.assertExecution(run, input.execution, input.expectedRevision);
9468
10252
  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";
10253
+ if (toolCall.status !== "pending" && toolCall.state !== "pending") throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
10254
+ const from = toolCall.status ?? "pending";
10255
+ toolCall.status = "in_progress";
10256
+ toolCall.state = "in_progress";
9471
10257
  toolCall.executionId = input.execution.executionId;
10258
+ if (input.title !== void 0) toolCall.title = input.title;
10259
+ if (input.kind !== void 0) toolCall.kind = input.kind;
10260
+ if (input.locations !== void 0) toolCall.locations = clone(input.locations);
10261
+ if (input.content !== void 0) toolCall.content = clone(input.content);
10262
+ if (input._meta !== void 0) {
10263
+ toolCall._meta = {
10264
+ ...toolCall._meta ?? {},
10265
+ ...clone(input._meta)
10266
+ };
10267
+ }
9472
10268
  toolCall.updatedAt = createTimestamp();
9473
10269
  this.storeToolCall(toolCall);
9474
10270
  run.revision += 1;
9475
10271
  run.updatedAt = toolCall.updatedAt;
9476
10272
  this.touchRun(run);
9477
- this.appendToolTransition(run, { from: "pending", to: "running" }, toolCall);
10273
+ this.appendToolTransition(run, { from, to: "in_progress" }, toolCall);
9478
10274
  const result = { run: clone(run), toolCall: clone(toolCall) };
9479
10275
  this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
9480
10276
  return result;
@@ -9488,16 +10284,18 @@ var InMemoryStore = class _InMemoryStore {
9488
10284
  const run = this.requireRun(input.runId);
9489
10285
  this.assertExecution(run, input.execution, input.expectedRevision);
9490
10286
  const toolCall = this.requireToolCall(input.toolCallId, run);
9491
- if (toolCall.state !== "running" || toolCall.executionId !== input.execution.executionId) {
10287
+ if (toolCall.status !== "in_progress" && toolCall.state !== "in_progress" || toolCall.executionId !== input.execution.executionId) {
9492
10288
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9493
10289
  }
10290
+ const from = toolCall.status ?? "in_progress";
10291
+ toolCall.status = "pending";
9494
10292
  toolCall.state = "pending";
9495
10293
  toolCall.updatedAt = createTimestamp();
9496
10294
  this.storeToolCall(toolCall);
9497
10295
  run.revision += 1;
9498
10296
  run.updatedAt = toolCall.updatedAt;
9499
10297
  this.touchRun(run);
9500
- this.appendToolTransition(run, { from: "running", to: "pending" }, toolCall);
10298
+ this.appendToolTransition(run, { from, to: "pending" }, toolCall);
9501
10299
  const result = { run: clone(run), toolCall: clone(toolCall) };
9502
10300
  this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
9503
10301
  return result;
@@ -9506,8 +10304,9 @@ var InMemoryStore = class _InMemoryStore {
9506
10304
  this.assertOwner(lease);
9507
10305
  assertToolExecutionResult(input.result);
9508
10306
  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`);
10307
+ const isCompleted = "ok" in input.result ? input.result.ok : !input.result.isError;
10308
+ if (input.state === "completed" && !isCompleted) throw new TypeError("completed ToolCall requires a successful result");
10309
+ if (input.state !== "completed" && isCompleted) throw new TypeError(`${input.state} ToolCall requires a failed result`);
9511
10310
  if (input.state === "indeterminate" && (!input.reason || input.reason.trim().length === 0)) throw new TypeError("indeterminate ToolCall requires a reason");
9512
10311
  const operationKey = `tool_commit:${input.toolCallId}`;
9513
10312
  const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.execution.executionId, input.state, input.result, input.reason ?? null]);
@@ -9517,10 +10316,9 @@ var InMemoryStore = class _InMemoryStore {
9517
10316
  this.assertExecution(run, input.execution, input.expectedRevision);
9518
10317
  const toolCall = this.requireToolCall(input.toolCallId, run);
9519
10318
  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) };
10319
+ const fromStatus = toolCall.status ?? (toolCall.state === "in_progress" ? "in_progress" : toolCall.state);
10320
+ const toStatus = input.state === "completed" ? "completed" : "failed";
10321
+ const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name ?? toolCall.title, ...clone(input.result) };
9524
10322
  const resultMessageId = createId("msg");
9525
10323
  const message = {
9526
10324
  id: resultMessageId,
@@ -9532,7 +10330,21 @@ var InMemoryStore = class _InMemoryStore {
9532
10330
  createdAt: createTimestamp()
9533
10331
  };
9534
10332
  this.storeMessage(message);
9535
- toolCall.state = input.state;
10333
+ toolCall.status = toStatus;
10334
+ toolCall.state = toStatus;
10335
+ if (input.title !== void 0) toolCall.title = input.title;
10336
+ if (input.kind !== void 0) toolCall.kind = input.kind;
10337
+ if (input.locations !== void 0) toolCall.locations = clone(input.locations);
10338
+ if (input.content !== void 0) toolCall.content = clone(input.content);
10339
+ else if (toolCall.content === void 0) toolCall.content = defaultToolCallContent(input.result);
10340
+ if (input.rawOutput !== void 0) toolCall.rawOutput = clone(input.rawOutput);
10341
+ 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);
10342
+ if (input._meta !== void 0) {
10343
+ toolCall._meta = {
10344
+ ...toolCall._meta ?? {},
10345
+ ...clone(input._meta)
10346
+ };
10347
+ }
9536
10348
  toolCall.result = durableResult;
9537
10349
  toolCall.resultMessageId = resultMessageId;
9538
10350
  if (input.state === "indeterminate") toolCall.reason = input.reason;
@@ -9542,13 +10354,13 @@ var InMemoryStore = class _InMemoryStore {
9542
10354
  run.revision += 1;
9543
10355
  run.updatedAt = message.createdAt;
9544
10356
  this.touchRun(run);
9545
- this.appendToolTransition(run, { from, to: input.state }, toolCall);
10357
+ this.appendToolTransition(run, { from: fromStatus, to: toStatus }, toolCall);
9546
10358
  this.appendMessage(run, message);
9547
10359
  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"));
10360
+ 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
10361
  const indeterminateToolCallIds = [toolCall.id];
9550
10362
  for (const candidate of openToolCalls) {
9551
- if (candidate.state === "running") indeterminateToolCallIds.push(candidate.id);
10363
+ if (candidate.status === "in_progress" || candidate.state === "in_progress") indeterminateToolCallIds.push(candidate.id);
9552
10364
  this.interruptToolCall(run, candidate, input.reason);
9553
10365
  }
9554
10366
  const failure = {
@@ -9627,14 +10439,14 @@ var InMemoryStore = class _InMemoryStore {
9627
10439
  return result2;
9628
10440
  }
9629
10441
  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) ? (() => {
10442
+ 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
10443
  const interactionState = run.checkpoint.data.runInteractions;
9632
10444
  const interactionCheckpoint = interactionState && typeof interactionState === "object" && !Array.isArray(interactionState) ? interactionState.checkpoint : void 0;
9633
10445
  const ids = interactionCheckpoint && typeof interactionCheckpoint === "object" && !Array.isArray(interactionCheckpoint) ? interactionCheckpoint.toolCallIds : void 0;
9634
10446
  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));
10447
+ return [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id && (toolCall.status === "pending" || toolCall.state === "pending") && allowed.has(toolCall.id));
9636
10448
  })() : [];
9637
- const indeterminateToolCallIds = activeToolCalls.filter((toolCall) => toolCall.state === "running").map((toolCall) => toolCall.id);
10449
+ const indeterminateToolCallIds = activeToolCalls.filter((toolCall) => toolCall.status === "in_progress" || toolCall.state === "in_progress").map((toolCall) => toolCall.id);
9638
10450
  for (const toolCall of activeToolCalls) this.interruptToolCall(run, toolCall, input.reason ?? "The Run was cancelled.");
9639
10451
  if (input.output && (typeof input.output.content === "string" ? input.output.content.length > 0 : input.output.content.length > 0)) {
9640
10452
  const output = { ...clone(input.output), sequenceWithinRun: this.nextMessageSequence(run.id) };
@@ -9681,6 +10493,74 @@ var InMemoryStore = class _InMemoryStore {
9681
10493
  const result = clone(run);
9682
10494
  return result;
9683
10495
  }
10496
+ recordToolCall(lease, input) {
10497
+ this.assertOwner(lease);
10498
+ const run = this.requireRun(input.runId);
10499
+ if (input.execution) {
10500
+ if (run.state !== "running" || !run.execution || run.execution.executionId !== input.execution.executionId || run.execution.ownerEpoch !== input.execution.ownerEpoch) {
10501
+ throw new RuntimeError("runtime_ownership_lost", {
10502
+ reason: "epoch_advanced",
10503
+ expectedEpoch: input.execution.ownerEpoch,
10504
+ actualEpoch: this.ownerEpoch
10505
+ });
10506
+ }
10507
+ }
10508
+ const toolCallId = input.update.toolCallId;
10509
+ const existing = this.toolCalls.get(toolCallId);
10510
+ const fromStatus = existing ? existing.status : null;
10511
+ const toStatus = input.update.status ?? (existing ? existing.status : "in_progress");
10512
+ const timestamp = createTimestamp();
10513
+ let stored;
10514
+ if (existing) {
10515
+ mergeToolCall(existing, input.update);
10516
+ existing.status = toStatus;
10517
+ existing.state = toStatus;
10518
+ if (input.external !== void 0) existing.external = input.external;
10519
+ existing.updatedAt = timestamp;
10520
+ stored = existing;
10521
+ } else {
10522
+ stored = {
10523
+ id: toolCallId,
10524
+ toolCallId,
10525
+ providerToolCallId: toolCallId,
10526
+ agentId: run.agentId,
10527
+ runId: run.id,
10528
+ executionId: input.execution?.executionId,
10529
+ title: "title" in input.update && input.update.title ? input.update.title : "external_tool",
10530
+ kind: "kind" in input.update && input.update.kind ? input.update.kind : "other",
10531
+ status: toStatus,
10532
+ state: toStatus,
10533
+ external: input.external ?? false,
10534
+ createdAt: timestamp,
10535
+ updatedAt: timestamp
10536
+ };
10537
+ mergeToolCall(stored, input.update);
10538
+ this.toolCalls.set(stored.id, stored);
10539
+ }
10540
+ this.storeToolCall(stored);
10541
+ run.revision += 1;
10542
+ run.updatedAt = timestamp;
10543
+ this.touchRun(run);
10544
+ const acpToolCall = {
10545
+ toolCallId: stored.toolCallId,
10546
+ title: stored.title,
10547
+ kind: stored.kind,
10548
+ status: stored.status,
10549
+ ...stored.content !== void 0 ? { content: clone(stored.content) } : {},
10550
+ ...stored.locations !== void 0 ? { locations: clone(stored.locations) } : {},
10551
+ ...stored.rawInput !== void 0 ? { rawInput: clone(stored.rawInput) } : {},
10552
+ ...stored.rawOutput !== void 0 ? { rawOutput: clone(stored.rawOutput) } : {},
10553
+ ...stored._meta !== void 0 ? { _meta: clone(stored._meta) } : {}
10554
+ };
10555
+ const event = this.baseEvent(run, {
10556
+ kind: "tool_state_changed",
10557
+ transition: { from: fromStatus, to: toStatus },
10558
+ toolCall: acpToolCall,
10559
+ ...input.external ? { external: true } : {}
10560
+ });
10561
+ this.appendEvent(event);
10562
+ return event;
10563
+ }
9684
10564
  snapshotRun(lease, runId) {
9685
10565
  this.assertOwner(lease);
9686
10566
  const run = this.requireRun(runId);
@@ -9691,6 +10571,8 @@ var InMemoryStore = class _InMemoryStore {
9691
10571
  revision: run.revision,
9692
10572
  input: clone(run.input),
9693
10573
  ...run.metadata ? { metadata: clone(run.metadata) } : {},
10574
+ ...run.scope !== void 0 ? { scope: clone(run.scope) } : {},
10575
+ ...run.cwd !== void 0 ? { cwd: run.cwd } : {},
9694
10576
  ...run.phasePayload === void 0 ? {} : { phasePayload: clone(run.phasePayload) },
9695
10577
  ...run.entryPhases === void 0 ? {} : { entryPhases: clone(run.entryPhases) },
9696
10578
  messageCount: this.messagesForRun(run.id).length,
@@ -9777,7 +10659,7 @@ var InMemoryStore = class _InMemoryStore {
9777
10659
  if (run.state === "running") blockedRunIds.add(run.id);
9778
10660
  }
9779
10661
  for (const tool of this.toolCalls.values()) {
9780
- if (tool.state === "pending" || tool.state === "running" || tool.state === "indeterminate") {
10662
+ if (tool.status === "pending" || tool.status === "in_progress" || tool.state === "pending" || tool.state === "in_progress") {
9781
10663
  blockedRunIds.add(tool.runId);
9782
10664
  }
9783
10665
  }
@@ -9918,13 +10800,13 @@ var InMemoryStore = class _InMemoryStore {
9918
10800
  }
9919
10801
  assertNoOpenTools(run, allowedPendingToolCallIds = []) {
9920
10802
  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)))) {
10803
+ 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
10804
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
9923
10805
  }
9924
10806
  }
9925
10807
  interruptToolCall(run, toolCall, reason) {
9926
10808
  const result = { ok: false, content: null, error: reason };
9927
- const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name, ...result };
10809
+ const durableResult = { toolCallId: toolCall.id, toolName: toolCall.name ?? toolCall.title, ...result };
9928
10810
  const message = {
9929
10811
  id: createId("msg"),
9930
10812
  agentId: run.agentId,
@@ -9934,13 +10816,14 @@ var InMemoryStore = class _InMemoryStore {
9934
10816
  sequenceWithinRun: this.nextMessageSequence(run.id),
9935
10817
  createdAt: createTimestamp()
9936
10818
  };
9937
- const from = toolCall.state;
9938
- const to = from === "running" ? "indeterminate" : "failed";
10819
+ const from = toolCall.status ?? toolCall.state ?? "in_progress";
10820
+ const to = "failed";
9939
10821
  this.storeMessage(message);
9940
10822
  toolCall.state = to;
10823
+ toolCall.status = to;
9941
10824
  toolCall.result = durableResult;
9942
10825
  toolCall.resultMessageId = message.id;
9943
- if (to === "indeterminate") toolCall.reason = reason;
10826
+ toolCall.reason = reason;
9944
10827
  toolCall.updatedAt = message.createdAt;
9945
10828
  this.storeToolCall(toolCall);
9946
10829
  run.revision += 1;
@@ -10101,11 +10984,24 @@ ${record.summary}`,
10101
10984
  this.appendEvent(transition);
10102
10985
  }
10103
10986
  appendToolTransition(run, transition, toolCall) {
10104
- this.appendEvent(this.baseEvent(run, {
10987
+ const acpToolCall = {
10988
+ toolCallId: toolCall.toolCallId ?? toolCall.id,
10989
+ title: toolCall.title ?? toolCall.name ?? toolCall.toolCallId,
10990
+ kind: toolCall.kind ?? "other",
10991
+ status: toolCall.status ?? toolCall.state,
10992
+ ...toolCall.content ? { content: clone(toolCall.content) } : {},
10993
+ ...toolCall.locations ? { locations: clone(toolCall.locations) } : {},
10994
+ ...toolCall.rawInput !== void 0 ? { rawInput: clone(toolCall.rawInput) } : {},
10995
+ ...toolCall.rawOutput !== void 0 ? { rawOutput: clone(toolCall.rawOutput) } : {},
10996
+ ...toolCall._meta ? { _meta: clone(toolCall._meta) } : {}
10997
+ };
10998
+ this.appendEvent({
10999
+ ...this.baseEvent(run),
10105
11000
  kind: "tool_state_changed",
10106
11001
  transition,
10107
- toolCall: clone(toolCall)
10108
- }));
11002
+ toolCall: acpToolCall,
11003
+ ...toolCall.external ? { external: true } : {}
11004
+ });
10109
11005
  }
10110
11006
  baseEvent(run, extra) {
10111
11007
  this.eventSequence += 1;
@@ -10207,6 +11103,9 @@ var MemoryOwnedStore = class {
10207
11103
  async cancelRun(input) {
10208
11104
  return this.store.cancelRun(this.lease, input);
10209
11105
  }
11106
+ async recordToolCall(input) {
11107
+ return this.store.recordToolCall(this.lease, input);
11108
+ }
10210
11109
  async snapshotRun(runId) {
10211
11110
  return this.store.snapshotRun(this.lease, runId);
10212
11111
  }
@@ -10350,19 +11249,19 @@ function requestMessageContent(toolCalls, contentBlocks) {
10350
11249
  const toolUse = (toolCall) => ({
10351
11250
  type: "tool_use",
10352
11251
  toolCallId: toolCall.id,
10353
- providerToolCallId: toolCall.providerToolCallId,
10354
- name: toolCall.name,
10355
- input: clone(toolCall.args)
11252
+ ...toolCall.providerToolCallId !== void 0 ? { providerToolCallId: toolCall.providerToolCallId } : {},
11253
+ name: toolCall.name ?? toolCall.title ?? "tool",
11254
+ input: clone(toolCall.args !== void 0 ? toolCall.args : toolCall.rawInput !== void 0 ? toolCall.rawInput : {})
10356
11255
  });
10357
11256
  if (!contentBlocks) return toolCalls.map(toolUse);
10358
- const reserved = new Map(toolCalls.map((toolCall) => [toolCall.providerToolCallId, toolCall]));
11257
+ const reserved = new Map(toolCalls.map((toolCall) => [toolCall.providerToolCallId ?? toolCall.id, toolCall]));
10359
11258
  const parts = [];
10360
11259
  for (const block of contentBlocks) {
10361
11260
  if (block.type === "text") {
10362
11261
  parts.push({ type: "text", text: block.text });
10363
11262
  } else if (block.type === "thinking") {
10364
11263
  parts.push({ type: "thinking", thinking: block.thinking, ...block.signature ? { signature: block.signature } : {} });
10365
- } else {
11264
+ } else if (block.type === "tool_call") {
10366
11265
  const toolCall = reserved.get(block.id);
10367
11266
  if (!toolCall) continue;
10368
11267
  reserved.delete(block.id);
@@ -10759,6 +11658,9 @@ var SqliteStore = class {
10759
11658
  async cancelRun(lease, input) {
10760
11659
  return this.invoke(lease, (store, current) => store.cancelRun(current, input));
10761
11660
  }
11661
+ async recordToolCall(lease, input) {
11662
+ return this.invoke(lease, (store, current) => store.recordToolCall(current, input));
11663
+ }
10762
11664
  async snapshotRun(lease, runId) {
10763
11665
  return this.invoke(lease, (store, current) => store.snapshotRun(current, runId), false);
10764
11666
  }
@@ -11059,7 +11961,9 @@ var SqliteStore = class {
11059
11961
  }
11060
11962
  getStmtInsertEvent() {
11061
11963
  return this.stmtInsertEvent ??= this.database.prepare(
11062
- "INSERT INTO run_events (id, run_id, payload_json, created_at) VALUES (?, ?, ?, ?)"
11964
+ `INSERT INTO run_events (id, run_id, payload_json, created_at)
11965
+ VALUES (?, ?, ?, ?)
11966
+ ON CONFLICT(id) DO UPDATE SET payload_json = excluded.payload_json`
11063
11967
  );
11064
11968
  }
11065
11969
  getStmtDeleteEvent() {
@@ -11142,7 +12046,7 @@ var SqliteStore = class {
11142
12046
  toolCall.id,
11143
12047
  toolCall.runId,
11144
12048
  toolCall.executionId,
11145
- toolCall.state,
12049
+ toolCall.status ?? toolCall.state ?? "in_progress",
11146
12050
  JSON.stringify(toolCall)
11147
12051
  );
11148
12052
  }
@@ -11530,6 +12434,9 @@ var SqliteOwnedStore = class {
11530
12434
  cancelRun(input) {
11531
12435
  return this.store.cancelRun(this.lease, input);
11532
12436
  }
12437
+ recordToolCall(input) {
12438
+ return this.store.recordToolCall(this.lease, input);
12439
+ }
11533
12440
  snapshotRun(runId) {
11534
12441
  return this.store.snapshotRun(this.lease, runId);
11535
12442
  }
@@ -11615,10 +12522,10 @@ export {
11615
12522
  isRuntimeError,
11616
12523
  loadExtensionsFromPath as loadExtensions,
11617
12524
  loadPhase,
11618
- loadPhaseSettings,
11619
12525
  loadPhases,
11620
12526
  loadSkill,
11621
12527
  loadSkills,
12528
+ mergeToolCall,
11622
12529
  parseAgentDefinition,
11623
12530
  parseFrontmatter,
11624
12531
  resolveConfigurationSnapshot