@rowan-agent/agent 0.9.6 → 0.9.8

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
@@ -1052,6 +1052,178 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1052
1052
  };
1053
1053
  }
1054
1054
 
1055
+ // src/runtime/json.ts
1056
+ function isPlainObject(value) {
1057
+ const prototype = Object.getPrototypeOf(value);
1058
+ return prototype === Object.prototype || prototype === null;
1059
+ }
1060
+ function hasOnlyDataProperties(value) {
1061
+ return Reflect.ownKeys(value).every((key) => {
1062
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1063
+ return Boolean(descriptor && "value" in descriptor);
1064
+ });
1065
+ }
1066
+ function visit(value, seen) {
1067
+ if (value === null || typeof value === "boolean" || typeof value === "string") return true;
1068
+ if (typeof value === "number") return Number.isFinite(value);
1069
+ if (typeof value !== "object" || seen.has(value)) return false;
1070
+ seen.add(value);
1071
+ try {
1072
+ if (Array.isArray(value)) {
1073
+ if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
1074
+ if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
1075
+ for (let index = 0; index < value.length; index += 1) {
1076
+ if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
1077
+ }
1078
+ return true;
1079
+ }
1080
+ if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
1081
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
1082
+ if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
1083
+ return Object.keys(value).every((key) => visit(value[key], seen));
1084
+ } finally {
1085
+ seen.delete(value);
1086
+ }
1087
+ }
1088
+ function isJsonValue(value) {
1089
+ return visit(value, /* @__PURE__ */ new Set());
1090
+ }
1091
+ function assertJsonValue(value, argument = "value") {
1092
+ if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
1093
+ }
1094
+ function canonicalJson(value) {
1095
+ assertJsonValue(value);
1096
+ const encode = (candidate) => {
1097
+ if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
1098
+ if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
1099
+ const object = candidate;
1100
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
1101
+ };
1102
+ return encode(value);
1103
+ }
1104
+ function utf8ByteLength(value) {
1105
+ return new TextEncoder().encode(value).byteLength;
1106
+ }
1107
+ function assertUtf8ByteLimit(value, limit, argument = "value") {
1108
+ if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
1109
+ }
1110
+
1111
+ // src/harness/phases/interactions.ts
1112
+ var PhaseInteractionCancelledError = class extends Error {
1113
+ code = "phase_interaction_cancelled";
1114
+ constructor() {
1115
+ super("Phase interaction work was cancelled.");
1116
+ this.name = "PhaseInteractionCancelledError";
1117
+ }
1118
+ };
1119
+ var PhaseInteractionBoundary = class extends Error {
1120
+ constructor(state, interactions) {
1121
+ super("Phase interactions require host input.");
1122
+ this.state = state;
1123
+ this.interactions = interactions;
1124
+ this.name = "PhaseInteractionBoundary";
1125
+ }
1126
+ state;
1127
+ interactions;
1128
+ };
1129
+ function createPhaseInteractionDriver(state, phase, signal) {
1130
+ const stored = state.phaseInteractions;
1131
+ const requests = new Map(
1132
+ stored?.requests.map((request) => [request.id, { ...request }]) ?? []
1133
+ );
1134
+ const answers = new Map(Object.entries(stored?.answers ?? {}));
1135
+ const usedRequestIds = /* @__PURE__ */ new Set();
1136
+ let checkpoint = stored?.checkpoint;
1137
+ const snapshot = () => ({
1138
+ requests: [...requests.values()].map((request) => ({
1139
+ ...request,
1140
+ status: answers.has(request.id) ? "answered" : request.status
1141
+ })),
1142
+ answers: Object.fromEntries(answers),
1143
+ ...checkpoint === void 0 ? {} : { checkpoint }
1144
+ });
1145
+ const sync = () => {
1146
+ state.phaseInteractions = snapshot();
1147
+ };
1148
+ const assertActive = () => {
1149
+ if (signal?.aborted) throw new PhaseInteractionCancelledError();
1150
+ };
1151
+ const matchingStoredRequest = (input) => {
1152
+ const fingerprint = interactionFingerprint(input);
1153
+ return [...requests.values()].find((request) => !usedRequestIds.has(request.id) && interactionFingerprint(request) === fingerprint);
1154
+ };
1155
+ return {
1156
+ signal: signal ?? new AbortController().signal,
1157
+ request(input) {
1158
+ assertActive();
1159
+ if (input.id !== void 0 && input.id.trim().length === 0) {
1160
+ throw new TypeError("Phase interaction id must be non-empty.");
1161
+ }
1162
+ if (input.prompt.trim().length === 0) {
1163
+ throw new TypeError("Phase interaction prompt must be non-empty.");
1164
+ }
1165
+ if (!PHASE_INTERACTION_KINDS.includes(input.kind)) {
1166
+ throw new TypeError(`Unsupported Phase interaction kind: ${String(input.kind)}.`);
1167
+ }
1168
+ if (input.payload !== void 0) assertJsonValue(input.payload, "Phase interaction payload");
1169
+ const matched = input.id === void 0 ? matchingStoredRequest(input) : void 0;
1170
+ const id = input.id ?? matched?.id ?? createId("interaction");
1171
+ const existing = requests.get(id);
1172
+ if (existing) {
1173
+ if (interactionFingerprint(existing) !== interactionFingerprint(input)) {
1174
+ throw new Error(`Phase interaction ${id} was requested with different metadata.`);
1175
+ }
1176
+ usedRequestIds.add(id);
1177
+ return {
1178
+ ...existing,
1179
+ status: answers.has(id) ? "answered" : existing.status
1180
+ };
1181
+ }
1182
+ const request = {
1183
+ id,
1184
+ phase,
1185
+ kind: input.kind,
1186
+ prompt: input.prompt,
1187
+ ...input.payload === void 0 ? {} : { payload: input.payload },
1188
+ createdAt: createTimestamp(),
1189
+ status: "pending"
1190
+ };
1191
+ requests.set(id, request);
1192
+ usedRequestIds.add(id);
1193
+ sync();
1194
+ return request;
1195
+ },
1196
+ pending() {
1197
+ return [...requests.values()].filter((request) => request.status === "pending" && !answers.has(request.id)).map((request) => ({ ...request, status: "pending" }));
1198
+ },
1199
+ answers() {
1200
+ return new Map(answers);
1201
+ },
1202
+ suspend(input = {}) {
1203
+ assertActive();
1204
+ const pending = this.pending();
1205
+ if (pending.length === 0) throw new Error("Cannot suspend without pending Phase interactions.");
1206
+ if (input.checkpoint !== void 0) checkpoint = input.checkpoint;
1207
+ state.status = "suspended";
1208
+ sync();
1209
+ throw new PhaseInteractionBoundary(state, pending);
1210
+ }
1211
+ };
1212
+ }
1213
+ var PHASE_INTERACTION_KINDS = [
1214
+ "user_input",
1215
+ "permission",
1216
+ "elicitation",
1217
+ "confirmation"
1218
+ ];
1219
+ function interactionFingerprint(value) {
1220
+ return canonicalJson({
1221
+ kind: value.kind,
1222
+ prompt: value.prompt,
1223
+ payload: value.payload ?? null
1224
+ });
1225
+ }
1226
+
1055
1227
  // src/harness/phases/loader.ts
1056
1228
  import { existsSync as existsSync2, statSync as statSync2 } from "fs";
1057
1229
  import { readdir as readdir2 } from "fs/promises";
@@ -1648,6 +1820,7 @@ function buildModelRequest(input, options) {
1648
1820
  }));
1649
1821
  return {
1650
1822
  model: options?.model ?? { provider: "", id: "" },
1823
+ ...options?.thinkingLevel === void 0 ? {} : { thinkingLevel: options.thinkingLevel },
1651
1824
  system: systemText,
1652
1825
  messages,
1653
1826
  tools: modelTools.length > 0 ? modelTools : void 0
@@ -2538,7 +2711,9 @@ async function executeToolCalls(input) {
2538
2711
  return results;
2539
2712
  }
2540
2713
  function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
2714
+ const interaction = createPhaseInteractionDriver(state, phase.name, config.signal);
2541
2715
  return {
2716
+ interaction,
2542
2717
  snapshot() {
2543
2718
  return {
2544
2719
  systemPrompt: config.context.systemPrompt,
@@ -2566,7 +2741,10 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2566
2741
  skills: phaseContext.skills,
2567
2742
  promptGuidelines: phaseContext.promptGuidelines,
2568
2743
  appendSystemPrompt: phaseContext.appendSystemPrompt
2569
- }, { model: phase.model ?? config.model });
2744
+ }, {
2745
+ model: phase.model ?? config.model,
2746
+ ...config.thinkingLevel === void 0 ? {} : { thinkingLevel: config.thinkingLevel }
2747
+ });
2570
2748
  if (!request.tools) {
2571
2749
  if (phaseContext.tools.length > 0) {
2572
2750
  request.tools = phaseContext.tools.map((t) => ({
@@ -2705,62 +2883,6 @@ async function waitForBackgroundTasks(backgroundTasks) {
2705
2883
  return successful;
2706
2884
  }
2707
2885
 
2708
- // src/runtime/json.ts
2709
- function isPlainObject(value) {
2710
- const prototype = Object.getPrototypeOf(value);
2711
- return prototype === Object.prototype || prototype === null;
2712
- }
2713
- function hasOnlyDataProperties(value) {
2714
- return Reflect.ownKeys(value).every((key) => {
2715
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
2716
- return Boolean(descriptor && "value" in descriptor);
2717
- });
2718
- }
2719
- function visit(value, seen) {
2720
- if (value === null || typeof value === "boolean" || typeof value === "string") return true;
2721
- if (typeof value === "number") return Number.isFinite(value);
2722
- if (typeof value !== "object" || seen.has(value)) return false;
2723
- seen.add(value);
2724
- try {
2725
- if (Array.isArray(value)) {
2726
- if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
2727
- if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
2728
- for (let index = 0; index < value.length; index += 1) {
2729
- if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
2730
- }
2731
- return true;
2732
- }
2733
- if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
2734
- if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
2735
- if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
2736
- return Object.keys(value).every((key) => visit(value[key], seen));
2737
- } finally {
2738
- seen.delete(value);
2739
- }
2740
- }
2741
- function isJsonValue(value) {
2742
- return visit(value, /* @__PURE__ */ new Set());
2743
- }
2744
- function assertJsonValue(value, argument = "value") {
2745
- if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
2746
- }
2747
- function canonicalJson(value) {
2748
- assertJsonValue(value);
2749
- const encode = (candidate) => {
2750
- if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
2751
- if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
2752
- const object = candidate;
2753
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
2754
- };
2755
- return encode(value);
2756
- }
2757
- function utf8ByteLength(value) {
2758
- return new TextEncoder().encode(value).byteLength;
2759
- }
2760
- function assertUtf8ByteLimit(value, limit, argument = "value") {
2761
- if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
2762
- }
2763
-
2764
2886
  // src/runtime/execution.ts
2765
2887
  var EXECUTION_CHECKPOINT_CODEC = "rowan.agent.execution";
2766
2888
  var EXECUTION_CHECKPOINT_VERSION = 1;
@@ -2809,7 +2931,8 @@ function encodeExecutionCheckpoint(state) {
2809
2931
  currentPhase: state.currentPhase,
2810
2932
  attempt: state.attempt,
2811
2933
  metrics,
2812
- ...continuation ? { continuation } : {}
2934
+ ...continuation ? { continuation } : {},
2935
+ ...state.phaseInteractions === void 0 ? {} : { phaseInteractions: state.phaseInteractions }
2813
2936
  };
2814
2937
  assertJsonValue(data, "execution checkpoint");
2815
2938
  return {
@@ -2840,6 +2963,13 @@ function decodeExecutionCheckpoint(checkpoint) {
2840
2963
  ...checkpoint.data.continuation,
2841
2964
  previousResults: checkpoint.data.continuation.previousResults.map((result) => ({ ...result }))
2842
2965
  }
2966
+ } : {},
2967
+ ...checkpoint.data.phaseInteractions ? {
2968
+ phaseInteractions: {
2969
+ requests: checkpoint.data.phaseInteractions.requests.map((request) => ({ ...request })),
2970
+ answers: { ...checkpoint.data.phaseInteractions.answers },
2971
+ ...checkpoint.data.phaseInteractions.checkpoint === void 0 ? {} : { checkpoint: checkpoint.data.phaseInteractions.checkpoint }
2972
+ }
2843
2973
  } : {}
2844
2974
  };
2845
2975
  }
@@ -2865,10 +2995,21 @@ async function executeOnce(input) {
2865
2995
  status: "running",
2866
2996
  metrics: createMetrics()
2867
2997
  };
2998
+ if (input.interactionAnswers) {
2999
+ state.phaseInteractions = {
3000
+ requests: state.phaseInteractions?.requests ?? [],
3001
+ answers: {
3002
+ ...state.phaseInteractions?.answers ?? {},
3003
+ ...input.interactionAnswers
3004
+ },
3005
+ ...state.phaseInteractions?.checkpoint === void 0 ? {} : { checkpoint: state.phaseInteractions.checkpoint }
3006
+ };
3007
+ }
2868
3008
  const config = {
2869
3009
  context,
2870
3010
  execution: input.execution,
2871
3011
  model: input.model,
3012
+ ...input.thinkingLevel === void 0 ? {} : { thinkingLevel: input.thinkingLevel },
2872
3013
  stream: input.stream,
2873
3014
  maxAttempts: input.maxAttempts,
2874
3015
  signal: input.signal,
@@ -2899,6 +3040,20 @@ async function executeOnce(input) {
2899
3040
  return {
2900
3041
  type: "input_required",
2901
3042
  request: error.request,
3043
+ interactions: [],
3044
+ checkpoint: encodeExecutionCheckpoint(error.state),
3045
+ messages: snapshotMessages(context.messages)
3046
+ };
3047
+ }
3048
+ if (error instanceof PhaseInteractionBoundary) {
3049
+ return {
3050
+ type: "input_required",
3051
+ request: {
3052
+ phase: error.interactions[0]?.phase ?? state.currentPhase,
3053
+ prompt: error.interactions.map((interaction) => interaction.prompt).join("\n"),
3054
+ requestedAt: error.interactions[0]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
3055
+ },
3056
+ interactions: error.interactions,
2902
3057
  checkpoint: encodeExecutionCheckpoint(error.state),
2903
3058
  messages: snapshotMessages(context.messages)
2904
3059
  };
@@ -2928,8 +3083,15 @@ function isCheckpointData(value) {
2928
3083
  if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
2929
3084
  if (!isMetrics(value.metrics)) return false;
2930
3085
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
3086
+ if (value.phaseInteractions !== void 0 && !isPhaseInteractionState(value.phaseInteractions)) return false;
2931
3087
  return true;
2932
3088
  }
3089
+ function isPhaseInteractionState(value) {
3090
+ if (!isRecord2(value) || !Array.isArray(value.requests) || !isRecord2(value.answers)) return false;
3091
+ if (!Object.values(value.answers).every((answer) => isJsonValue(answer))) return false;
3092
+ if (value.checkpoint !== void 0 && !isJsonValue(value.checkpoint)) return false;
3093
+ return value.requests.every((request) => isRecord2(request) && typeof request.id === "string" && typeof request.phase === "string" && ["user_input", "permission", "elicitation", "confirmation"].includes(request.kind) && typeof request.prompt === "string" && typeof request.createdAt === "string" && ["pending", "answered", "denied", "cancelled", "expired"].includes(request.status) && (request.payload === void 0 || isJsonValue(request.payload)));
3094
+ }
2933
3095
  function isMetrics(value) {
2934
3096
  if (!isRecord2(value)) return false;
2935
3097
  return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord2(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
@@ -2941,6 +3103,33 @@ function isContinuation(value) {
2941
3103
  }
2942
3104
 
2943
3105
  // src/runtime/contracts.ts
3106
+ var THINKING_LEVELS = [
3107
+ "off",
3108
+ "minimal",
3109
+ "low",
3110
+ "medium",
3111
+ "high",
3112
+ "xhigh",
3113
+ "max"
3114
+ ];
3115
+ function thinkingLevelFromMetadata(metadata) {
3116
+ const record = isRecord3(metadata) ? metadata : void 0;
3117
+ const everyield = record?.everyield;
3118
+ if (!isRecord3(everyield)) return void 0;
3119
+ const level = everyield.thinkingLevel;
3120
+ return typeof level === "string" && THINKING_LEVELS.includes(level) ? level : void 0;
3121
+ }
3122
+ function thinkingLevelFromMessages(messages) {
3123
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
3124
+ const message = messages[index];
3125
+ if (message?.role !== "user") continue;
3126
+ const metadata = message.metadata;
3127
+ const kind = isRecord3(metadata) && typeof metadata.kind === "string" ? metadata.kind : void 0;
3128
+ if (kind === "phase_prompt" || kind === "phase_input") continue;
3129
+ return thinkingLevelFromMetadata(metadata);
3130
+ }
3131
+ return void 0;
3132
+ }
2944
3133
  function isAgentConfiguration(config) {
2945
3134
  return !("resources" in config);
2946
3135
  }
@@ -5380,6 +5569,15 @@ var AgentRuntime = class _AgentRuntime {
5380
5569
  await this.owned.answerInput({ runId, requestId: input.requestId, expectedRevision: snapshot.revision, input: input.input });
5381
5570
  void this.pump();
5382
5571
  }
5572
+ async respondInteraction(runId, input) {
5573
+ this.assertOpen();
5574
+ const snapshot = await this.owned.snapshotRun(runId);
5575
+ if (snapshot.state !== "input_required" || !snapshot.interactions.some((interaction) => interaction.id === input.interactionId)) {
5576
+ throw new RuntimeError("input_request_conflict", { runId, requestId: input.interactionId, reason: "not_found" });
5577
+ }
5578
+ await this.owned.answerInteraction({ runId, interactionId: input.interactionId, expectedRevision: snapshot.revision, input: input.input });
5579
+ void this.pump();
5580
+ }
5383
5581
  async cancel(runId, reason) {
5384
5582
  this.assertOpen();
5385
5583
  const done = this.executionDone.get(runId);
@@ -5505,9 +5703,11 @@ var AgentRuntime = class _AgentRuntime {
5505
5703
  ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
5506
5704
  },
5507
5705
  model,
5706
+ thinkingLevel: thinkingLevelFromMessages(executionContext.messages),
5508
5707
  stream,
5509
5708
  maxAttempts: config.maxAttempts,
5510
5709
  checkpoint: claim.run.checkpoint,
5710
+ interactionAnswers: claim.run.interactionAnswers,
5511
5711
  signal: controller.signal,
5512
5712
  beforePhase: assembly.beforePhase,
5513
5713
  afterPhase: assembly.afterPhase,
@@ -5577,7 +5777,17 @@ var AgentRuntime = class _AgentRuntime {
5577
5777
  }
5578
5778
  if (result.type === "input_required") {
5579
5779
  const prompt = promptMessage(run, result.request.prompt, result.messages.length);
5580
- await this.owned.commitInputRequired({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, requestId: createId("input"), phase: result.request.phase, prompt, checkpoint: result.checkpoint });
5780
+ await this.owned.commitInputRequired({
5781
+ runId: run.id,
5782
+ execution: claim.execution,
5783
+ expectedRevision: executionRevision,
5784
+ requestId: createId("input"),
5785
+ phase: result.request.phase,
5786
+ prompt,
5787
+ checkpoint: result.checkpoint,
5788
+ interactions: result.interactions,
5789
+ interactionAnswers: claim.run.interactionAnswers
5790
+ });
5581
5791
  this.transientEvents.clear(run.id, claim.execution.executionId);
5582
5792
  return;
5583
5793
  }
@@ -5840,6 +6050,9 @@ var DurableRun = class {
5840
6050
  respond(input) {
5841
6051
  return this.runtime.respond(this.id, input);
5842
6052
  }
6053
+ respondInteraction(input) {
6054
+ return this.runtime.respondInteraction(this.id, input);
6055
+ }
5843
6056
  cancel(reason) {
5844
6057
  return this.runtime.cancel(this.id, reason);
5845
6058
  }
@@ -5866,7 +6079,14 @@ function durableOutcome(outcome) {
5866
6079
  function boundaryFromSnapshot(snapshot) {
5867
6080
  switch (snapshot.state) {
5868
6081
  case "input_required":
5869
- return { type: "input_required", requestId: snapshot.request.id, phase: snapshot.request.phase, prompt: snapshot.request.prompt };
6082
+ return {
6083
+ type: "input_required",
6084
+ requestId: snapshot.request.id,
6085
+ phase: snapshot.request.phase,
6086
+ prompt: snapshot.request.prompt,
6087
+ interactions: snapshot.interactions,
6088
+ answers: snapshot.answers
6089
+ };
5870
6090
  case "completed":
5871
6091
  return { type: "completed", outcome: snapshot.outcome, ...snapshot.output ? { output: snapshot.output } : {} };
5872
6092
  case "failed":
@@ -6362,7 +6582,8 @@ var InMemoryStore = class _InMemoryStore {
6362
6582
  if (typeof input.phase !== "string" || input.phase.length === 0) throw new TypeError("phase must be non-empty");
6363
6583
  const requestId = input.requestId ?? createId("input");
6364
6584
  const operationKey = `input_required:${requestId}`;
6365
- const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.phase, input.prompt.id, input.checkpoint]);
6585
+ const interactions = input.interactions ?? [];
6586
+ const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.phase, input.prompt.id, input.checkpoint, interactions, input.interactionAnswers ?? null]);
6366
6587
  const replay = this.replayOperation(operationKey, operationPayload);
6367
6588
  if (replay) return clone(replay);
6368
6589
  const run = this.requireRun(input.runId);
@@ -6383,12 +6604,19 @@ var InMemoryStore = class _InMemoryStore {
6383
6604
  run.state = "input_required";
6384
6605
  run.checkpoint = clone(input.checkpoint);
6385
6606
  run.openInputRequest = request;
6607
+ if (interactions.length > 0) {
6608
+ run.openInteractions = clone(interactions);
6609
+ run.interactionAnswers = clone(input.interactionAnswers ?? run.interactionAnswers ?? {});
6610
+ } else {
6611
+ delete run.openInteractions;
6612
+ delete run.interactionAnswers;
6613
+ }
6386
6614
  delete run.execution;
6387
6615
  run.revision += 1;
6388
6616
  run.updatedAt = createTimestamp();
6389
6617
  this.appendMessage(run, prompt);
6390
- this.appendTransition(run, "running", "input_required", request, prompt);
6391
- const result = { run: clone(run), prompt: clone(prompt), request: clone(request) };
6618
+ this.appendTransition(run, "running", "input_required", request, prompt, void 0, void 0, void 0, interactions, run.interactionAnswers ?? {});
6619
+ const result = { run: clone(run), prompt: clone(prompt), request: clone(request), interactions: clone(interactions) };
6392
6620
  this.writeOperationReceipt(operationKey, operationPayload, result);
6393
6621
  return result;
6394
6622
  }
@@ -6405,6 +6633,9 @@ var InMemoryStore = class _InMemoryStore {
6405
6633
  if (!run.openInputRequest || run.openInputRequest.id !== input.requestId) {
6406
6634
  throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.requestId, reason: "not_found" });
6407
6635
  }
6636
+ if (run.openInteractions && run.openInteractions.length > 0) {
6637
+ throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.requestId, reason: "not_found" });
6638
+ }
6408
6639
  const message = {
6409
6640
  id: input.messageId ?? createId("msg"),
6410
6641
  agentId: run.agentId,
@@ -6427,6 +6658,50 @@ var InMemoryStore = class _InMemoryStore {
6427
6658
  this.writeOperationReceipt(operationKey, operationPayload, result);
6428
6659
  return result;
6429
6660
  }
6661
+ answerInteraction(lease, input) {
6662
+ this.assertOwner(lease);
6663
+ if (input.interactionId.trim().length === 0) throw new TypeError("interactionId must be non-empty");
6664
+ const operationKey = `interaction_answer:${input.runId}:${input.interactionId}`;
6665
+ const operationPayload = canonicalJson(input.input);
6666
+ const replay = this.replayOperation(operationKey, operationPayload);
6667
+ if (replay) return clone(replay);
6668
+ const run = this.requireRun(input.runId);
6669
+ this.assertRevision(run, input.expectedRevision);
6670
+ this.assertState(run, ["input_required"]);
6671
+ const openInteractions = run.openInteractions ?? [];
6672
+ if (!openInteractions.some((interaction) => interaction.id === input.interactionId)) {
6673
+ throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.interactionId, reason: "not_found" });
6674
+ }
6675
+ const message = {
6676
+ id: createId("msg"),
6677
+ agentId: run.agentId,
6678
+ runId: run.id,
6679
+ role: "user",
6680
+ content: typeof input.input === "string" ? input.input : JSON.stringify(input.input),
6681
+ metadata: { kind: "phase_interaction", interactionId: input.interactionId },
6682
+ sequenceWithinRun: this.nextMessageSequence(run.id),
6683
+ createdAt: createTimestamp()
6684
+ };
6685
+ this.messages.set(message.id, message);
6686
+ run.openInteractions = openInteractions.filter((interaction) => interaction.id !== input.interactionId);
6687
+ run.interactionAnswers = {
6688
+ ...run.interactionAnswers ?? {},
6689
+ [input.interactionId]: clone(input.input)
6690
+ };
6691
+ run.revision += 1;
6692
+ run.updatedAt = message.createdAt;
6693
+ this.appendMessage(run, message);
6694
+ if (run.openInteractions.length === 0) {
6695
+ delete run.openInteractions;
6696
+ delete run.openInputRequest;
6697
+ run.readySequence = this.nextReady(run.agentId);
6698
+ run.state = "queued";
6699
+ this.appendTransition(run, "input_required", "queued");
6700
+ }
6701
+ const result = clone(run);
6702
+ this.writeOperationReceipt(operationKey, operationPayload, result);
6703
+ return result;
6704
+ }
6430
6705
  reserveToolCall(lease, input) {
6431
6706
  const providerToolCallId = input.providerToolCallId ?? (input.toolCallId ? String(input.toolCallId) : createId("provider"));
6432
6707
  const result = this.reserveToolCalls(lease, {
@@ -6621,6 +6896,8 @@ var InMemoryStore = class _InMemoryStore {
6621
6896
  if (input.outcome) run.outcome = clone(input.outcome);
6622
6897
  if (input.failure) run.failure = clone(input.failure);
6623
6898
  delete run.execution;
6899
+ delete run.openInteractions;
6900
+ delete run.interactionAnswers;
6624
6901
  run.revision += 1;
6625
6902
  run.updatedAt = createTimestamp();
6626
6903
  this.appendTransition(run, "running", nextState, void 0, output, input.outcome, input.failure);
@@ -6666,6 +6943,8 @@ var InMemoryStore = class _InMemoryStore {
6666
6943
  delete run.cancellationReason;
6667
6944
  delete run.execution;
6668
6945
  delete run.openInputRequest;
6946
+ delete run.openInteractions;
6947
+ delete run.interactionAnswers;
6669
6948
  run.revision += 1;
6670
6949
  run.updatedAt = createTimestamp();
6671
6950
  this.appendTransition(run, from, "failed", void 0, void 0, void 0, failure);
@@ -6677,6 +6956,8 @@ var InMemoryStore = class _InMemoryStore {
6677
6956
  run.cancellationReason = input.reason;
6678
6957
  delete run.execution;
6679
6958
  delete run.openInputRequest;
6959
+ delete run.openInteractions;
6960
+ delete run.interactionAnswers;
6680
6961
  run.revision += 1;
6681
6962
  run.updatedAt = createTimestamp();
6682
6963
  this.appendTransition(run, from, "cancelled", void 0, void 0, void 0, void 0, input.reason);
@@ -6708,7 +6989,13 @@ var InMemoryStore = class _InMemoryStore {
6708
6989
  const request = run.openInputRequest;
6709
6990
  const prompt = request && this.messages.get(request.messageId);
6710
6991
  if (!prompt || prompt.role !== "assistant") throw new Error(`Run ${run.id} has an invalid input prompt.`);
6711
- return { ...base, state: "input_required", request: { id: request.id, phase: request.phase, prompt: clone(prompt) } };
6992
+ return {
6993
+ ...base,
6994
+ state: "input_required",
6995
+ request: { id: request.id, phase: request.phase, prompt: clone(prompt) },
6996
+ interactions: clone(run.openInteractions ?? []),
6997
+ answers: clone(run.interactionAnswers ?? {})
6998
+ };
6712
6999
  }
6713
7000
  case "completed": {
6714
7001
  const output = this.messagesForRun(run.id).filter((message) => message.role === "assistant").at(-1);
@@ -6930,13 +7217,14 @@ var InMemoryStore = class _InMemoryStore {
6930
7217
  message
6931
7218
  }));
6932
7219
  }
6933
- appendTransition(run, from, to, request, prompt, outcome, failure, reason) {
7220
+ appendTransition(run, from, to, request, prompt, outcome, failure, reason, interactions, answers) {
6934
7221
  const transition = {
6935
7222
  ...this.baseEvent(run),
6936
7223
  kind: "run_state_changed",
6937
7224
  from,
6938
7225
  to,
6939
7226
  ...to === "input_required" && request && prompt ? { request: { id: request.id, phase: request.phase, prompt } } : {},
7227
+ ...to === "input_required" ? { interactions: clone(interactions ?? []), answers: clone(answers ?? {}) } : {},
6940
7228
  ...to === "completed" && outcome ? { outcome } : {},
6941
7229
  ...to === "failed" && failure ? { failure } : {},
6942
7230
  ...to === "cancelled" && reason ? { reason } : {}
@@ -7014,6 +7302,9 @@ var MemoryOwnedStore = class {
7014
7302
  async answerInput(input) {
7015
7303
  return this.store.answerInput(this.lease, input);
7016
7304
  }
7305
+ async answerInteraction(input) {
7306
+ return this.store.answerInteraction(this.lease, input);
7307
+ }
7017
7308
  async commitOutcome(input) {
7018
7309
  return this.store.commitOutcome(this.lease, input);
7019
7310
  }
@@ -7315,6 +7606,9 @@ var SqliteStore = class {
7315
7606
  async answerInput(lease, input) {
7316
7607
  return this.invoke(lease, (store, current) => store.answerInput(current, input));
7317
7608
  }
7609
+ async answerInteraction(lease, input) {
7610
+ return this.invoke(lease, (store, current) => store.answerInteraction(current, input));
7611
+ }
7318
7612
  async commitOutcome(lease, input) {
7319
7613
  return this.invoke(lease, (store, current) => store.commitOutcome(current, input));
7320
7614
  }
@@ -7632,6 +7926,9 @@ var SqliteOwnedStore = class {
7632
7926
  answerInput(input) {
7633
7927
  return this.store.answerInput(this.lease, input);
7634
7928
  }
7929
+ answerInteraction(input) {
7930
+ return this.store.answerInteraction(this.lease, input);
7931
+ }
7635
7932
  commitOutcome(input) {
7636
7933
  return this.store.commitOutcome(this.lease, input);
7637
7934
  }
@@ -7734,6 +8031,8 @@ export {
7734
8031
  ExtensionLifetimeError,
7735
8032
  InMemoryConfigProvider,
7736
8033
  InMemoryStore,
8034
+ PhaseInteractionBoundary,
8035
+ PhaseInteractionCancelledError,
7737
8036
  ResourceRegistry,
7738
8037
  ResourceRegistryError,
7739
8038
  RuntimeBootstrapRegistry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",