@rowan-agent/agent 0.9.7 → 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";
@@ -2539,7 +2711,9 @@ async function executeToolCalls(input) {
2539
2711
  return results;
2540
2712
  }
2541
2713
  function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
2714
+ const interaction = createPhaseInteractionDriver(state, phase.name, config.signal);
2542
2715
  return {
2716
+ interaction,
2543
2717
  snapshot() {
2544
2718
  return {
2545
2719
  systemPrompt: config.context.systemPrompt,
@@ -2709,62 +2883,6 @@ async function waitForBackgroundTasks(backgroundTasks) {
2709
2883
  return successful;
2710
2884
  }
2711
2885
 
2712
- // src/runtime/json.ts
2713
- function isPlainObject(value) {
2714
- const prototype = Object.getPrototypeOf(value);
2715
- return prototype === Object.prototype || prototype === null;
2716
- }
2717
- function hasOnlyDataProperties(value) {
2718
- return Reflect.ownKeys(value).every((key) => {
2719
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
2720
- return Boolean(descriptor && "value" in descriptor);
2721
- });
2722
- }
2723
- function visit(value, seen) {
2724
- if (value === null || typeof value === "boolean" || typeof value === "string") return true;
2725
- if (typeof value === "number") return Number.isFinite(value);
2726
- if (typeof value !== "object" || seen.has(value)) return false;
2727
- seen.add(value);
2728
- try {
2729
- if (Array.isArray(value)) {
2730
- if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
2731
- if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
2732
- for (let index = 0; index < value.length; index += 1) {
2733
- if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
2734
- }
2735
- return true;
2736
- }
2737
- if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
2738
- if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
2739
- if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
2740
- return Object.keys(value).every((key) => visit(value[key], seen));
2741
- } finally {
2742
- seen.delete(value);
2743
- }
2744
- }
2745
- function isJsonValue(value) {
2746
- return visit(value, /* @__PURE__ */ new Set());
2747
- }
2748
- function assertJsonValue(value, argument = "value") {
2749
- if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
2750
- }
2751
- function canonicalJson(value) {
2752
- assertJsonValue(value);
2753
- const encode = (candidate) => {
2754
- if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
2755
- if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
2756
- const object = candidate;
2757
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
2758
- };
2759
- return encode(value);
2760
- }
2761
- function utf8ByteLength(value) {
2762
- return new TextEncoder().encode(value).byteLength;
2763
- }
2764
- function assertUtf8ByteLimit(value, limit, argument = "value") {
2765
- if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
2766
- }
2767
-
2768
2886
  // src/runtime/execution.ts
2769
2887
  var EXECUTION_CHECKPOINT_CODEC = "rowan.agent.execution";
2770
2888
  var EXECUTION_CHECKPOINT_VERSION = 1;
@@ -2813,7 +2931,8 @@ function encodeExecutionCheckpoint(state) {
2813
2931
  currentPhase: state.currentPhase,
2814
2932
  attempt: state.attempt,
2815
2933
  metrics,
2816
- ...continuation ? { continuation } : {}
2934
+ ...continuation ? { continuation } : {},
2935
+ ...state.phaseInteractions === void 0 ? {} : { phaseInteractions: state.phaseInteractions }
2817
2936
  };
2818
2937
  assertJsonValue(data, "execution checkpoint");
2819
2938
  return {
@@ -2844,6 +2963,13 @@ function decodeExecutionCheckpoint(checkpoint) {
2844
2963
  ...checkpoint.data.continuation,
2845
2964
  previousResults: checkpoint.data.continuation.previousResults.map((result) => ({ ...result }))
2846
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
+ }
2847
2973
  } : {}
2848
2974
  };
2849
2975
  }
@@ -2869,6 +2995,16 @@ async function executeOnce(input) {
2869
2995
  status: "running",
2870
2996
  metrics: createMetrics()
2871
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
+ }
2872
3008
  const config = {
2873
3009
  context,
2874
3010
  execution: input.execution,
@@ -2904,6 +3040,20 @@ async function executeOnce(input) {
2904
3040
  return {
2905
3041
  type: "input_required",
2906
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,
2907
3057
  checkpoint: encodeExecutionCheckpoint(error.state),
2908
3058
  messages: snapshotMessages(context.messages)
2909
3059
  };
@@ -2933,8 +3083,15 @@ function isCheckpointData(value) {
2933
3083
  if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
2934
3084
  if (!isMetrics(value.metrics)) return false;
2935
3085
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
3086
+ if (value.phaseInteractions !== void 0 && !isPhaseInteractionState(value.phaseInteractions)) return false;
2936
3087
  return true;
2937
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
+ }
2938
3095
  function isMetrics(value) {
2939
3096
  if (!isRecord2(value)) return false;
2940
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));
@@ -5412,6 +5569,15 @@ var AgentRuntime = class _AgentRuntime {
5412
5569
  await this.owned.answerInput({ runId, requestId: input.requestId, expectedRevision: snapshot.revision, input: input.input });
5413
5570
  void this.pump();
5414
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
+ }
5415
5581
  async cancel(runId, reason) {
5416
5582
  this.assertOpen();
5417
5583
  const done = this.executionDone.get(runId);
@@ -5541,6 +5707,7 @@ var AgentRuntime = class _AgentRuntime {
5541
5707
  stream,
5542
5708
  maxAttempts: config.maxAttempts,
5543
5709
  checkpoint: claim.run.checkpoint,
5710
+ interactionAnswers: claim.run.interactionAnswers,
5544
5711
  signal: controller.signal,
5545
5712
  beforePhase: assembly.beforePhase,
5546
5713
  afterPhase: assembly.afterPhase,
@@ -5610,7 +5777,17 @@ var AgentRuntime = class _AgentRuntime {
5610
5777
  }
5611
5778
  if (result.type === "input_required") {
5612
5779
  const prompt = promptMessage(run, result.request.prompt, result.messages.length);
5613
- 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
+ });
5614
5791
  this.transientEvents.clear(run.id, claim.execution.executionId);
5615
5792
  return;
5616
5793
  }
@@ -5873,6 +6050,9 @@ var DurableRun = class {
5873
6050
  respond(input) {
5874
6051
  return this.runtime.respond(this.id, input);
5875
6052
  }
6053
+ respondInteraction(input) {
6054
+ return this.runtime.respondInteraction(this.id, input);
6055
+ }
5876
6056
  cancel(reason) {
5877
6057
  return this.runtime.cancel(this.id, reason);
5878
6058
  }
@@ -5899,7 +6079,14 @@ function durableOutcome(outcome) {
5899
6079
  function boundaryFromSnapshot(snapshot) {
5900
6080
  switch (snapshot.state) {
5901
6081
  case "input_required":
5902
- 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
+ };
5903
6090
  case "completed":
5904
6091
  return { type: "completed", outcome: snapshot.outcome, ...snapshot.output ? { output: snapshot.output } : {} };
5905
6092
  case "failed":
@@ -6395,7 +6582,8 @@ var InMemoryStore = class _InMemoryStore {
6395
6582
  if (typeof input.phase !== "string" || input.phase.length === 0) throw new TypeError("phase must be non-empty");
6396
6583
  const requestId = input.requestId ?? createId("input");
6397
6584
  const operationKey = `input_required:${requestId}`;
6398
- 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]);
6399
6587
  const replay = this.replayOperation(operationKey, operationPayload);
6400
6588
  if (replay) return clone(replay);
6401
6589
  const run = this.requireRun(input.runId);
@@ -6416,12 +6604,19 @@ var InMemoryStore = class _InMemoryStore {
6416
6604
  run.state = "input_required";
6417
6605
  run.checkpoint = clone(input.checkpoint);
6418
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
+ }
6419
6614
  delete run.execution;
6420
6615
  run.revision += 1;
6421
6616
  run.updatedAt = createTimestamp();
6422
6617
  this.appendMessage(run, prompt);
6423
- this.appendTransition(run, "running", "input_required", request, prompt);
6424
- 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) };
6425
6620
  this.writeOperationReceipt(operationKey, operationPayload, result);
6426
6621
  return result;
6427
6622
  }
@@ -6438,6 +6633,9 @@ var InMemoryStore = class _InMemoryStore {
6438
6633
  if (!run.openInputRequest || run.openInputRequest.id !== input.requestId) {
6439
6634
  throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.requestId, reason: "not_found" });
6440
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
+ }
6441
6639
  const message = {
6442
6640
  id: input.messageId ?? createId("msg"),
6443
6641
  agentId: run.agentId,
@@ -6460,6 +6658,50 @@ var InMemoryStore = class _InMemoryStore {
6460
6658
  this.writeOperationReceipt(operationKey, operationPayload, result);
6461
6659
  return result;
6462
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
+ }
6463
6705
  reserveToolCall(lease, input) {
6464
6706
  const providerToolCallId = input.providerToolCallId ?? (input.toolCallId ? String(input.toolCallId) : createId("provider"));
6465
6707
  const result = this.reserveToolCalls(lease, {
@@ -6654,6 +6896,8 @@ var InMemoryStore = class _InMemoryStore {
6654
6896
  if (input.outcome) run.outcome = clone(input.outcome);
6655
6897
  if (input.failure) run.failure = clone(input.failure);
6656
6898
  delete run.execution;
6899
+ delete run.openInteractions;
6900
+ delete run.interactionAnswers;
6657
6901
  run.revision += 1;
6658
6902
  run.updatedAt = createTimestamp();
6659
6903
  this.appendTransition(run, "running", nextState, void 0, output, input.outcome, input.failure);
@@ -6699,6 +6943,8 @@ var InMemoryStore = class _InMemoryStore {
6699
6943
  delete run.cancellationReason;
6700
6944
  delete run.execution;
6701
6945
  delete run.openInputRequest;
6946
+ delete run.openInteractions;
6947
+ delete run.interactionAnswers;
6702
6948
  run.revision += 1;
6703
6949
  run.updatedAt = createTimestamp();
6704
6950
  this.appendTransition(run, from, "failed", void 0, void 0, void 0, failure);
@@ -6710,6 +6956,8 @@ var InMemoryStore = class _InMemoryStore {
6710
6956
  run.cancellationReason = input.reason;
6711
6957
  delete run.execution;
6712
6958
  delete run.openInputRequest;
6959
+ delete run.openInteractions;
6960
+ delete run.interactionAnswers;
6713
6961
  run.revision += 1;
6714
6962
  run.updatedAt = createTimestamp();
6715
6963
  this.appendTransition(run, from, "cancelled", void 0, void 0, void 0, void 0, input.reason);
@@ -6741,7 +6989,13 @@ var InMemoryStore = class _InMemoryStore {
6741
6989
  const request = run.openInputRequest;
6742
6990
  const prompt = request && this.messages.get(request.messageId);
6743
6991
  if (!prompt || prompt.role !== "assistant") throw new Error(`Run ${run.id} has an invalid input prompt.`);
6744
- 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
+ };
6745
6999
  }
6746
7000
  case "completed": {
6747
7001
  const output = this.messagesForRun(run.id).filter((message) => message.role === "assistant").at(-1);
@@ -6963,13 +7217,14 @@ var InMemoryStore = class _InMemoryStore {
6963
7217
  message
6964
7218
  }));
6965
7219
  }
6966
- appendTransition(run, from, to, request, prompt, outcome, failure, reason) {
7220
+ appendTransition(run, from, to, request, prompt, outcome, failure, reason, interactions, answers) {
6967
7221
  const transition = {
6968
7222
  ...this.baseEvent(run),
6969
7223
  kind: "run_state_changed",
6970
7224
  from,
6971
7225
  to,
6972
7226
  ...to === "input_required" && request && prompt ? { request: { id: request.id, phase: request.phase, prompt } } : {},
7227
+ ...to === "input_required" ? { interactions: clone(interactions ?? []), answers: clone(answers ?? {}) } : {},
6973
7228
  ...to === "completed" && outcome ? { outcome } : {},
6974
7229
  ...to === "failed" && failure ? { failure } : {},
6975
7230
  ...to === "cancelled" && reason ? { reason } : {}
@@ -7047,6 +7302,9 @@ var MemoryOwnedStore = class {
7047
7302
  async answerInput(input) {
7048
7303
  return this.store.answerInput(this.lease, input);
7049
7304
  }
7305
+ async answerInteraction(input) {
7306
+ return this.store.answerInteraction(this.lease, input);
7307
+ }
7050
7308
  async commitOutcome(input) {
7051
7309
  return this.store.commitOutcome(this.lease, input);
7052
7310
  }
@@ -7348,6 +7606,9 @@ var SqliteStore = class {
7348
7606
  async answerInput(lease, input) {
7349
7607
  return this.invoke(lease, (store, current) => store.answerInput(current, input));
7350
7608
  }
7609
+ async answerInteraction(lease, input) {
7610
+ return this.invoke(lease, (store, current) => store.answerInteraction(current, input));
7611
+ }
7351
7612
  async commitOutcome(lease, input) {
7352
7613
  return this.invoke(lease, (store, current) => store.commitOutcome(current, input));
7353
7614
  }
@@ -7665,6 +7926,9 @@ var SqliteOwnedStore = class {
7665
7926
  answerInput(input) {
7666
7927
  return this.store.answerInput(this.lease, input);
7667
7928
  }
7929
+ answerInteraction(input) {
7930
+ return this.store.answerInteraction(this.lease, input);
7931
+ }
7668
7932
  commitOutcome(input) {
7669
7933
  return this.store.commitOutcome(this.lease, input);
7670
7934
  }
@@ -7767,6 +8031,8 @@ export {
7767
8031
  ExtensionLifetimeError,
7768
8032
  InMemoryConfigProvider,
7769
8033
  InMemoryStore,
8034
+ PhaseInteractionBoundary,
8035
+ PhaseInteractionCancelledError,
7770
8036
  ResourceRegistry,
7771
8037
  ResourceRegistryError,
7772
8038
  RuntimeBootstrapRegistry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",