@moxt-ai/mobius 0.0.355 → 0.0.357

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.
Files changed (59) hide show
  1. package/LICENSE +202 -0
  2. package/dist/cli.js +388 -187
  3. package/dist/cli.js.map +4 -4
  4. package/dist/engine/acp/adapter/codex/adapter.d.ts.map +1 -1
  5. package/dist/engine/acp/adapter/codex/adapter.js +6 -4
  6. package/dist/engine/acp/adapter/codex/adapter.js.map +1 -1
  7. package/dist/engine/acp/adapter/session-events.d.ts.map +1 -1
  8. package/dist/engine/acp/adapter/session-events.js +57 -56
  9. package/dist/engine/acp/adapter/session-events.js.map +1 -1
  10. package/dist/engine/acp/content/content-externalizer.d.ts +8 -2
  11. package/dist/engine/acp/content/content-externalizer.d.ts.map +1 -1
  12. package/dist/engine/acp/content/content-externalizer.js +23 -6
  13. package/dist/engine/acp/content/content-externalizer.js.map +1 -1
  14. package/dist/engine/acp/harness/codex/history.d.ts.map +1 -1
  15. package/dist/engine/acp/harness/codex/history.js +25 -23
  16. package/dist/engine/acp/harness/codex/history.js.map +1 -1
  17. package/dist/engine/agent/runtime-agents.d.ts +1 -1
  18. package/dist/engine/agent/runtime-agents.d.ts.map +1 -1
  19. package/dist/engine/agent/runtime-agents.js +2 -2
  20. package/dist/engine/agent/runtime-agents.js.map +1 -1
  21. package/dist/engine/runtime/command/registry/interactions.d.ts.map +1 -1
  22. package/dist/engine/runtime/command/registry/interactions.js +7 -1
  23. package/dist/engine/runtime/command/registry/interactions.js.map +1 -1
  24. package/dist/engine/runtime/run.d.ts.map +1 -1
  25. package/dist/engine/runtime/run.js +1 -1
  26. package/dist/engine/runtime/run.js.map +1 -1
  27. package/dist/engine/session/content/external-content-store.d.ts +4 -0
  28. package/dist/engine/session/content/external-content-store.d.ts.map +1 -1
  29. package/dist/engine/session/content/external-content-store.js +31 -1
  30. package/dist/engine/session/content/external-content-store.js.map +1 -1
  31. package/dist/engine/session/live/agent-session-restore.d.ts +1 -1
  32. package/dist/engine/session/live/agent-session-restore.d.ts.map +1 -1
  33. package/dist/engine/session/live/agent-session.d.ts +1 -1
  34. package/dist/engine/session/live/agent-session.d.ts.map +1 -1
  35. package/dist/engine/session/live/agent-session.js +23 -2
  36. package/dist/engine/session/live/agent-session.js.map +1 -1
  37. package/dist/engine/session/live/session-configurations.d.ts +1 -1
  38. package/dist/engine/session/live/session-configurations.d.ts.map +1 -1
  39. package/dist/engine/session/live/session-configurations.js +1 -1
  40. package/dist/engine/session/live/session-configurations.js.map +1 -1
  41. package/dist/engine/session/live/session-restore.d.ts.map +1 -1
  42. package/dist/engine/session/live/session-restore.js +7 -1
  43. package/dist/engine/session/live/session-restore.js.map +1 -1
  44. package/dist/engine/session/native/native-session-browser.d.ts.map +1 -1
  45. package/dist/engine/session/native/native-session-browser.js +1 -0
  46. package/dist/engine/session/native/native-session-browser.js.map +1 -1
  47. package/dist/engine/session/prompt/execute-prompt.d.ts.map +1 -1
  48. package/dist/engine/session/prompt/execute-prompt.js +1 -0
  49. package/dist/engine/session/prompt/execute-prompt.js.map +1 -1
  50. package/dist/engine/session/prompt/prompt-lifecycle.d.ts.map +1 -1
  51. package/dist/engine/session/prompt/prompt-lifecycle.js +6 -1
  52. package/dist/engine/session/prompt/prompt-lifecycle.js.map +1 -1
  53. package/dist/engine/session/turn/execution-receipts.d.ts +2 -0
  54. package/dist/engine/session/turn/execution-receipts.d.ts.map +1 -1
  55. package/dist/engine/session/turn/execution-receipts.js +18 -1
  56. package/dist/engine/session/turn/execution-receipts.js.map +1 -1
  57. package/dist/engine.js +375 -186
  58. package/dist/engine.js.map +4 -4
  59. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -2086,7 +2086,7 @@ var init_package = __esm({
2086
2086
  "package.json"() {
2087
2087
  package_default = {
2088
2088
  name: "@moxt-ai/mobius",
2089
- version: "0.0.355",
2089
+ version: "0.0.357",
2090
2090
  description: "Mobius CLI for pairing and running local AI agent services.",
2091
2091
  type: "module",
2092
2092
  bin: {
@@ -2161,8 +2161,8 @@ var init_package = __esm({
2161
2161
  ws: "^8.21.3"
2162
2162
  },
2163
2163
  keywords: [],
2164
- author: "",
2165
- license: "ISC",
2164
+ author: "Moxt",
2165
+ license: "Apache-2.0",
2166
2166
  bugs: {
2167
2167
  url: "https://github.com/moxt-ai/mobius/issues"
2168
2168
  },
@@ -27237,6 +27237,7 @@ var init_events = __esm({
27237
27237
  external_exports.object({
27238
27238
  ...lifecycleBase,
27239
27239
  kind: external_exports.literal("prompt.completed"),
27240
+ execution: external_exports.literal("not-started").optional(),
27240
27241
  stopReason: shortString
27241
27242
  }).readonly(),
27242
27243
  external_exports.object({
@@ -27963,7 +27964,9 @@ var init_native = __esm({
27963
27964
  // unrecognized delivery from a newer peer reads as absent instead of failing the snapshot.
27964
27965
  command: commandDeliverySchema.optional().catch(void 0),
27965
27966
  // Absent from older Runtimes and while the last started command is still running.
27966
- lastTurn: lastTurnOutcomeSchema.optional().catch(void 0)
27967
+ lastTurn: lastTurnOutcomeSchema.optional().catch(void 0),
27968
+ // Runtime-owned support; older Relays strip this field before exposing the snapshot.
27969
+ queuedCancellation: external_exports.literal("before-start").optional().catch(void 0)
27967
27970
  }).readonly(),
27968
27971
  external_exports.object({
27969
27972
  ...resultEnvelope,
@@ -76923,40 +76926,42 @@ async function replayCodexHistory(client2, threadId, emit, signal) {
76923
76926
  continue;
76924
76927
  }
76925
76928
  if (item.type === "userMessage") {
76926
- const text2 = item.content.flatMap((content) => {
76929
+ const contents = item.content.flatMap((content) => {
76927
76930
  if (typeof content !== "object" || content === null) {
76928
76931
  return [];
76929
76932
  }
76930
76933
  const type = Reflect.get(content, "type");
76931
- const text3 = Reflect.get(content, "text");
76934
+ const text2 = Reflect.get(content, "text");
76932
76935
  const url2 = Reflect.get(content, "url");
76933
76936
  if (type === "image" && typeof url2 === "string") {
76934
76937
  return [`[@image](${url2})`];
76935
76938
  }
76936
- return type === "text" && typeof text3 === "string" ? [text3] : [];
76937
- }).join("");
76938
- const history = await embedLocalHistoryImages(
76939
- { status: "available", truncated: false, messages: [{ role: "user", text: text2 }] },
76940
- deadline
76941
- );
76942
- if (history.status !== "available") {
76943
- continue;
76944
- }
76945
- const message = history.messages[0];
76946
- for (const image of message?.images ?? []) {
76947
- await emit({
76948
- content: { type: "image", data: image.data, mimeType: image.mimeType },
76949
- messageId: item.id,
76950
- sessionUpdate: "user_message_chunk"
76951
- });
76952
- }
76953
- const restoredText = message?.text ?? "";
76954
- if (restoredText.length > 0) {
76955
- await emit({
76956
- content: { text: restoredText, type: "text" },
76957
- messageId: item.id,
76958
- sessionUpdate: "user_message_chunk"
76959
- });
76939
+ return type === "text" && typeof text2 === "string" ? [text2] : [];
76940
+ });
76941
+ for (const text2 of contents) {
76942
+ const history = await embedLocalHistoryImages(
76943
+ { status: "available", truncated: false, messages: [{ role: "user", text: text2 }] },
76944
+ deadline
76945
+ );
76946
+ if (history.status !== "available") {
76947
+ continue;
76948
+ }
76949
+ const message = history.messages[0];
76950
+ for (const image of message?.images ?? []) {
76951
+ await emit({
76952
+ content: { type: "image", data: image.data, mimeType: image.mimeType },
76953
+ messageId: item.id,
76954
+ sessionUpdate: "user_message_chunk"
76955
+ });
76956
+ }
76957
+ const restoredText = message?.text ?? "";
76958
+ if (restoredText.length > 0) {
76959
+ await emit({
76960
+ content: { text: restoredText, type: "text" },
76961
+ messageId: item.id,
76962
+ sessionUpdate: "user_message_chunk"
76963
+ });
76964
+ }
76960
76965
  }
76961
76966
  } else if (item.type === "agentMessage") {
76962
76967
  await emit(agentMessageUpdate(item.id, item.text));
@@ -78349,8 +78354,69 @@ var init_pi_commands = __esm({
78349
78354
  }
78350
78355
  });
78351
78356
 
78357
+ // src/engine/acp/harness/pi/upstream/dist/acp/pi-history.js
78358
+ import { readFileSync as readFileSync3 } from "node:fs";
78359
+ import { RequestError as RequestError8 } from "@agentclientprotocol/sdk";
78360
+ function readPiHistory(sessionFile) {
78361
+ try {
78362
+ const entries = /* @__PURE__ */ new Map();
78363
+ let leaf = null;
78364
+ for (const line of readFileSync3(sessionFile, "utf8").split(/\r?\n/u)) {
78365
+ if (!line.trim()) {
78366
+ continue;
78367
+ }
78368
+ const value = JSON.parse(line);
78369
+ if (external_exports.object({ type: external_exports.literal("session") }).safeParse(value).success) {
78370
+ continue;
78371
+ }
78372
+ const entry = entrySchema.parse(value);
78373
+ entries.set(entry.id, entry);
78374
+ leaf = entry.id;
78375
+ }
78376
+ const history = [];
78377
+ const visited = /* @__PURE__ */ new Set();
78378
+ while (leaf !== null) {
78379
+ const entry = entries.get(leaf);
78380
+ if (entry === void 0 || visited.has(leaf)) {
78381
+ throw new Error("Invalid Pi history branch");
78382
+ }
78383
+ visited.add(leaf);
78384
+ if (entry.type === "message") {
78385
+ const { role } = external_exports.object({ role: external_exports.string() }).parse(entry.message);
78386
+ if (role === "user" || role === "assistant" || role === "toolResult") {
78387
+ history.push({ type: "message", message: messageSchema.parse(entry.message) });
78388
+ }
78389
+ } else if (entry.type === "compaction") {
78390
+ history.push({ type: "compaction", id: entry.id, summary: external_exports.string().parse(entry.summary) });
78391
+ }
78392
+ leaf = entry.parentId;
78393
+ }
78394
+ return history.reverse();
78395
+ } catch (cause) {
78396
+ const error62 = RequestError8.internalError({ code: "PI_HISTORY_READ_FAILED" }, "Unable to read Pi history");
78397
+ error62.cause = cause;
78398
+ throw error62;
78399
+ }
78400
+ }
78401
+ var entrySchema, messageSchema;
78402
+ var init_pi_history = __esm({
78403
+ "src/engine/acp/harness/pi/upstream/dist/acp/pi-history.js"() {
78404
+ "use strict";
78405
+ init_zod();
78406
+ entrySchema = external_exports.object({
78407
+ type: external_exports.string(),
78408
+ id: external_exports.string(),
78409
+ parentId: external_exports.string().nullable()
78410
+ }).passthrough();
78411
+ messageSchema = external_exports.object({
78412
+ role: external_exports.enum(["user", "assistant", "toolResult"]),
78413
+ content: external_exports.unknown()
78414
+ }).passthrough();
78415
+ }
78416
+ });
78417
+
78352
78418
  // src/engine/acp/harness/pi/upstream/dist/acp/pi-sessions.js
78353
- import { closeSync as closeSync2, existsSync as existsSync4, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync3, readSync as readSync2, statSync as statSync2 } from "node:fs";
78419
+ import { closeSync as closeSync2, existsSync as existsSync4, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync2 } from "node:fs";
78354
78420
  import { isAbsolute as isAbsolute10, join as join19, resolve as resolve11 } from "node:path";
78355
78421
  function getPiAgentDir(environment) {
78356
78422
  const configured = environment["PI_CODING_AGENT_DIR"];
@@ -78362,7 +78428,7 @@ function readSessionDirFromSettings(agentDir) {
78362
78428
  if (!existsSync4(settingsPath)) {
78363
78429
  return null;
78364
78430
  }
78365
- const raw = readFileSync3(settingsPath, "utf8");
78431
+ const raw = readFileSync4(settingsPath, "utf8");
78366
78432
  const data = JSON.parse(raw);
78367
78433
  if (!data || typeof data !== "object" || Array.isArray(data)) {
78368
78434
  return null;
@@ -78578,7 +78644,7 @@ function pickUpdatedAtFromTail(tail) {
78578
78644
  }
78579
78645
  function pickFallbackTitleFromHead(path8) {
78580
78646
  try {
78581
- const raw = readFileSync3(path8, { encoding: "utf8" });
78647
+ const raw = readFileSync4(path8, { encoding: "utf8" });
78582
78648
  const lines = raw.split(/\r?\n/);
78583
78649
  for (const line0 of lines) {
78584
78650
  const line = line0.trim();
@@ -78683,7 +78749,7 @@ var init_pi_sessions = __esm({
78683
78749
  });
78684
78750
 
78685
78751
  // src/engine/acp/harness/pi/upstream/dist/acp/pi-settings.js
78686
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
78752
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
78687
78753
  import { join as join20, resolve as resolve12 } from "node:path";
78688
78754
  function isObject2(x) {
78689
78755
  return Boolean(x) && typeof x === "object" && !Array.isArray(x);
@@ -78705,7 +78771,7 @@ function readJsonFile2(path8) {
78705
78771
  if (!existsSync5(path8)) {
78706
78772
  return {};
78707
78773
  }
78708
- const raw = readFileSync4(path8, "utf-8");
78774
+ const raw = readFileSync5(path8, "utf-8");
78709
78775
  const data = JSON.parse(raw);
78710
78776
  return isObject2(data) ? data : {};
78711
78777
  } catch {
@@ -78755,7 +78821,7 @@ var init_pi_settings = __esm({
78755
78821
  });
78756
78822
 
78757
78823
  // src/engine/acp/harness/pi/upstream/dist/acp/extension-questions.js
78758
- import { RequestError as RequestError8 } from "@agentclientprotocol/sdk";
78824
+ import { RequestError as RequestError9 } from "@agentclientprotocol/sdk";
78759
78825
  function questionField(question) {
78760
78826
  const title2 = question.title || "Answer";
78761
78827
  switch (question.method) {
@@ -78803,7 +78869,7 @@ var init_extension_questions = __esm({
78803
78869
  external_exports.object({ ...questionBase, method: external_exports.literal("editor"), prefill: external_exports.string().max(1e4).optional() })
78804
78870
  ]).readonly();
78805
78871
  QUESTION_TIMEOUT_MS = 36e5;
78806
- PiQuestionError = class extends RequestError8 {
78872
+ PiQuestionError = class extends RequestError9 {
78807
78873
  constructor(kind, cause) {
78808
78874
  const messages = {
78809
78875
  request_failed: "Pi could not request an answer.",
@@ -78915,7 +78981,7 @@ var init_extension_questions = __esm({
78915
78981
  throw new PiQuestionError("invalid_response");
78916
78982
  }
78917
78983
  } catch (cause) {
78918
- if (!signal.aborted && !(cause instanceof RequestError8 && cause.code === -32800)) {
78984
+ if (!signal.aborted && !(cause instanceof RequestError9 && cause.code === -32800)) {
78919
78985
  turn.controller.abort(cause instanceof PiQuestionError ? cause : new PiQuestionError("request_failed", cause));
78920
78986
  }
78921
78987
  } finally {
@@ -78931,14 +78997,14 @@ var init_extension_questions = __esm({
78931
78997
  });
78932
78998
 
78933
78999
  // src/engine/acp/harness/pi/upstream/dist/acp/session-store.js
78934
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync } from "node:fs";
79000
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync } from "node:fs";
78935
79001
  import { dirname as dirname8 } from "node:path";
78936
79002
  function ensureParentDir(path8) {
78937
79003
  mkdirSync2(dirname8(path8), { recursive: true });
78938
79004
  }
78939
79005
  function loadFile(path8) {
78940
79006
  try {
78941
- const raw = readFileSync5(path8, "utf-8");
79007
+ const raw = readFileSync6(path8, "utf-8");
78942
79008
  const parsed = JSON.parse(raw);
78943
79009
  if (parsed?.version !== 1 || typeof parsed.sessions !== "object" || !parsed.sessions) {
78944
79010
  return { version: 1, sessions: {} };
@@ -78990,7 +79056,7 @@ var init_session_store = __esm({
78990
79056
  });
78991
79057
 
78992
79058
  // src/engine/acp/harness/pi/upstream/dist/acp/slash-commands.js
78993
- import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
79059
+ import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
78994
79060
  import { join as join21, resolve as resolve13 } from "node:path";
78995
79061
  function parseFrontmatter(content) {
78996
79062
  const frontmatter = {};
@@ -79029,7 +79095,7 @@ function loadCommandsFromDir(dir, source, subdir = "") {
79029
79095
  continue;
79030
79096
  }
79031
79097
  try {
79032
- const rawContent = readFileSync6(fullPath, "utf-8");
79098
+ const rawContent = readFileSync7(fullPath, "utf-8");
79033
79099
  const { frontmatter, content } = parseFrontmatter(rawContent);
79034
79100
  const name2 = entry.name.slice(0, -3);
79035
79101
  const sourceStr = source === "user" ? subdir ? `(user:${subdir})` : "(user)" : subdir ? `(project:${subdir})` : "(project)";
@@ -79221,9 +79287,9 @@ var init_pi_tools = __esm({
79221
79287
 
79222
79288
  // src/engine/acp/harness/pi/upstream/dist/acp/session.js
79223
79289
  import { randomUUID as randomUUID10 } from "node:crypto";
79224
- import { readFileSync as readFileSync7 } from "node:fs";
79290
+ import { readFileSync as readFileSync8 } from "node:fs";
79225
79291
  import { isAbsolute as isAbsolute11, resolve as resolvePath } from "node:path";
79226
- import { RequestError as RequestError9 } from "@agentclientprotocol/sdk";
79292
+ import { RequestError as RequestError10 } from "@agentclientprotocol/sdk";
79227
79293
  function toUsageUpdate(stats) {
79228
79294
  const used = stats?.contextUsage?.tokens;
79229
79295
  const size = stats?.contextUsage?.contextWindow;
@@ -79338,10 +79404,11 @@ function toToolKind(toolName) {
79338
79404
  return "other";
79339
79405
  }
79340
79406
  }
79341
- var SessionManager, PiAcpSession;
79407
+ var nativeUserMessageSchema, SessionManager, PiAcpSession;
79342
79408
  var init_session = __esm({
79343
79409
  "src/engine/acp/harness/pi/upstream/dist/acp/session.js"() {
79344
79410
  "use strict";
79411
+ init_zod();
79345
79412
  init_process();
79346
79413
  init_auth_required();
79347
79414
  init_extension_questions();
@@ -79349,6 +79416,16 @@ var init_session = __esm({
79349
79416
  init_slash_commands();
79350
79417
  init_bash();
79351
79418
  init_pi_tools();
79419
+ nativeUserMessageSchema = external_exports.object({
79420
+ role: external_exports.literal("user"),
79421
+ content: external_exports.union([
79422
+ external_exports.string().transform((text2) => [{ type: "text", text: text2 }]),
79423
+ external_exports.array(external_exports.discriminatedUnion("type", [
79424
+ external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() }),
79425
+ external_exports.object({ type: external_exports.literal("image"), data: external_exports.string(), mimeType: external_exports.string() })
79426
+ ]))
79427
+ ])
79428
+ });
79352
79429
  SessionManager = class {
79353
79430
  sessions = /* @__PURE__ */ new Map();
79354
79431
  store = new SessionStore();
@@ -79397,7 +79474,7 @@ var init_session = __esm({
79397
79474
  });
79398
79475
  } catch (e) {
79399
79476
  if (e instanceof PiRpcSpawnError) {
79400
- throw RequestError9.internalError({ code: e.code }, e.message);
79477
+ throw RequestError10.internalError({ code: e.code }, e.message);
79401
79478
  }
79402
79479
  throw e;
79403
79480
  }
@@ -79426,7 +79503,7 @@ var init_session = __esm({
79426
79503
  get(sessionId) {
79427
79504
  const s = this.sessions.get(sessionId);
79428
79505
  if (!s) {
79429
- throw RequestError9.invalidParams(`Unknown sessionId: ${sessionId}`);
79506
+ throw RequestError10.invalidParams(`Unknown sessionId: ${sessionId}`);
79430
79507
  }
79431
79508
  return s;
79432
79509
  }
@@ -79526,8 +79603,8 @@ var init_session = __esm({
79526
79603
  if (cause instanceof PiRpcProcessError) {
79527
79604
  const pending = this.pendingTurn;
79528
79605
  const reason = pending?.questionSignal.reason;
79529
- const error62 = reason instanceof RequestError9 ? reason : RequestError9.internalError({ code: cause.code, details: cause.message });
79530
- if (!(reason instanceof RequestError9)) {
79606
+ const error62 = reason instanceof RequestError10 ? reason : RequestError10.internalError({ code: cause.code, details: cause.message });
79607
+ if (!(reason instanceof RequestError10)) {
79531
79608
  error62.cause = cause;
79532
79609
  }
79533
79610
  this.extensionQuestions.endTurn(error62);
@@ -79595,12 +79672,12 @@ var init_session = __esm({
79595
79672
  const signal = this.extensionQuestions.startTurn();
79596
79673
  try {
79597
79674
  const result = await this.proc.compact(customInstructions, { signal, timeoutMs });
79598
- if (signal.aborted && signal.reason instanceof RequestError9) {
79675
+ if (signal.aborted && signal.reason instanceof RequestError10) {
79599
79676
  throw signal.reason;
79600
79677
  }
79601
79678
  return result;
79602
79679
  } catch (cause) {
79603
- if (signal.aborted && signal.reason instanceof RequestError9) {
79680
+ if (signal.aborted && signal.reason instanceof RequestError10) {
79604
79681
  try {
79605
79682
  await this.proc.abort({ signal: this.proc.signal, timeoutMs });
79606
79683
  } catch (abortCause) {
@@ -79693,14 +79770,14 @@ var init_session = __esm({
79693
79770
  await this.publishContextUsage();
79694
79771
  const retryFailure = this.turnRetryFailure;
79695
79772
  this.turnRetryFailure = null;
79696
- if (questionSignal?.aborted && questionSignal.reason instanceof RequestError9 && !this.cancelRequested) {
79773
+ if (questionSignal?.aborted && questionSignal.reason instanceof RequestError10 && !this.cancelRequested) {
79697
79774
  this.pendingTurn?.reject(questionSignal.reason);
79698
79775
  } else if (retryFailure !== null && !this.cancelRequested) {
79699
79776
  this.pendingTurn?.reject(
79700
79777
  // The message rides in `data.details` so clients that unwrap the
79701
79778
  // JSON-RPC internal-error envelope (like Mobius) surface the real
79702
79779
  // model error; `code` keeps the failure machine-readable.
79703
- RequestError9.internalError({
79780
+ RequestError10.internalError({
79704
79781
  code: "pi_retries_exhausted",
79705
79782
  details: `Pi retries exhausted: ${retryFailure}`
79706
79783
  })
@@ -79784,6 +79861,17 @@ var init_session = __esm({
79784
79861
  handlePiEvent(ev2) {
79785
79862
  const type = String(ev2.type ?? "");
79786
79863
  switch (type) {
79864
+ case "message_start": {
79865
+ const message = nativeUserMessageSchema.safeParse(ev2.message);
79866
+ if (!message.success) {
79867
+ break;
79868
+ }
79869
+ this.lastEmit = this.lastEmit.then(() => this.conn.startUserMessage());
79870
+ for (const content of message.data.content) {
79871
+ this.emit({ sessionUpdate: "user_message_chunk", content });
79872
+ }
79873
+ break;
79874
+ }
79787
79875
  case "message_update": {
79788
79876
  const ame2 = ev2.assistantMessageEvent;
79789
79877
  if (this.turnRetryFailure !== null && (ame2?.type === "text_delta" || ame2?.type === "thinking_delta" || ame2?.type === "toolcall_start")) {
@@ -79890,7 +79978,7 @@ var init_session = __esm({
79890
79978
  if (p) {
79891
79979
  try {
79892
79980
  const abs = isAbsolute11(p) ? p : resolvePath(this.cwd, p);
79893
- snapshotOldText = readFileSync7(abs, "utf8");
79981
+ snapshotOldText = readFileSync8(abs, "utf8");
79894
79982
  this.fileSnapshots.set(toolCallId, { path: p, oldText: snapshotOldText });
79895
79983
  if (toolName === "edit") {
79896
79984
  for (const needle of getEditOldTexts(args)) {
@@ -79973,7 +80061,7 @@ var init_session = __esm({
79973
80061
  if (!isError && snapshot) {
79974
80062
  try {
79975
80063
  const abs = isAbsolute11(snapshot.path) ? snapshot.path : resolvePath(this.cwd, snapshot.path);
79976
- const newText = readFileSync7(abs, "utf8");
80064
+ const newText = readFileSync8(abs, "utf8");
79977
80065
  if (snapshot.oldText === null || newText !== snapshot.oldText) {
79978
80066
  hasStructuredDiff = true;
79979
80067
  content = [
@@ -80068,14 +80156,17 @@ var init_session = __esm({
80068
80156
  });
80069
80157
 
80070
80158
  // src/engine/acp/harness/pi/upstream/dist/acp/translate/pi-messages.js
80071
- function normalizePiMessageText(content) {
80159
+ function piUserMessageContent(content) {
80072
80160
  if (typeof content === "string") {
80073
- return content;
80161
+ return content.length === 0 ? [] : [{ type: "text", text: content }];
80074
80162
  }
80075
80163
  if (!Array.isArray(content)) {
80076
- return "";
80164
+ return [];
80077
80165
  }
80078
- return content.map((c) => c?.type === "text" && typeof c.text === "string" ? c.text : "").filter(Boolean).join("");
80166
+ return content.flatMap((value) => {
80167
+ const parsed = userContentSchema.safeParse(value);
80168
+ return parsed.success ? [parsed.data] : [];
80169
+ });
80079
80170
  }
80080
80171
  function normalizePiAssistantText(content) {
80081
80172
  if (!Array.isArray(content)) {
@@ -80083,9 +80174,15 @@ function normalizePiAssistantText(content) {
80083
80174
  }
80084
80175
  return content.map((c) => c?.type === "text" && typeof c.text === "string" ? c.text : "").filter(Boolean).join("");
80085
80176
  }
80177
+ var userContentSchema;
80086
80178
  var init_pi_messages = __esm({
80087
80179
  "src/engine/acp/harness/pi/upstream/dist/acp/translate/pi-messages.js"() {
80088
80180
  "use strict";
80181
+ init_zod();
80182
+ userContentSchema = external_exports.discriminatedUnion("type", [
80183
+ external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() }),
80184
+ external_exports.object({ type: external_exports.literal("image"), data: external_exports.string(), mimeType: external_exports.string() })
80185
+ ]);
80089
80186
  }
80090
80187
  });
80091
80188
 
@@ -80150,9 +80247,9 @@ var init_prompt = __esm({
80150
80247
  // src/engine/acp/harness/pi/upstream/dist/acp/agent.js
80151
80248
  import { spawnSync } from "node:child_process";
80152
80249
  import { randomUUID as randomUUID11 } from "node:crypto";
80153
- import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync3, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
80250
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync9, realpathSync as realpathSync3, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
80154
80251
  import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute12, join as join22 } from "node:path";
80155
- import { RequestError as RequestError10 } from "@agentclientprotocol/sdk";
80252
+ import { RequestError as RequestError11 } from "@agentclientprotocol/sdk";
80156
80253
  function thinkingLevelName(id) {
80157
80254
  return id.split(/[_-]/u).map((part) => part.length === 0 ? part : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
80158
80255
  }
@@ -80356,7 +80453,7 @@ async function setSessionModel(proc, requestedModelId) {
80356
80453
  }
80357
80454
  }
80358
80455
  if (!provider || !modelId) {
80359
- throw RequestError10.invalidParams(`Unknown modelId: ${requestedModelId}`);
80456
+ throw RequestError11.invalidParams(`Unknown modelId: ${requestedModelId}`);
80360
80457
  }
80361
80458
  await proc.setModel(provider, modelId);
80362
80459
  }
@@ -80510,7 +80607,7 @@ function buildStartupInfo(opts) {
80510
80607
  ];
80511
80608
  for (const settingsPath of settingsPaths) {
80512
80609
  try {
80513
- const settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
80610
+ const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
80514
80611
  const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
80515
80612
  for (const pkg of pkgs) {
80516
80613
  const s = String(pkg);
@@ -80543,6 +80640,7 @@ var init_agent2 = __esm({
80543
80640
  init_auth_required();
80544
80641
  init_paths();
80545
80642
  init_pi_commands();
80643
+ init_pi_history();
80546
80644
  init_pi_sessions();
80547
80645
  init_pi_settings();
80548
80646
  init_session();
@@ -80638,7 +80736,7 @@ var init_agent2 = __esm({
80638
80736
  const restorePromise = (async () => {
80639
80737
  const stored = this.findStoredSession(sessionId);
80640
80738
  if (!stored) {
80641
- throw RequestError10.invalidParams(`Unknown sessionId: ${sessionId}`);
80739
+ throw RequestError11.invalidParams(`Unknown sessionId: ${sessionId}`);
80642
80740
  }
80643
80741
  const cwd = opts?.cwd ?? stored.cwd;
80644
80742
  let proc;
@@ -80651,7 +80749,7 @@ var init_agent2 = __esm({
80651
80749
  });
80652
80750
  } catch (e) {
80653
80751
  if (e?.name === "PiRpcSpawnError") {
80654
- throw RequestError10.internalError({ code: e?.code }, String(e?.message ?? e));
80752
+ throw RequestError11.internalError({ code: e?.code }, String(e?.message ?? e));
80655
80753
  }
80656
80754
  throw e;
80657
80755
  }
@@ -80711,7 +80809,7 @@ var init_agent2 = __esm({
80711
80809
  }
80712
80810
  async newSession(params) {
80713
80811
  if (!isAbsolute12(params.cwd)) {
80714
- throw RequestError10.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
80812
+ throw RequestError11.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
80715
80813
  }
80716
80814
  this.lastSessionCwd = params.cwd;
80717
80815
  const fileCommands = loadSlashCommands(params.cwd, this.environment);
@@ -80750,16 +80848,16 @@ var init_agent2 = __esm({
80750
80848
  }
80751
80849
  if (availableModelsErr) {
80752
80850
  this.cleanupFailedNewSession(session.sessionId, state);
80753
- throw RequestError10.internalError({}, String(availableModelsErr?.message ?? availableModelsErr));
80851
+ throw RequestError11.internalError({}, String(availableModelsErr?.message ?? availableModelsErr));
80754
80852
  }
80755
80853
  const rawModelsCount = Array.isArray(availableModels?.models) ? availableModels.models.length : 0;
80756
80854
  if (rawModelsCount === 0) {
80757
80855
  this.cleanupFailedNewSession(session.sessionId, state);
80758
- throw RequestError10.authRequired({ authMethods: getAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
80856
+ throw RequestError11.authRequired({ authMethods: getAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
80759
80857
  }
80760
80858
  if (stateErr && maybeAuthRequiredError(stateErr)) {
80761
80859
  this.cleanupFailedNewSession(session.sessionId, state);
80762
- throw RequestError10.authRequired({ authMethods: getAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
80860
+ throw RequestError11.authRequired({ authMethods: getAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
80763
80861
  }
80764
80862
  let configuration;
80765
80863
  try {
@@ -80769,7 +80867,7 @@ var init_agent2 = __esm({
80769
80867
  configuration = await getSessionConfiguration(session.proc, { state, availableModels });
80770
80868
  } catch (err) {
80771
80869
  this.cleanupFailedNewSession(session.sessionId, state);
80772
- throw maybeAuthRequiredError(err) ?? RequestError10.internalError({}, String(err?.message ?? err));
80870
+ throw maybeAuthRequiredError(err) ?? RequestError11.internalError({}, String(err?.message ?? err));
80773
80871
  }
80774
80872
  const { configOptions, models, modes } = configuration;
80775
80873
  const quietStartup = getQuietStartup(params.cwd, this.environment);
@@ -80849,16 +80947,6 @@ var init_agent2 = __esm({
80849
80947
  }
80850
80948
  async promptSubmission(params, submission) {
80851
80949
  const session = await this.restoreSession(params.sessionId);
80852
- this.conn.startUserMessage();
80853
- for (const content of params.prompt) {
80854
- if (submission.aborted) {
80855
- return { stopReason: "cancelled" };
80856
- }
80857
- await this.conn.sessionUpdate({
80858
- sessionId: params.sessionId,
80859
- update: { sessionUpdate: "user_message_chunk", content }
80860
- });
80861
- }
80862
80950
  if (submission.aborted) {
80863
80951
  return { stopReason: "cancelled" };
80864
80952
  }
@@ -80869,6 +80957,9 @@ var init_agent2 = __esm({
80869
80957
  const cmd = space === -1 ? trimmed.slice(1) : trimmed.slice(1, space);
80870
80958
  const argsString = space === -1 ? "" : trimmed.slice(space + 1);
80871
80959
  const args = parseCommandArgs(argsString);
80960
+ if (builtinAvailableCommands().some((command) => command.name === cmd)) {
80961
+ this.conn.startUserMessage();
80962
+ }
80872
80963
  if (cmd === "compact") {
80873
80964
  const customInstructions = args.join(" ").trim() || void 0;
80874
80965
  const compactionId = randomUUID11();
@@ -81130,7 +81221,7 @@ ${JSON.stringify(stats, null, 2)}`;
81130
81221
  }
81131
81222
  let text2 = "";
81132
81223
  try {
81133
- text2 = readFileSync8(changelogPath, "utf-8");
81224
+ text2 = readFileSync9(changelogPath, "utf-8");
81134
81225
  } catch (e) {
81135
81226
  await this.conn.sessionUpdate({
81136
81227
  sessionId: session.sessionId,
@@ -81174,7 +81265,7 @@ ${JSON.stringify(stats, null, 2)}`;
81174
81265
  return { stopReason: "end_turn" };
81175
81266
  }
81176
81267
  try {
81177
- const raw = readFileSync8(sessionFile, "utf-8");
81268
+ const raw = readFileSync9(sessionFile, "utf-8");
81178
81269
  if (raw.trim().length === 0) {
81179
81270
  await this.conn.sessionUpdate({
81180
81271
  sessionId: session.sessionId,
@@ -81297,7 +81388,7 @@ ${JSON.stringify(stats, null, 2)}`;
81297
81388
  signal.throwIfAborted();
81298
81389
  const parsed = steerRequestSchema.safeParse(params);
81299
81390
  if (!parsed.success) {
81300
- throw RequestError10.invalidParams("Invalid steering request");
81391
+ throw RequestError11.invalidParams("Invalid steering request");
81301
81392
  }
81302
81393
  const session = this.sessions.maybeGet(parsed.data.sessionId);
81303
81394
  if (session === void 0) {
@@ -81309,14 +81400,6 @@ ${JSON.stringify(stats, null, 2)}`;
81309
81400
  case "idle":
81310
81401
  return { outcome: "promptRequired", reason: "noRunningTurn" };
81311
81402
  case "injected": {
81312
- this.conn.startUserMessage();
81313
- for (const content of parsed.data.prompt) {
81314
- signal.throwIfAborted();
81315
- await this.conn.sessionUpdate({
81316
- sessionId: parsed.data.sessionId,
81317
- update: { sessionUpdate: "user_message_chunk", content }
81318
- });
81319
- }
81320
81403
  return { outcome: "injected" };
81321
81404
  }
81322
81405
  }
@@ -81350,11 +81433,11 @@ ${JSON.stringify(stats, null, 2)}`;
81350
81433
  }
81351
81434
  async forkSession(params) {
81352
81435
  if (!isAbsolute12(params.cwd)) {
81353
- throw RequestError10.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
81436
+ throw RequestError11.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
81354
81437
  }
81355
81438
  const source = this.findStoredSession(params.sessionId);
81356
81439
  if (!source) {
81357
- throw RequestError10.invalidParams(`Unknown sessionId: ${params.sessionId}`);
81440
+ throw RequestError11.invalidParams(`Unknown sessionId: ${params.sessionId}`);
81358
81441
  }
81359
81442
  this.lastSessionCwd = params.cwd;
81360
81443
  const fileCommands = loadSlashCommands(params.cwd, this.environment);
@@ -81416,13 +81499,13 @@ ${JSON.stringify(stats, null, 2)}`;
81416
81499
  }
81417
81500
  async resumeSession(params) {
81418
81501
  if (!isAbsolute12(params.cwd)) {
81419
- throw RequestError10.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
81502
+ throw RequestError11.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
81420
81503
  }
81421
81504
  this.sessions.close(params.sessionId);
81422
81505
  this.lastSessionCwd = params.cwd;
81423
81506
  const stored = this.findStoredSession(params.sessionId);
81424
81507
  if (!stored) {
81425
- throw RequestError10.invalidParams(`Unknown sessionId: ${params.sessionId}`);
81508
+ throw RequestError11.invalidParams(`Unknown sessionId: ${params.sessionId}`);
81426
81509
  }
81427
81510
  const session = await this.restoreSession(params.sessionId, {
81428
81511
  cwd: params.cwd,
@@ -81487,13 +81570,13 @@ ${JSON.stringify(stats, null, 2)}`;
81487
81570
  }
81488
81571
  async loadSession(params) {
81489
81572
  if (!isAbsolute12(params.cwd)) {
81490
- throw RequestError10.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
81573
+ throw RequestError11.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
81491
81574
  }
81492
81575
  this.sessions.close(params.sessionId);
81493
81576
  this.lastSessionCwd = params.cwd;
81494
81577
  const stored = this.findStoredSession(params.sessionId);
81495
81578
  if (!stored) {
81496
- throw RequestError10.invalidParams(`Unknown sessionId: ${params.sessionId}`);
81579
+ throw RequestError11.invalidParams(`Unknown sessionId: ${params.sessionId}`);
81497
81580
  }
81498
81581
  const enableSkillCommands = getEnableSkillCommands(params.cwd, this.environment);
81499
81582
  const session = await this.restoreSession(params.sessionId, {
@@ -81516,18 +81599,38 @@ ${JSON.stringify(stats, null, 2)}`;
81516
81599
  cwd: params.cwd,
81517
81600
  sessionFile: stored.sessionFile
81518
81601
  });
81519
- const data = await proc.getMessages();
81520
- const messages = Array.isArray(data?.messages) ? data.messages : [];
81521
- for (const m of messages) {
81602
+ for (const entry of readPiHistory(stored.sessionFile)) {
81603
+ if (entry.type === "compaction") {
81604
+ await this.conn.sessionUpdate({
81605
+ sessionId: session.sessionId,
81606
+ update: {
81607
+ sessionUpdate: "compaction_update",
81608
+ compactionId: entry.id,
81609
+ status: "completed"
81610
+ }
81611
+ });
81612
+ if (entry.summary) {
81613
+ await this.conn.sessionUpdate({
81614
+ sessionId: session.sessionId,
81615
+ update: {
81616
+ sessionUpdate: "compaction_summary_chunk",
81617
+ compactionId: entry.id,
81618
+ content: { type: "text", text: entry.summary }
81619
+ }
81620
+ });
81621
+ }
81622
+ continue;
81623
+ }
81624
+ const m = entry.message;
81522
81625
  const role = String(m?.role ?? "");
81523
81626
  if (role === "user") {
81524
- const text2 = normalizePiMessageText(m?.content);
81525
- if (text2) {
81627
+ this.conn.startUserMessage();
81628
+ for (const content of piUserMessageContent(m.content)) {
81526
81629
  await this.conn.sessionUpdate({
81527
81630
  sessionId: session.sessionId,
81528
81631
  update: {
81529
81632
  sessionUpdate: "user_message_chunk",
81530
- content: { type: "text", text: text2 }
81633
+ content
81531
81634
  }
81532
81635
  });
81533
81636
  }
@@ -81668,7 +81771,7 @@ ${JSON.stringify(stats, null, 2)}`;
81668
81771
  const session = await this.restoreSession(params.sessionId);
81669
81772
  const mode = params.modeId;
81670
81773
  if (typeof mode !== "string" || mode.length === 0) {
81671
- throw RequestError10.invalidParams("Expected nonempty string modeId");
81774
+ throw RequestError11.invalidParams("Expected nonempty string modeId");
81672
81775
  }
81673
81776
  await session.proc.setThinkingLevel(mode);
81674
81777
  await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
@@ -81679,18 +81782,18 @@ ${JSON.stringify(stats, null, 2)}`;
81679
81782
  const configId = String(params.configId);
81680
81783
  let modelChanged = false;
81681
81784
  if (typeof params.value !== "string") {
81682
- throw RequestError10.invalidParams(`Expected string value for config option: ${configId}`);
81785
+ throw RequestError11.invalidParams(`Expected string value for config option: ${configId}`);
81683
81786
  }
81684
81787
  if (configId === MODEL_CONFIG_ID2) {
81685
81788
  await setSessionModel(session.proc, params.value);
81686
81789
  modelChanged = true;
81687
81790
  } else if (configId === THOUGHT_LEVEL_CONFIG_ID) {
81688
81791
  if (params.value.length === 0) {
81689
- throw RequestError10.invalidParams("Expected nonempty thinking level");
81792
+ throw RequestError11.invalidParams("Expected nonempty thinking level");
81690
81793
  }
81691
81794
  await session.proc.setThinkingLevel(params.value);
81692
81795
  } else {
81693
- throw RequestError10.invalidParams(`Unknown config option: ${configId}`);
81796
+ throw RequestError11.invalidParams(`Unknown config option: ${configId}`);
81694
81797
  }
81695
81798
  const configOptions = await emitConfigOptionsUpdate(this.conn, session.sessionId, session.proc);
81696
81799
  if (modelChanged) {
@@ -83213,63 +83316,64 @@ var init_session_events = __esm({
83213
83316
  const externalized = await externalizer.update(options.sessionId(), notification.update, signal);
83214
83317
  signal.throwIfAborted();
83215
83318
  const update = externalized.agent;
83216
- const eventUpdate = externalized.event;
83217
- const backgroundTask = update._meta?.["mobius.backgroundTask"];
83218
- const payload = decodeAcpSessionUpdate(
83219
- JSON.stringify(
83220
- normalizeEvent(
83221
- (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") && backgroundTask !== void 0 && typeof eventUpdate === "object" && eventUpdate !== null ? { ...eventUpdate, backgroundTask } : eventUpdate
83319
+ for (const eventUpdate of externalized.events) {
83320
+ const backgroundTask = update._meta?.["mobius.backgroundTask"];
83321
+ const payload = decodeAcpSessionUpdate(
83322
+ JSON.stringify(
83323
+ normalizeEvent(
83324
+ (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") && backgroundTask !== void 0 && typeof eventUpdate === "object" && eventUpdate !== null ? { ...eventUpdate, backgroundTask } : eventUpdate
83325
+ )
83222
83326
  )
83223
- )
83224
- );
83225
- let event;
83226
- switch (update.sessionUpdate) {
83227
- case "available_commands_update":
83228
- event = {
83229
- kind: "commands",
83230
- payload,
83231
- commands: update.availableCommands.slice(0, 500).map((command) => ({
83232
- description: truncate(command.description, 500),
83233
- inputHint: truncate(command.input?.hint ?? "", 255),
83234
- name: truncate(command.name, 128)
83235
- })).filter((command) => command.name.length > 0)
83236
- };
83237
- break;
83238
- case "config_option_update":
83239
- event = {
83240
- kind: "configuration",
83241
- payload,
83242
- configOptions: protocol.normalizeUpdate(options.configOptions(), update.configOptions)
83243
- };
83244
- break;
83245
- case "current_mode_update":
83246
- event = { kind: "mode", payload, modeId: update.currentModeId };
83247
- break;
83248
- case "session_info_update": {
83249
- const changes = [];
83250
- if (Object.hasOwn(update, "title")) {
83251
- changes.push({ kind: "title", value: truncate(update.title ?? "", 500) });
83252
- }
83253
- if (Object.hasOwn(update, "updatedAt")) {
83254
- changes.push({ kind: "updated-at", value: truncate(update.updatedAt ?? "", 128) });
83327
+ );
83328
+ let event;
83329
+ switch (update.sessionUpdate) {
83330
+ case "available_commands_update":
83331
+ event = {
83332
+ kind: "commands",
83333
+ payload,
83334
+ commands: update.availableCommands.slice(0, 500).map((command) => ({
83335
+ description: truncate(command.description, 500),
83336
+ inputHint: truncate(command.input?.hint ?? "", 255),
83337
+ name: truncate(command.name, 128)
83338
+ })).filter((command) => command.name.length > 0)
83339
+ };
83340
+ break;
83341
+ case "config_option_update":
83342
+ event = {
83343
+ kind: "configuration",
83344
+ payload,
83345
+ configOptions: protocol.normalizeUpdate(options.configOptions(), update.configOptions)
83346
+ };
83347
+ break;
83348
+ case "current_mode_update":
83349
+ event = { kind: "mode", payload, modeId: update.currentModeId };
83350
+ break;
83351
+ case "session_info_update": {
83352
+ const changes = [];
83353
+ if (Object.hasOwn(update, "title")) {
83354
+ changes.push({ kind: "title", value: truncate(update.title ?? "", 500) });
83355
+ }
83356
+ if (Object.hasOwn(update, "updatedAt")) {
83357
+ changes.push({ kind: "updated-at", value: truncate(update.updatedAt ?? "", 128) });
83358
+ }
83359
+ event = { kind: "info", payload, changes };
83360
+ break;
83255
83361
  }
83256
- event = { kind: "info", payload, changes };
83257
- break;
83362
+ case "user_message_chunk":
83363
+ case "agent_message_chunk":
83364
+ case "agent_thought_chunk":
83365
+ case "tool_call":
83366
+ case "tool_call_update":
83367
+ case "plan":
83368
+ case "plan_update":
83369
+ case "plan_removed":
83370
+ event = { kind: "content", payload };
83371
+ break;
83372
+ default:
83373
+ event = { kind: "other", payload };
83258
83374
  }
83259
- case "user_message_chunk":
83260
- case "agent_message_chunk":
83261
- case "agent_thought_chunk":
83262
- case "tool_call":
83263
- case "tool_call_update":
83264
- case "plan":
83265
- case "plan_update":
83266
- case "plan_removed":
83267
- event = { kind: "content", payload };
83268
- break;
83269
- default:
83270
- event = { kind: "other", payload };
83375
+ await options.onEvent(event);
83271
83376
  }
83272
- await options.onEvent(event);
83273
83377
  });
83274
83378
  this.events = this.#updates;
83275
83379
  const client2 = {
@@ -90757,10 +90861,12 @@ var init_adapter2 = __esm({
90757
90861
  request.nativeSessionId,
90758
90862
  async (update) => {
90759
90863
  const externalized = await this.#contentExternalizer.update(sessionId, update, signal);
90760
- events.push({
90761
- type: "acp",
90762
- payload: decodeAcpSessionUpdate(JSON.stringify(fitCodexToolEvent(externalized.event)))
90763
- });
90864
+ for (const event of externalized.events) {
90865
+ events.push({
90866
+ type: "acp",
90867
+ payload: decodeAcpSessionUpdate(JSON.stringify(fitCodexToolEvent(event)))
90868
+ });
90869
+ }
90764
90870
  },
90765
90871
  signal
90766
90872
  );
@@ -90910,8 +91016,18 @@ var init_content_externalizer = __esm({
90910
91016
  ]);
90911
91017
  AcpContentExternalizer = class {
90912
91018
  #store;
90913
- constructor(store) {
91019
+ #linkedResources;
91020
+ constructor(store, linkedResources = () => []) {
90914
91021
  this.#store = store;
91022
+ this.#linkedResources = linkedResources;
91023
+ }
91024
+ #resourceLinks(text2) {
91025
+ return this.#linkedResources(text2).map((resource) => ({
91026
+ type: "resource_link",
91027
+ uri: resource.uri,
91028
+ name: resource.name,
91029
+ mimeType: resource.mediaType
91030
+ }));
90915
91031
  }
90916
91032
  async #content(sessionId, content, signal) {
90917
91033
  switch (content.type) {
@@ -91063,7 +91179,7 @@ var init_content_externalizer = __esm({
91063
91179
  const hasContent = update.content !== void 0 && update.content !== null || agentContent.length > 0;
91064
91180
  return {
91065
91181
  agent: { ...update, ...meta3, ...output2, ...hasContent ? { content: agentContent } : {} },
91066
- event: { ...update, ...meta3, ...output2, ...hasContent ? { content: eventContent } : {} }
91182
+ events: [{ ...update, ...meta3, ...output2, ...hasContent ? { content: eventContent } : {} }]
91067
91183
  };
91068
91184
  }
91069
91185
  update = async (sessionId, update, signal) => {
@@ -91075,7 +91191,12 @@ var init_content_externalizer = __esm({
91075
91191
  const content = await this.#content(sessionId, update.content, signal);
91076
91192
  return {
91077
91193
  agent: { ...update, content: content.agent },
91078
- event: { ...update, content: content.event }
91194
+ events: [
91195
+ { ...update, content: content.event },
91196
+ // Keep native text intact. Only add cards for exact file URLs whose
91197
+ // original upload metadata is still known; arbitrary context stays text.
91198
+ ...update.sessionUpdate === "user_message_chunk" && update.content.type === "text" ? this.#resourceLinks(update.content.text).map((content2) => ({ ...update, content: content2 })) : []
91199
+ ]
91079
91200
  };
91080
91201
  }
91081
91202
  case "tool_call":
@@ -91083,14 +91204,14 @@ var init_content_externalizer = __esm({
91083
91204
  return await this.#toolUpdate(sessionId, update, signal);
91084
91205
  case "compaction_update": {
91085
91206
  if (update.summary === void 0 || update.summary === null) {
91086
- return { agent: update, event: update };
91207
+ return { agent: update, events: [update] };
91087
91208
  }
91088
91209
  const content = await Promise.all(
91089
91210
  update.summary.map(async (item) => await this.#content(sessionId, item, signal))
91090
91211
  );
91091
91212
  return {
91092
91213
  agent: { ...update, summary: content.map((item) => item.agent) },
91093
- event: { ...update, summary: content.map((item) => item.event) }
91214
+ events: [{ ...update, summary: content.map((item) => item.event) }]
91094
91215
  };
91095
91216
  }
91096
91217
  case "plan":
@@ -91101,7 +91222,7 @@ var init_content_externalizer = __esm({
91101
91222
  case "config_option_update":
91102
91223
  case "session_info_update":
91103
91224
  case "usage_update":
91104
- return { agent: update, event: update };
91225
+ return { agent: update, events: [update] };
91105
91226
  }
91106
91227
  };
91107
91228
  };
@@ -91109,10 +91230,10 @@ var init_content_externalizer = __esm({
91109
91230
  });
91110
91231
 
91111
91232
  // src/engine/agent/runtime-agents.ts
91112
- function createRuntimeAdapterRegistry(adapters, processes, externalize) {
91233
+ function createRuntimeAdapterRegistry(adapters, processes, externalize, linkedResources) {
91113
91234
  return new AdapterRegistry(adapters, {
91114
91235
  processes,
91115
- contentExternalizer: new AcpContentExternalizer(externalize)
91236
+ contentExternalizer: new AcpContentExternalizer(externalize, linkedResources)
91116
91237
  });
91117
91238
  }
91118
91239
  var RuntimeAgentDependencies;
@@ -91190,7 +91311,7 @@ var init_environment_proxy = __esm({
91190
91311
  });
91191
91312
 
91192
91313
  // src/engine/storage/logs/bounded-log.ts
91193
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, statSync as statSync5, writeFileSync as writeFileSync2 } from "node:fs";
91314
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync as renameSync2, statSync as statSync5, writeFileSync as writeFileSync2 } from "node:fs";
91194
91315
  import { join as join26 } from "node:path";
91195
91316
  var BoundedLineLog;
91196
91317
  var init_bounded_log = __esm({
@@ -91235,7 +91356,7 @@ var init_bounded_log = __esm({
91235
91356
  appendFileSync2(this.#path, encoded, { encoding: "utf8", mode: 384 });
91236
91357
  return;
91237
91358
  }
91238
- const lines = `${existingBytes === 0 ? "" : readFileSync9(this.#path, "utf8")}${encoded}`.split("\n").filter((candidate) => candidate.length > 0);
91359
+ const lines = `${existingBytes === 0 ? "" : readFileSync10(this.#path, "utf8")}${encoded}`.split("\n").filter((candidate) => candidate.length > 0);
91239
91360
  let totalBytes = lines.reduce((total, candidate) => total + Buffer.byteLength(candidate) + 1, 0);
91240
91361
  while (totalBytes > this.#maximumBytes && lines.length > 0) {
91241
91362
  const oldest = lines.shift();
@@ -91256,7 +91377,7 @@ var init_bounded_log = __esm({
91256
91377
  });
91257
91378
 
91258
91379
  // src/engine/storage/logs/log-files.ts
91259
- import { readFileSync as readFileSync10 } from "node:fs";
91380
+ import { readFileSync as readFileSync11 } from "node:fs";
91260
91381
  import { join as join27 } from "node:path";
91261
91382
  function unhandledRejectionLogFilename(role) {
91262
91383
  return `unhandled-rejections-${role}.log`;
@@ -91273,7 +91394,7 @@ function processLogPaths(dataDirectory) {
91273
91394
  }
91274
91395
  function readLogText(path8) {
91275
91396
  try {
91276
- return readFileSync10(path8, "utf8");
91397
+ return readFileSync11(path8, "utf8");
91277
91398
  } catch {
91278
91399
  return null;
91279
91400
  }
@@ -93205,7 +93326,7 @@ var init_session_configurations = __esm({
93205
93326
  restore = async (stored, options, apply, signal) => {
93206
93327
  this.#selected = stored;
93207
93328
  for (const selection of this.#selected) {
93208
- const current = options.find(
93329
+ const current = options().find(
93209
93330
  (option) => option.id === selection.configId && option.type === selection.type
93210
93331
  );
93211
93332
  if (current === void 0) {
@@ -93414,6 +93535,7 @@ async function restoreSession(session, signal, recovery, origin) {
93414
93535
  session.store.remove(session.sessionId);
93415
93536
  stored = session.metrics.measureSync("session-state-read", () => session.store.read(session.sessionId));
93416
93537
  }
93538
+ const forkSource = origin?.kind === "fork" ? session.store.findNative(session.agentId, origin.nativeSessionId)[0] : void 0;
93417
93539
  let lifecycleResponse2;
93418
93540
  if (origin?.kind === "fork") {
93419
93541
  if (!session.capabilities.unstableForkSession) {
@@ -93575,7 +93697,9 @@ async function restoreSession(session, signal, recovery, origin) {
93575
93697
  }
93576
93698
  session.restrictSessionCapabilities(lifecycleResponse2);
93577
93699
  session.setConfigOptions(session.sessionConfigProtocol.normalizeLifecycle(lifecycleResponse2));
93578
- if (stored instanceof FoundStoredSession && session.restored) {
93700
+ if (forkSource !== void 0) {
93701
+ await session.restoreStoredConfigurations(forkSource, signal, "fork");
93702
+ } else if (stored instanceof FoundStoredSession && session.restored) {
93579
93703
  await session.restoreStoredConfigurations(stored.session, signal);
93580
93704
  }
93581
93705
  await session.drained();
@@ -93819,13 +93943,31 @@ var init_agent_session = __esm({
93819
93943
  this.writeStoredSession((/* @__PURE__ */ new Date()).toISOString());
93820
93944
  }
93821
93945
  };
93822
- restoreStoredConfigurations = async (session, signal) => {
93946
+ restoreStoredConfigurations = async (session, signal, intent = "restore") => {
93947
+ const configurations = intent === "fork" ? session.configurations.toSorted(
93948
+ (left, right) => Number(right.configId === "model") - Number(left.configId === "model")
93949
+ ) : session.configurations;
93823
93950
  await this.#sessionConfigurations.restore(
93824
- session.configurations,
93825
- this.#configOptions,
93951
+ configurations,
93952
+ () => this.#configOptions,
93826
93953
  this.#applyConfiguration,
93827
93954
  signal
93828
93955
  );
93956
+ if (intent === "fork") {
93957
+ for (const selection of session.configurations) {
93958
+ if (selection.configId !== "mode") {
93959
+ continue;
93960
+ }
93961
+ const current = this.#configOptions.find((option) => option.id === selection.configId);
93962
+ if (current?.type !== selection.type || (selection.type === "select" ? current.currentValue !== selection.value : current.booleanValue !== selection.booleanValue)) {
93963
+ throw new AcpExecutionError(
93964
+ "The Agent could not preserve the source Session mode for this Fork",
93965
+ {},
93966
+ "invalid_session_configuration"
93967
+ );
93968
+ }
93969
+ }
93970
+ }
93829
93971
  };
93830
93972
  restrictSessionCapabilities = (response) => {
93831
93973
  this.#capabilities = restrictAcpSessionCapabilities(this.#capabilities, response);
@@ -95311,7 +95453,8 @@ var init_native_session_browser = __esm({
95311
95453
  requestId: command.requestId,
95312
95454
  sessionId: command.sessionId,
95313
95455
  targetSessionId: command.targetSessionId,
95314
- ...await this.#activeTurn(command, options)
95456
+ ...await this.#activeTurn(command, options),
95457
+ queuedCancellation: "before-start"
95315
95458
  };
95316
95459
  }
95317
95460
  const capabilities = await options.adapter.getCapabilities(options);
@@ -95792,7 +95935,12 @@ var init_prompt_lifecycle = __esm({
95792
95935
  }
95793
95936
  const recorded = await this.#recordLifecycle(sessionId, {
95794
95937
  type: "lifecycle-metadata",
95795
- payload: { kind: "prompt.completed", commandId, stopReason }
95938
+ payload: {
95939
+ kind: "prompt.completed",
95940
+ commandId,
95941
+ stopReason,
95942
+ ...request.phase === "queued" ? { execution: "not-started" } : {}
95943
+ }
95796
95944
  });
95797
95945
  if (!recorded) {
95798
95946
  this.#sink.publishOperationFailure(
@@ -95945,15 +96093,27 @@ var init_execution_receipts = __esm({
95945
96093
  return void 0;
95946
96094
  }
95947
96095
  };
96096
+ /** Started identities remain authoritative after their detailed receipt is evicted. */
96097
+ hasStarted = (commandId, sessionId) => {
96098
+ const receipt = this.#executions.get(commandId);
96099
+ return receipt?.sessionId === sessionId && receipt.started || this.#table.identity(commandId)?.sessionId === sessionId;
96100
+ };
95948
96101
  /** Reserves an unseen command for cancellation before a delayed prompt can claim its identity. */
95949
96102
  recordCancellationBeforeAdmission = (commandId, sessionId) => {
95950
96103
  const existing = this.#executions.get(commandId);
95951
96104
  if (existing !== void 0) {
95952
96105
  return existing.sessionId === sessionId ? this.outcome(commandId) : void 0;
95953
96106
  }
96107
+ const identity2 = this.#table.identity(commandId);
96108
+ if (identity2 !== void 0) {
96109
+ return identity2.sessionId === sessionId ? {
96110
+ type: "lifecycle-metadata",
96111
+ payload: { kind: "prompt.completed", commandId, stopReason: "cancelled" }
96112
+ } : void 0;
96113
+ }
95954
96114
  const outcome = {
95955
96115
  type: "lifecycle-metadata",
95956
- payload: { kind: "prompt.completed", commandId, stopReason: "cancelled" }
96116
+ payload: { kind: "prompt.completed", commandId, stopReason: "cancelled", execution: "not-started" }
95957
96117
  };
95958
96118
  const commandHash = createHash7("sha256").update(
95959
96119
  encodeSessionCancelBeforeStartCommand({
@@ -96890,6 +97050,7 @@ var init_scratch_directory = __esm({
96890
97050
  // src/engine/session/content/external-content-store.ts
96891
97051
  import { createHash as createHash8 } from "node:crypto";
96892
97052
  import { extname as extname2, join as join31 } from "node:path";
97053
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
96893
97054
  function contentFileName(key, name2) {
96894
97055
  const extension = extname2(name2).toLowerCase();
96895
97056
  return /^\.[a-z0-9]{1,16}$/u.test(extension) ? `${key}${extension}` : key;
@@ -96899,6 +97060,7 @@ var init_external_content_store = __esm({
96899
97060
  "src/engine/session/content/external-content-store.ts"() {
96900
97061
  "use strict";
96901
97062
  init_zod();
97063
+ init_operation_error();
96902
97064
  init_scratch_directory();
96903
97065
  contentRequest = /^mobius-content:(image|audio|resource):([a-f0-9]{64})$/u;
96904
97066
  CONTENT_LIFETIME_MILLISECONDS = 24 * 60 * 60 * 1e3;
@@ -96962,8 +97124,12 @@ var init_external_content_store = __esm({
96962
97124
  }
96963
97125
  await this.#expire();
96964
97126
  active.throwIfAborted();
96965
- const path8 = await this.#scratch.write(kind, contentFileName(key, metadata.name), bytes, active);
97127
+ const writtenPath = await this.#scratch.write(kind, contentFileName(key, metadata.name), bytes, active);
97128
+ const path8 = await this.#scratch.resolveFile(writtenPath);
96966
97129
  active.throwIfAborted();
97130
+ if (path8 === null) {
97131
+ throw new OperationError("content_unavailable", "The cached content file is unavailable");
97132
+ }
96967
97133
  const retained = this.#entries.get(request);
96968
97134
  this.#entries.set(request, {
96969
97135
  byteLength: bytes.byteLength,
@@ -97004,6 +97170,30 @@ var init_external_content_store = __esm({
97004
97170
  const bytes = await this.#scratch.read(resolved.path, active);
97005
97171
  return bytes === null ? null : { ...resolved, bytes };
97006
97172
  });
97173
+ /** Metadata for exact, still-cached file URLs. This does not grant content access. */
97174
+ linkedResources = (text2) => {
97175
+ if (!text2.includes("file:///")) {
97176
+ return [];
97177
+ }
97178
+ const resources = [];
97179
+ for (const [request, entry] of this.#entries) {
97180
+ if (!request.startsWith("mobius-content:resource:") || entry.expiresAt <= Date.now()) {
97181
+ continue;
97182
+ }
97183
+ const uri = pathToFileURL3(entry.path).href;
97184
+ for (let index2 = text2.indexOf(uri); index2 >= 0; index2 = text2.indexOf(uri, index2 + uri.length)) {
97185
+ const following = text2[index2 + uri.length];
97186
+ if (following === void 0 || /[\s<>"')\]]/u.test(following)) {
97187
+ resources.push({
97188
+ index: index2,
97189
+ resource: { uri, mediaType: entry.mediaType, name: entry.name, path: entry.path }
97190
+ });
97191
+ break;
97192
+ }
97193
+ }
97194
+ }
97195
+ return resources.sort((left, right) => left.index - right.index).map(({ resource }) => resource);
97196
+ };
97007
97197
  [Symbol.asyncDispose] = async () => {
97008
97198
  clearInterval(this.#expirationTimer);
97009
97199
  this.#lifetime.abort();
@@ -98730,10 +98920,19 @@ var init_interactions2 = __esm({
98730
98920
  if (message.kind !== SESSION_CANCEL_BEFORE_START_KIND) {
98731
98921
  throw new Error("Command kind mismatch");
98732
98922
  }
98923
+ if (operations.hasStarted(message.commandId, message.sessionId)) {
98924
+ reportOperationFailure(
98925
+ socket,
98926
+ message.commandId,
98927
+ message.sessionId,
98928
+ new SessionError("turn_already_started", "The message already started and could not be withdrawn")
98929
+ );
98930
+ return;
98931
+ }
98733
98932
  const turn = sessionTurns.get(message.commandId, message.sessionId);
98734
98933
  if (turn === void 0) {
98735
98934
  const outcome = operations.recordCancellationBeforeAdmission(message.commandId, message.sessionId);
98736
- if (outcome !== void 0) {
98935
+ if (outcome?.type === "lifecycle-metadata" && outcome.payload.kind === "prompt.completed" && outcome.payload.stopReason === "cancelled") {
98737
98936
  void eventProjection.replayOutcome(message.sessionId, outcome).catch((error62) => {
98738
98937
  reportOperationFailure(socket, message.commandId, message.sessionId, error62);
98739
98938
  });
@@ -99230,6 +99429,7 @@ async function executePrompt({
99230
99429
  );
99231
99430
  prepared = await sessions.prepareExecutionSession(preparation);
99232
99431
  }
99432
+ signal.throwIfAborted();
99233
99433
  operations.markExecutionStarted(command.commandId);
99234
99434
  if (availability.disposition === SessionPromptDisposition.Steer) {
99235
99435
  if (!recordSteeringPrompt()) {
@@ -100692,7 +100892,8 @@ async function runRuntime(config2, parentSignal, timer = systemTimer, options =
100692
100892
  const adapters = createRuntimeAdapterRegistry(
100693
100893
  config2.adapters,
100694
100894
  processes,
100695
- (sessionId, content, contentSignal) => externalContent.put({ ...content, sessionId, signal: contentSignal })
100895
+ (sessionId, content, contentSignal) => externalContent.put({ ...content, sessionId, signal: contentSignal }),
100896
+ externalContent.linkedResources
100696
100897
  );
100697
100898
  const resolveAgent = async (agentId, resolveSignal, sessionId) => {
100698
100899
  const catalogAgent = await resolveCatalogAgent(config2, agentId, resolveSignal);