maka-agent 0.2.0-dev.53.20260927 → 0.2.0-dev.62.20260928

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 (55) hide show
  1. package/LICENSE +6 -5
  2. package/dist/workspace-root.js +2 -2
  3. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  4. package/node_modules/@maka/acp-executor-plugin/dist/index.js +333 -28
  5. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +283 -27
  6. package/node_modules/@maka/core/dist/executor-catalog.js +10 -1
  7. package/node_modules/@maka/core/dist/message-queue-order.js +47 -0
  8. package/node_modules/@maka/core/dist/model-metadata.js +2 -1
  9. package/node_modules/@maka/core/dist/redaction.js +42 -3
  10. package/node_modules/@maka/core/dist/relative-time-policy.js +71 -0
  11. package/node_modules/@maka/core/dist/relative-time.js +3 -54
  12. package/node_modules/@maka/core/package.json +1 -0
  13. package/node_modules/@maka/eval/dist/egress-audit.js +127 -0
  14. package/node_modules/@maka/eval/dist/harness-executor.js +16 -96
  15. package/node_modules/@maka/eval/harbor/egress-proxy/entrypoint.sh +4 -4
  16. package/node_modules/@maka/eval/harbor/egress_filter.py +153 -104
  17. package/node_modules/@maka/eval/harbor/eval_framework.py +37 -14
  18. package/node_modules/@maka/eval/harbor/relay_agent.py +4 -6
  19. package/node_modules/@maka/eval/harbor/run_trial.py +11 -4
  20. package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +33 -6
  21. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +1 -1
  22. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +26 -5
  23. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +36 -0
  24. package/node_modules/@maka/runtime/dist/provider-error-classification.js +8 -73
  25. package/node_modules/@maka/runtime/dist/provider-error-signals.js +31 -0
  26. package/node_modules/@maka/runtime/dist/provider-retry-policy.js +79 -0
  27. package/node_modules/@maka/runtime/dist/session-manager.js +6 -4
  28. package/node_modules/@maka/runtime/dist/session-projection-helpers.js +2 -0
  29. package/node_modules/@maka/runtime/dist/shell-run-manager.js +8 -8
  30. package/node_modules/@maka/runtime/dist/terminal-run-commit.js +7 -1
  31. package/node_modules/@maka/runtime/dist/tool-runtime.js +5 -4
  32. package/node_modules/@maka/runtime-host/dist/adapter/provider-retry-projector.js +63 -0
  33. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +7 -51
  34. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -3
  35. package/node_modules/@maka/runtime-host/dist/protocol/message-queue-limits.js +21 -0
  36. package/node_modules/@maka/runtime-host/dist/protocol/message.js +6 -104
  37. package/node_modules/@maka/runtime-host/dist/protocol/queue-mutation.js +136 -0
  38. package/node_modules/@maka/runtime-host/dist/protocol/turn-provider-retry.js +69 -0
  39. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +2 -43
  40. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +42 -5
  41. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +105 -233
  42. package/node_modules/@maka/runtime-host/dist/server/message-queue-state.js +69 -0
  43. package/node_modules/@maka/runtime-host/dist/server/provider-retry-projection.js +71 -0
  44. package/node_modules/@maka/runtime-host/dist/server/queued-mutation-executor.js +94 -0
  45. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +1 -1
  46. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +9 -58
  47. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +0 -3
  48. package/node_modules/@maka/storage/dist/agent-run-store.js +15 -6
  49. package/node_modules/@maka/storage/dist/execution-persistence-provider.js +1 -0
  50. package/node_modules/@maka/storage/dist/execution-stores.js +1 -0
  51. package/node_modules/@maka/storage/dist/project-catalog.js +41 -42
  52. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +102 -31
  53. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +52 -1
  54. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +34 -2
  55. package/package.json +1 -1
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/index.ts
8
8
  import { createHash } from "node:crypto";
9
- import { constants as constants2 } from "node:fs";
9
+ import { constants as constants2, createReadStream } from "node:fs";
10
10
  import { access, mkdtemp, rm, realpath as realpath2, stat as stat2 } from "node:fs/promises";
11
11
  import { tmpdir } from "node:os";
12
12
  import { dirname as dirname2, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
@@ -23915,17 +23915,52 @@ var AcpExecutor = class {
23915
23915
  async inspectConversation(input2) {
23916
23916
  const session = this.#sessions.get(input2.conversationKey);
23917
23917
  if (this.#disposed) return this.#catalogEntry("unavailable");
23918
- if (session?.lost || !session && await this.#state?.has(input2.conversationKey, input2.cwd))
23919
- return this.#catalogEntry("history_only");
23920
- return this.#catalogEntry("ready", session?.configOptions ?? [], input2.configuration?.model);
23918
+ if (session && session.cwd !== resolve2(input2.cwd))
23919
+ return this.#catalogEntry("history_only", [], input2.configuration?.model);
23920
+ if (session?.restoring)
23921
+ return this.#catalogEntry("restoring", session.configOptions, input2.configuration?.model);
23922
+ if (session?.restoreFailed)
23923
+ return this.#catalogEntry(
23924
+ "restore_failed",
23925
+ session.configOptions,
23926
+ input2.configuration?.model
23927
+ );
23928
+ if (session?.historyGap)
23929
+ return this.#catalogEntry("history_gap", session.configOptions, input2.configuration?.model);
23930
+ if (session && !session.lost)
23931
+ return this.#catalogEntry("ready", session.configOptions, input2.configuration?.model);
23932
+ if (session?.lost && !session.record)
23933
+ return this.#catalogEntry("history_only", session.configOptions, input2.configuration?.model);
23934
+ const stored = this.#state?.read ? decodeContinuity(await this.#state.read(input2.conversationKey)) : await this.#state?.has(input2.conversationKey, input2.cwd) ? "legacy" : void 0;
23935
+ if (!stored) return this.#catalogEntry("ready", [], input2.configuration?.model);
23936
+ if (stored === "invalid" || stored === "legacy" || stored.cwd !== resolve2(input2.cwd))
23937
+ return this.#catalogEntry("history_only", [], input2.configuration?.model);
23938
+ if (stored.phase === "reserved")
23939
+ return this.#catalogEntry("history_only", [], input2.configuration?.model);
23940
+ return this.#catalogEntry(
23941
+ stored.phase === "prompt_pending" || stored.phase === "history_gap" ? "history_gap" : "restorable",
23942
+ [],
23943
+ input2.configuration?.model ?? stored.confirmedModel
23944
+ );
23921
23945
  }
23922
23946
  async configureConversation(input2, signal) {
23923
- const session = this.#sessions.get(input2.conversationKey);
23924
- if (!session?.connection || session.lost || session.active || session.configuring || !input2.configuration?.model)
23925
- throw new Error("ACP model change is unavailable");
23947
+ if (!input2.configuration?.model) throw new Error("ACP model change is unavailable");
23948
+ const session = await this.#session(input2);
23949
+ if (session.active || session.configuring) throw new Error("ACP model change is unavailable");
23926
23950
  session.configuring = true;
23951
+ const wasConnected = !!session.connection;
23927
23952
  try {
23953
+ await this.#ensureInitialized(session, signal);
23928
23954
  await this.#applyInitialConfig(session, { model: input2.configuration.model }, signal, true);
23955
+ } catch (error62) {
23956
+ if (!wasConnected || session.lost) {
23957
+ await this.#lose(session);
23958
+ if (session.record?.sessionId && !session.historyGap) session.restoreFailed = true;
23959
+ const retryable = !session.acpSessionId && !session.continuityReserved && !session.record;
23960
+ if (retryable && this.#sessions.get(session.conversationKey) === session)
23961
+ this.#sessions.delete(session.conversationKey);
23962
+ }
23963
+ throw error62;
23929
23964
  } finally {
23930
23965
  session.configuring = false;
23931
23966
  }
@@ -23934,12 +23969,15 @@ var AcpExecutor = class {
23934
23969
  const model = options.find(
23935
23970
  (option) => option.type === "select" && (option.category === "model" || option.id === "model")
23936
23971
  );
23937
- const models = model?.type === "select" ? model.options.flatMap((entry) => "options" in entry ? entry.options : [entry]).map((entry) => ({ id: entry.value, name: entry.name })) : [];
23972
+ const models = model?.type === "select" ? model.options.flatMap((entry) => "options" in entry ? entry.options : [entry]).map((entry) => ({ id: entry.value, name: entry.name })) : this.#catalog?.models ?? [];
23938
23973
  return {
23939
23974
  id: this.id,
23940
23975
  displayName: this.displayName,
23941
23976
  readiness,
23942
- ...this.#adapter.describeModels?.(models) ?? { models },
23977
+ ...model?.type === "select" ? this.#adapter.describeModels?.(models) ?? { models } : {
23978
+ models,
23979
+ ...this.#catalog?.modelGroups ? { modelGroups: this.#catalog.modelGroups } : {}
23980
+ },
23943
23981
  ...model?.type === "select" ? { currentModel: model.currentValue } : selected ? { currentModel: selected } : {},
23944
23982
  supportsAttachments: false,
23945
23983
  supportsModelChange: model?.type === "select"
@@ -23959,11 +23997,11 @@ var AcpExecutor = class {
23959
23997
  if (model) request = { ...request, configuration: { model } };
23960
23998
  let session;
23961
23999
  try {
23962
- session = this.#session(request);
24000
+ session = await this.#session(request);
23963
24001
  } catch (error62) {
23964
24002
  return failure2(safeErrorMessage(error62), errorCode(error62));
23965
24003
  }
23966
- if (session.active || session.configuring)
24004
+ if (session.active || session.configuring || session.awaitingAck)
23967
24005
  return failure2(`${this.displayName} ACP Session is busy`, "acp_busy", true);
23968
24006
  const active = { context, tools: /* @__PURE__ */ new Map(), text: "" };
23969
24007
  session.active = active;
@@ -23976,12 +24014,14 @@ var AcpExecutor = class {
23976
24014
  { model: request.configuration.model },
23977
24015
  context.signal
23978
24016
  );
24017
+ await this.#beginPrompt(session, request.turnId);
23979
24018
  const prompt = session.connection.agent.request(methods.agent.session.prompt, {
23980
24019
  sessionId: session.acpSessionId,
23981
24020
  prompt: [{ type: "text", text: promptText(request) }]
23982
24021
  });
23983
24022
  const response = await this.#awaitPrompt(session, prompt, context.signal);
23984
24023
  if (!response) return { status: "cancelled", reason: "timeout" };
24024
+ if (session.record) session.awaitingAck = request.turnId;
23985
24025
  if (context.signal.aborted || response.stopReason === "cancelled") {
23986
24026
  return { status: "cancelled", providerStopReason: response.stopReason };
23987
24027
  }
@@ -23996,8 +24036,10 @@ var AcpExecutor = class {
23996
24036
  }
23997
24037
  return { status: "completed", text: active.text };
23998
24038
  } catch (error62) {
24039
+ if (errorCode(error62) === "acp_history_gap") session.historyGap = true;
23999
24040
  await this.#lose(session);
24000
- const retryable = !session.acpSessionId && !session.continuityReserved && errorCode(error62) !== "acp_history_only";
24041
+ if (session.record?.sessionId && !session.historyGap) session.restoreFailed = true;
24042
+ const retryable = !session.acpSessionId && !session.continuityReserved && errorCode(error62) !== "acp_history_only" && errorCode(error62) !== "acp_history_gap";
24001
24043
  if (retryable && this.#sessions.get(session.conversationKey) === session)
24002
24044
  this.#sessions.delete(session.conversationKey);
24003
24045
  if (context.signal.aborted) {
@@ -24027,18 +24069,23 @@ var AcpExecutor = class {
24027
24069
  );
24028
24070
  if (failures.length) throw new AggregateError(failures, "ACP process cleanup failed");
24029
24071
  }
24030
- #session(request) {
24072
+ async #session(request) {
24031
24073
  const cwd = resolve2(request.cwd);
24032
24074
  const existing = this.#sessions.get(request.conversationKey);
24033
24075
  if (existing) {
24034
24076
  if (existing.cwd !== cwd)
24035
24077
  throw new AcpRuntimeError("ACP conversation cannot change workspace", "acp_cwd_changed");
24036
- if (existing.lost)
24078
+ if (!existing.lost) return existing;
24079
+ if (existing.active || existing.configuring)
24080
+ throw new AcpRuntimeError("ACP Session is busy", "acp_busy");
24081
+ if (!existing.record)
24037
24082
  throw new AcpRuntimeError(
24038
24083
  "ACP conversation is history-only because its external process was lost",
24039
24084
  "acp_history_only"
24040
24085
  );
24041
- return existing;
24086
+ await existing.loss;
24087
+ if (this.#sessions.get(request.conversationKey) !== existing) return this.#session(request);
24088
+ this.#sessions.delete(request.conversationKey);
24042
24089
  }
24043
24090
  const created = {
24044
24091
  conversationKey: request.conversationKey,
@@ -24058,20 +24105,40 @@ var AcpExecutor = class {
24058
24105
  await initialization;
24059
24106
  } catch (error62) {
24060
24107
  session.initialization = void 0;
24108
+ session.restoring = false;
24109
+ session.replay = void 0;
24061
24110
  throw error62;
24062
24111
  }
24063
24112
  }
24064
24113
  async #initialize(session, signal, probe = false) {
24065
- if (!probe && await this.#state?.has(session.conversationKey, session.cwd)) {
24066
- throw new AcpRuntimeError(
24067
- "ACP conversation is history-only after the Plugin or Host was restarted",
24068
- "acp_history_only"
24069
- );
24070
- }
24071
24114
  const timeout = AbortSignal.timeout(INITIALIZE_TIMEOUT_MS);
24072
24115
  const startupSignal = AbortSignal.any([signal, timeout]);
24073
24116
  startupSignal.throwIfAborted();
24074
24117
  const launch = this.#configured.launch;
24118
+ const stored = probe ? void 0 : this.#state?.read ? decodeContinuity(await this.#state.read(session.conversationKey)) : await this.#state?.has(session.conversationKey, session.cwd) ? "legacy" : void 0;
24119
+ if (stored === "legacy")
24120
+ throw new AcpRuntimeError(
24121
+ "ACP conversation is history-only after the Plugin or Host was restarted",
24122
+ "acp_history_only"
24123
+ );
24124
+ if (stored === "invalid" || stored && stored.cwd !== session.cwd || stored?.phase === "reserved")
24125
+ throw new AcpRuntimeError(
24126
+ "ACP conversation has no recoverable external Session identity",
24127
+ "acp_history_only"
24128
+ );
24129
+ if (stored?.phase === "history_gap")
24130
+ throw new AcpRuntimeError(
24131
+ "External Session history cannot be aligned with saved conversation events",
24132
+ "acp_history_gap"
24133
+ );
24134
+ if (stored) {
24135
+ session.record = stored;
24136
+ session.acpSessionId = stored.sessionId;
24137
+ session.restoring = true;
24138
+ }
24139
+ const binding = !probe && this.#state?.write ? await launchBinding(this.id, launch, startupSignal) : void 0;
24140
+ if (stored && stored.binding !== binding)
24141
+ throw new AcpRuntimeError("ACP Agent installation changed", "acp_restore_identity_changed");
24075
24142
  const executable = await checkedExecutable(launch.executable);
24076
24143
  await Promise.all((launch.requiredExecutables ?? []).map(checkedExecutable));
24077
24144
  const owner = this.#createConnection({
@@ -24101,9 +24168,62 @@ var AcpExecutor = class {
24101
24168
  owner.failed
24102
24169
  ]);
24103
24170
  if (initialized.protocolVersion !== 1) throw new Error("Unsupported ACP protocol version");
24171
+ if (stored) {
24172
+ session.replay = { updates: 0, userChunks: 0 };
24173
+ const pending = stored.phase === "prompt_pending";
24174
+ const supported = pending ? initialized.agentCapabilities?.loadSession : initialized.agentCapabilities?.sessionCapabilities?.resume;
24175
+ if (!supported)
24176
+ throw new AcpRuntimeError(
24177
+ "ACP Agent cannot restore this Session",
24178
+ "acp_restore_unsupported"
24179
+ );
24180
+ const restored = await Promise.race([
24181
+ owner.connection.agent.request(
24182
+ pending ? methods.agent.session.load : methods.agent.session.resume,
24183
+ { sessionId: stored.sessionId, cwd: session.cwd, mcpServers: [] },
24184
+ { cancellationSignal: startupSignal }
24185
+ ),
24186
+ owner.failed
24187
+ ]);
24188
+ session.configOptions = restored.configOptions ?? [];
24189
+ const restoredModel = currentAcpModel(session.configOptions);
24190
+ if (restoredModel) await this.#persistConfirmedModel(session, restoredModel);
24191
+ session.restoring = false;
24192
+ if (pending) {
24193
+ session.historyGap = true;
24194
+ session.record = {
24195
+ ...session.record,
24196
+ phase: "history_gap",
24197
+ pendingTurnId: void 0,
24198
+ gapEvidence: {
24199
+ replayedUpdates: session.replay?.updates ?? 0,
24200
+ replayedUserChunks: session.replay?.userChunks ?? 0
24201
+ }
24202
+ };
24203
+ await this.#state?.write?.(session.conversationKey, session.record);
24204
+ session.replay = void 0;
24205
+ throw new AcpRuntimeError(
24206
+ "External Session was restored, but conversation history may have a gap",
24207
+ "acp_history_gap"
24208
+ );
24209
+ }
24210
+ session.replay = void 0;
24211
+ return;
24212
+ }
24104
24213
  if (!probe) {
24105
24214
  startupSignal.throwIfAborted();
24106
- await this.#state?.mark(session.conversationKey, session.cwd);
24215
+ if (this.#state?.write && binding) {
24216
+ session.record = {
24217
+ version: 2,
24218
+ cwd: session.cwd,
24219
+ binding,
24220
+ phase: "reserved",
24221
+ committedPrompts: 0
24222
+ };
24223
+ await this.#state.write(session.conversationKey, session.record);
24224
+ } else {
24225
+ await this.#state?.mark(session.conversationKey, session.cwd);
24226
+ }
24107
24227
  session.continuityReserved = true;
24108
24228
  }
24109
24229
  const created = await Promise.race([
@@ -24116,6 +24236,17 @@ var AcpExecutor = class {
24116
24236
  ]);
24117
24237
  session.acpSessionId = created.sessionId;
24118
24238
  session.configOptions = created.configOptions ?? [];
24239
+ if (session.record) {
24240
+ const established = {
24241
+ ...session.record,
24242
+ phase: "established",
24243
+ sessionId: created.sessionId
24244
+ };
24245
+ await this.#state?.write?.(session.conversationKey, established);
24246
+ session.record = established;
24247
+ const createdModel = currentAcpModel(session.configOptions);
24248
+ if (createdModel) await this.#persistConfirmedModel(session, createdModel);
24249
+ }
24119
24250
  if (!probe) {
24120
24251
  await this.#applyInitialConfig(
24121
24252
  session,
@@ -24124,6 +24255,55 @@ var AcpExecutor = class {
24124
24255
  );
24125
24256
  }
24126
24257
  }
24258
+ async #beginPrompt(session, turnId) {
24259
+ if (!session.record) return;
24260
+ const pending = {
24261
+ ...session.record,
24262
+ phase: "prompt_pending",
24263
+ pendingTurnId: turnId
24264
+ };
24265
+ await this.#state?.write?.(session.conversationKey, pending);
24266
+ session.record = pending;
24267
+ }
24268
+ /** Called only after the Runtime Kernel has durably accepted the terminal event. */
24269
+ async acknowledgeExecution(conversationKey, turnId) {
24270
+ const session = this.#sessions.get(conversationKey);
24271
+ if (!session?.record || session.awaitingAck !== turnId || session.record.phase !== "prompt_pending" || session.record.pendingTurnId !== turnId)
24272
+ return;
24273
+ const committed = {
24274
+ ...session.record,
24275
+ phase: "committed",
24276
+ committedPrompts: session.record.committedPrompts + 1,
24277
+ pendingTurnId: void 0
24278
+ };
24279
+ try {
24280
+ await this.#state?.write?.(conversationKey, committed);
24281
+ } catch (error62) {
24282
+ session.historyGap = true;
24283
+ await this.#lose(session);
24284
+ throw error62;
24285
+ }
24286
+ session.record = committed;
24287
+ session.awaitingAck = void 0;
24288
+ }
24289
+ /** The external turn settled, but Maka did not durably consume its terminal event. */
24290
+ async abandonExecution(conversationKey, turnId) {
24291
+ const session = this.#sessions.get(conversationKey);
24292
+ if (!session?.record || session.awaitingAck !== turnId || session.record.phase !== "prompt_pending" || session.record.pendingTurnId !== turnId)
24293
+ return;
24294
+ session.historyGap = true;
24295
+ session.awaitingAck = void 0;
24296
+ session.record = {
24297
+ ...session.record,
24298
+ phase: "history_gap",
24299
+ pendingTurnId: void 0
24300
+ };
24301
+ try {
24302
+ await this.#state?.write?.(conversationKey, session.record);
24303
+ } finally {
24304
+ await this.#lose(session);
24305
+ }
24306
+ }
24127
24307
  async #applyInitialConfig(session, values, signal, restoreOnFailure = false) {
24128
24308
  for (const [key, value] of Object.entries(values)) {
24129
24309
  const option = session.configOptions.find(
@@ -24142,7 +24322,10 @@ var AcpExecutor = class {
24142
24322
  `ACP configuration value is unavailable: ${key}`,
24143
24323
  "acp_config_invalid"
24144
24324
  );
24145
- if (option.currentValue === value) continue;
24325
+ if (option.currentValue === value) {
24326
+ if (key === "model") await this.#persistConfirmedModel(session, value);
24327
+ continue;
24328
+ }
24146
24329
  signal.throwIfAborted();
24147
24330
  try {
24148
24331
  const updated = await session.connection.agent.request(
@@ -24157,6 +24340,7 @@ var AcpExecutor = class {
24157
24340
  "acp_config_unconfirmed"
24158
24341
  );
24159
24342
  session.configOptions = updated.configOptions;
24343
+ if (key === "model") await this.#persistConfirmedModel(session, value);
24160
24344
  } catch (error62) {
24161
24345
  let restored = false;
24162
24346
  if (restoreOnFailure && !session.lost && !signal.aborted) {
@@ -24171,6 +24355,7 @@ var AcpExecutor = class {
24171
24355
  );
24172
24356
  if (confirmed?.type === "select" && confirmed.currentValue === option.currentValue && !session.lost) {
24173
24357
  session.configOptions = rollback.configOptions;
24358
+ if (key === "model") await this.#persistConfirmedModel(session, option.currentValue);
24174
24359
  restored = true;
24175
24360
  }
24176
24361
  } catch {
@@ -24181,16 +24366,22 @@ var AcpExecutor = class {
24181
24366
  }
24182
24367
  }
24183
24368
  }
24369
+ async #persistConfirmedModel(session, model) {
24370
+ if (!session.record || session.record.confirmedModel === model) return;
24371
+ const record2 = { ...session.record, confirmedModel: model };
24372
+ await this.#state?.write?.(session.conversationKey, record2);
24373
+ session.record = record2;
24374
+ }
24184
24375
  #configureClient(session, app) {
24185
24376
  app.onNotification(methods.client.session.update, ({ params }) => {
24186
24377
  if (params.sessionId === session.acpSessionId) this.#acceptUpdate(session, params.update);
24187
24378
  }).onRequest(methods.client.fs.readTextFile, async ({ params }) => {
24188
- this.#assertSession(session, params.sessionId);
24379
+ this.#assertWorkspaceAccess(session, params.sessionId);
24189
24380
  return {
24190
24381
  content: await readWorkspaceTextFile(session.cwd, params.path, params.line, params.limit)
24191
24382
  };
24192
24383
  }).onRequest(methods.client.fs.writeTextFile, async ({ params }) => {
24193
- this.#assertSession(session, params.sessionId);
24384
+ this.#assertWorkspaceAccess(session, params.sessionId);
24194
24385
  await writeWorkspaceTextFile(session.cwd, params.path, params.content);
24195
24386
  return {};
24196
24387
  }).onRequest(
@@ -24200,6 +24391,8 @@ var AcpExecutor = class {
24200
24391
  }
24201
24392
  async #requestPermission(session, request) {
24202
24393
  this.#assertSession(session, request.sessionId);
24394
+ if (session.restoring || session.historyGap || session.lost)
24395
+ return { outcome: { outcome: "cancelled" } };
24203
24396
  const active = session.active;
24204
24397
  if (!active) return { outcome: { outcome: "cancelled" } };
24205
24398
  const outcome = await active.context.requestPermission({
@@ -24211,6 +24404,14 @@ var AcpExecutor = class {
24211
24404
  return { outcome };
24212
24405
  }
24213
24406
  #acceptUpdate(session, update) {
24407
+ if (session.restoring) {
24408
+ if (session.replay) {
24409
+ session.replay.updates++;
24410
+ if (update.sessionUpdate === "user_message_chunk") session.replay.userChunks++;
24411
+ }
24412
+ return;
24413
+ }
24414
+ if (session.historyGap || session.lost) return;
24214
24415
  if (update.sessionUpdate === "config_option_update") {
24215
24416
  if (!session.configuring && !session.lost) session.configOptions = update.configOptions;
24216
24417
  return;
@@ -24229,6 +24430,15 @@ var AcpExecutor = class {
24229
24430
  if (update.sessionUpdate === "tool_call") this.#acceptTool(active, update);
24230
24431
  if (update.sessionUpdate === "tool_call_update") this.#acceptTool(active, update);
24231
24432
  }
24433
+ #assertWorkspaceAccess(session, sessionId) {
24434
+ this.#assertSession(session, sessionId);
24435
+ if (session.restoring || session.historyGap || session.lost) {
24436
+ throw new AcpRuntimeError(
24437
+ "ACP filesystem access is unavailable while Session history is untrusted",
24438
+ "acp_history_gap"
24439
+ );
24440
+ }
24441
+ }
24232
24442
  #acceptTool(active, update) {
24233
24443
  const snapshot = active.tools.get(update.toolCallId) ?? {
24234
24444
  id: update.toolCallId,
@@ -24327,6 +24537,12 @@ var AcpExecutor = class {
24327
24537
  if (owner) await owner.dispose();
24328
24538
  }
24329
24539
  };
24540
+ function currentAcpModel(options) {
24541
+ const option = options.find(
24542
+ (candidate) => candidate.type === "select" && (candidate.id === "model" || candidate.category === "model")
24543
+ );
24544
+ return option?.type === "select" ? option.currentValue : void 0;
24545
+ }
24330
24546
  function toolTextContent(content) {
24331
24547
  return content.flatMap(
24332
24548
  (item) => item.type === "content" && item.content.type === "text" ? [item.content.text] : []
@@ -24350,14 +24566,54 @@ function pluginStateStore(storage, executorId) {
24350
24566
  const key = (conversationKey) => `acp/${executorId}/${createHash("sha256").update(conversationKey).digest("hex")}`;
24351
24567
  return {
24352
24568
  async has(conversationKey) {
24353
- const value = (await storage.get(key(conversationKey))).value;
24354
- return value?.version === 1;
24569
+ return (await storage.get(key(conversationKey))).value !== void 0;
24355
24570
  },
24356
24571
  async mark(conversationKey, cwd) {
24357
24572
  await storage.set(key(conversationKey), { version: 1, cwd });
24573
+ },
24574
+ async read(conversationKey) {
24575
+ return (await storage.get(key(conversationKey))).value;
24576
+ },
24577
+ async write(conversationKey, state) {
24578
+ await storage.set(key(conversationKey), state);
24358
24579
  }
24359
24580
  };
24360
24581
  }
24582
+ function decodeContinuity(value) {
24583
+ if (value === void 0) return void 0;
24584
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "invalid";
24585
+ const record2 = value;
24586
+ if (record2.version === 1) return "legacy";
24587
+ if (record2.version !== 2 || typeof record2.cwd !== "string" || !isAbsolute2(record2.cwd) || typeof record2.binding !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(record2.binding) || !Number.isSafeInteger(record2.committedPrompts) || record2.committedPrompts < 0 || !["reserved", "established", "prompt_pending", "committed", "history_gap"].includes(
24588
+ String(record2.phase)
24589
+ ) || record2.sessionId !== void 0 && (typeof record2.sessionId !== "string" || !record2.sessionId || record2.sessionId.length > 4096) || record2.pendingTurnId !== void 0 && (typeof record2.pendingTurnId !== "string" || !record2.pendingTurnId) || record2.confirmedModel !== void 0 && (typeof record2.confirmedModel !== "string" || !record2.confirmedModel) || record2.phase === "reserved" && record2.sessionId !== void 0 || record2.phase !== "reserved" && !record2.sessionId || record2.phase === "prompt_pending" && !record2.pendingTurnId || record2.phase !== "prompt_pending" && record2.pendingTurnId !== void 0 || record2.gapEvidence !== void 0 && (record2.phase !== "history_gap" || !record2.gapEvidence || typeof record2.gapEvidence !== "object" || !Number.isSafeInteger(record2.gapEvidence.replayedUpdates) || !Number.isSafeInteger(record2.gapEvidence.replayedUserChunks) || record2.gapEvidence.replayedUpdates < 0 || record2.gapEvidence.replayedUserChunks < 0))
24590
+ return "invalid";
24591
+ return record2;
24592
+ }
24593
+ async function launchBinding(adapterId, launch, signal) {
24594
+ const paths = [launch.executable, ...launch.requiredExecutables ?? []];
24595
+ const files = [];
24596
+ for (const path of paths) {
24597
+ signal.throwIfAborted();
24598
+ const resolved = await checkedExecutable(path);
24599
+ const hash2 = createHash("sha256");
24600
+ for await (const chunk of createReadStream(resolved)) {
24601
+ signal.throwIfAborted();
24602
+ hash2.update(chunk);
24603
+ }
24604
+ files.push({ path: resolved, digest: hash2.digest("hex") });
24605
+ }
24606
+ return `sha256:${createHash("sha256").update(
24607
+ JSON.stringify({
24608
+ adapterId,
24609
+ files,
24610
+ args: launch.args ?? [],
24611
+ cwd: launch.cwd ?? dirname2(files[0].path),
24612
+ platform: process.platform,
24613
+ arch: process.arch
24614
+ })
24615
+ ).digest("hex")}`;
24616
+ }
24361
24617
  function validateAdapter(adapter) {
24362
24618
  if (!adapter || typeof adapter !== "object" || typeof adapter.id !== "string" || !/^[a-z][a-z0-9]*(?:[-_.][a-z0-9]+)*$/u.test(adapter.id) || typeof adapter.displayName !== "string" || !adapter.displayName.trim() || adapter.clientName !== void 0 && (typeof adapter.clientName !== "string" || !adapter.clientName.trim()) || typeof adapter.configure !== "function") {
24363
24619
  throw new TypeError("Invalid ACP Agent adapter");
@@ -35,7 +35,16 @@ export function normalizeCatalogEntry(value, id) {
35
35
  !isExecutorId(id) ||
36
36
  value.id !== id ||
37
37
  !isCatalogText(value.displayName) ||
38
- !['ready', 'unavailable', 'authentication_required', 'history_only'].includes(value.readiness) ||
38
+ ![
39
+ 'ready',
40
+ 'unavailable',
41
+ 'authentication_required',
42
+ 'history_only',
43
+ 'restorable',
44
+ 'restoring',
45
+ 'restore_failed',
46
+ 'history_gap',
47
+ ].includes(value.readiness) ||
39
48
  !Array.isArray(value.models) ||
40
49
  value.models.length > 256 ||
41
50
  !Array.from(value.models).every((model) => model &&
@@ -0,0 +1,47 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ export function exactQueueReorder(current, requestedEntryIds) {
20
+ if (requestedEntryIds.length !== current.length)
21
+ return undefined;
22
+ const remaining = new Map(current.map((entry) => [entry.entryId, entry]));
23
+ if (remaining.size !== current.length)
24
+ return undefined;
25
+ const entries = [];
26
+ for (const entryId of requestedEntryIds) {
27
+ const entry = remaining.get(entryId);
28
+ if (!entry)
29
+ return undefined;
30
+ remaining.delete(entryId);
31
+ entries.push(entry);
32
+ }
33
+ return {
34
+ entries,
35
+ changed: entries.some((entry, index) => current[index] !== entry),
36
+ };
37
+ }
38
+ export function moveQueueEntryId(currentEntryIds, sourceEntryId, targetEntryId) {
39
+ const sourceIndex = currentEntryIds.indexOf(sourceEntryId);
40
+ const targetIndex = currentEntryIds.indexOf(targetEntryId);
41
+ if (sourceIndex === -1 || targetIndex === -1 || sourceIndex === targetIndex)
42
+ return undefined;
43
+ const entries = [...currentEntryIds];
44
+ entries.splice(sourceIndex, 1);
45
+ entries.splice(targetIndex, 0, sourceEntryId);
46
+ return entries;
47
+ }
@@ -137,7 +137,8 @@ export function lookupModelRuntimeOverride(providerType, modelId) {
137
137
  export function openAiAdapterApiProtocol(modelId, providerType) {
138
138
  const id = modelId.trim();
139
139
  return (providerType === 'deepseek' && deepSeekModelSupportsResponses(id)) ||
140
- (providerType === 'opencode-go' && id === 'muse-spark-1.2-contributor') ||
140
+ (providerType === 'opencode-go' &&
141
+ (id === 'muse-spark-1.2-contributor' || id === 'muse-spark-1.3-contributor')) ||
141
142
  ((providerType === 'alibaba-token-plan-cn' || providerType === 'alibaba-token-plan') &&
142
143
  id === 'qwen3.8-max') ||
143
144
  /^gpt-[56]/i.test(id) ||
@@ -36,7 +36,15 @@ const AWS_CONFIG_SECRET_KEY_SOURCE = posixContinuedTokenSource('aws_secret_acces
36
36
  const AWS_SECRET_ACCESS_KEY_FLAG_SOURCE = posixContinuedTokenSource('--secret-access-key');
37
37
  const AWS_SECRET_ACCESS_KEY_ENV_SOURCE = posixContinuedTokenSource('AWS_SECRET_ACCESS_KEY');
38
38
  const QUOTED_SECRET_KEY_VALUE_PATTERN = /((?:"([^"\\]+)"\s*:\s*"))(?:\\.|[^"\\])*/g;
39
- const ASSIGNED_SECRET_KEY_VALUE_PATTERN = /\b(([A-Za-z][A-Za-z0-9_-]*)(?:[ \t]|\\\r?\n)*[:=](?:[ \t]|\\\r?\n)*['"]?)(?:\\\r?\n|[^\s"'&<>])+/g;
39
+ // The prefix lookahead and the value pattern share this source, so the sticky
40
+ // value pattern always matches where a prefix ends.
41
+ const ASSIGNED_SECRET_VALUE_CHARACTER_SOURCE = `${POSIX_LINE_CONTINUATION_SOURCE}|[^\\s"'&<>]`;
42
+ // The prefix matches a whole key-character run once, and the key starts at the
43
+ // run's first word-initial letter. Trying each such letter as its own start
44
+ // would rescan a long hyphenated run (base64url) once per hyphen.
45
+ const ASSIGNED_SECRET_PREFIX_PATTERN = new RegExp(`(?<![A-Za-z0-9_-])([A-Za-z0-9_-]+)${OPTIONAL_SHELL_SEPARATOR_SOURCE}[:=]${OPTIONAL_SHELL_SEPARATOR_SOURCE}['"]?(?=${ASSIGNED_SECRET_VALUE_CHARACTER_SOURCE})`, 'g');
46
+ const ASSIGNED_SECRET_KEY_PATTERN = /(?<![A-Za-z0-9_])[A-Za-z][A-Za-z0-9_-]*/;
47
+ const ASSIGNED_SECRET_VALUE_PATTERN = new RegExp(`(?:${ASSIGNED_SECRET_VALUE_CHARACTER_SOURCE})+`, 'y');
40
48
  const AUTHORIZATION_HEADER_PATTERN = /(^|[^A-Za-z0-9_])(['"]?(?:proxy[-_]?authorization|authorization)['"]?\s*:\s*['"]?(?:bearer|basic|token)\s+)[^\s"'<>]+/gim;
41
49
  const AWS_CLI_SPACE_SECRET_PATTERN = new RegExp(`(^|[\\s;&|()])((?:aws${SHELL_SEPARATOR_SOURCE}configure${SHELL_SEPARATOR_SOURCE}set${SHELL_SEPARATOR_SOURCE}${AWS_CONFIG_SECRET_KEY_SOURCE}|${AWS_SECRET_ACCESS_KEY_FLAG_SOURCE})${SHELL_SEPARATOR_SOURCE})${SHELL_SECRET_TOKEN_SOURCE}`, 'gm');
42
50
  const AWS_SECRET_ASSIGNMENT_PATTERN = new RegExp(`\\b(${AWS_SECRET_ACCESS_KEY_ENV_SOURCE}${OPTIONAL_SHELL_SEPARATOR_SOURCE}[:=]${OPTIONAL_SHELL_SEPARATOR_SOURCE}['"]?)(?:${POSIX_LINE_CONTINUATION_SOURCE}|[^\\s"'&<>])+`, 'gi');
@@ -59,7 +67,7 @@ function redactTextSecrets(value) {
59
67
  next = next.replace(AUTHORIZATION_HEADER_PATTERN, (_match, boundary, prefix) => `${boundary}${prefix}[redacted]`);
60
68
  next = next.replace(AWS_CLI_SPACE_SECRET_PATTERN, (_match, boundary, prefix, token) => `${boundary}${prefix}${redactShellToken(token)}`);
61
69
  next = next.replace(AWS_SECRET_ASSIGNMENT_PATTERN, (_match, prefix) => `${prefix}[redacted]`);
62
- next = next.replace(ASSIGNED_SECRET_KEY_VALUE_PATTERN, (match, prefix, key) => isAssignmentSensitiveKey(key) ? `${prefix}[redacted]` : match);
70
+ next = redactAssignedSecrets(next);
63
71
  for (const pattern of SECRET_PATTERNS) {
64
72
  // Each pattern's single capture group matches only the secret token, so the
65
73
  // replacement is always the full redaction marker. Never echo any part of
@@ -69,6 +77,34 @@ function redactTextSecrets(value) {
69
77
  }
70
78
  return next;
71
79
  }
80
+ function redactAssignedSecrets(value) {
81
+ let next = '';
82
+ let copied = 0;
83
+ ASSIGNED_SECRET_PREFIX_PATTERN.lastIndex = 0;
84
+ for (let match = ASSIGNED_SECRET_PREFIX_PATTERN.exec(value); match; match = ASSIGNED_SECRET_PREFIX_PATTERN.exec(value)) {
85
+ // The prefix stops where the value starts, so every value is still searched
86
+ // for a nested sensitive assignment (`excerpt: password=…`). This includes a
87
+ // redacted value: a key with an empty value takes the next `KEY=` as its
88
+ // value (`token= password="…"`), and that key's own value follows the quote.
89
+ const key = ASSIGNED_SECRET_KEY_PATTERN.exec(match[1] ?? '')?.[0] ?? '';
90
+ if (!isAssignmentSensitiveKey(key))
91
+ continue;
92
+ const valueStart = ASSIGNED_SECRET_PREFIX_PATTERN.lastIndex;
93
+ // A value that starts inside the last redacted value ends with it.
94
+ if (valueStart < copied)
95
+ continue;
96
+ ASSIGNED_SECRET_VALUE_PATTERN.lastIndex = valueStart;
97
+ const valueMatch = ASSIGNED_SECRET_VALUE_PATTERN.exec(value);
98
+ // The prefix lookahead promises a value here. Should the two patterns ever
99
+ // disagree, skip: a failed sticky match resets lastIndex, and copying from
100
+ // there would echo the value after its marker.
101
+ if (!valueMatch)
102
+ continue;
103
+ next += `${value.slice(copied, valueStart)}[redacted]`;
104
+ copied = valueStart + valueMatch[0].length;
105
+ }
106
+ return next + value.slice(copied);
107
+ }
72
108
  function posixContinuedTokenSource(token) {
73
109
  return [...token]
74
110
  .map((character) => escapeRegExpLiteral(character))
@@ -173,9 +209,12 @@ export function isSensitiveKey(key) {
173
209
  return qualifiedKey === 'service_account_key' || qualifiedKey === 'secret_access_key';
174
210
  }
175
211
  function sensitiveKeySegments(key) {
212
+ // The second split marks one capital per step, which keeps a long uppercase
213
+ // run (zero-filled base64) linear; `([A-Z]+)([A-Z][a-z])` backtracks through
214
+ // the run from every capital.
176
215
  return key
177
216
  .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
178
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
217
+ .replace(/([A-Z])(?=[A-Z][a-z])/g, '$1 ')
179
218
  .toLowerCase()
180
219
  .split(/[^a-z0-9]+/)
181
220
  .filter(Boolean);