salmon-loop 0.2.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/dist/cli/argv/headless-detection.js +27 -0
  2. package/dist/cli/chat-flow.js +11 -0
  3. package/dist/cli/chat.js +160 -24
  4. package/dist/cli/commands/chat.js +14 -7
  5. package/dist/cli/commands/flow-mode.js +63 -0
  6. package/dist/cli/commands/registry.js +2 -0
  7. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  8. package/dist/cli/commands/run/early-errors.js +23 -0
  9. package/dist/cli/commands/run/handler.js +115 -27
  10. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  11. package/dist/cli/commands/run/loop-params.js +2 -0
  12. package/dist/cli/commands/run/mode.js +2 -5
  13. package/dist/cli/commands/run/parse-options.js +16 -0
  14. package/dist/cli/commands/run/persist-session.js +10 -1
  15. package/dist/cli/commands/run/preflight.js +10 -0
  16. package/dist/cli/commands/run/reporter-factory.js +4 -0
  17. package/dist/cli/commands/run/runtime-llm.js +38 -11
  18. package/dist/cli/commands/run/runtime-options.js +2 -2
  19. package/dist/cli/commands/serve.js +97 -77
  20. package/dist/cli/commands/tool-names.js +78 -78
  21. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  22. package/dist/cli/headless/json-protocol.js +37 -0
  23. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  24. package/dist/cli/headless/protocol-metadata.js +22 -0
  25. package/dist/cli/headless/stream-json-protocol.js +34 -1
  26. package/dist/cli/index.js +6 -4
  27. package/dist/cli/locales/en.js +30 -6
  28. package/dist/cli/program-bootstrap.js +10 -5
  29. package/dist/cli/program-commands.js +5 -1
  30. package/dist/cli/reporters/anthropic-stream.js +7 -1
  31. package/dist/cli/reporters/json.js +4 -0
  32. package/dist/cli/reporters/stream-json.js +17 -2
  33. package/dist/cli/run-cli.js +5 -3
  34. package/dist/cli/slash/runtime.js +27 -12
  35. package/dist/cli/ui/components/CommandInput.js +7 -3
  36. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  37. package/dist/cli/utils/command-option-source.js +13 -0
  38. package/dist/cli/utils/verify-resolver.js +8 -4
  39. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  40. package/dist/core/adapters/fs/file-adapter.js +6 -0
  41. package/dist/core/adapters/fs/filesystem.js +2 -1
  42. package/dist/core/adapters/git/git-adapter.js +78 -1
  43. package/dist/core/backends/salmon-loop/task-executor.js +1 -0
  44. package/dist/core/benchmark/patch-artifact.js +124 -0
  45. package/dist/core/benchmark/swe-bench.js +25 -0
  46. package/dist/core/config/load.js +18 -11
  47. package/dist/core/config/resolve-llm.js +12 -0
  48. package/dist/core/config/resolvers/server.js +0 -6
  49. package/dist/core/config/validate.js +73 -21
  50. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  51. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  52. package/dist/core/context/keywords.js +18 -4
  53. package/dist/core/context/service-deps.js +2 -2
  54. package/dist/core/context/service.js +8 -0
  55. package/dist/core/context/steps/context-gather.js +38 -0
  56. package/dist/core/context/summarization/summarizer.js +55 -12
  57. package/dist/core/context/targeting/target-resolver.js +4 -4
  58. package/dist/core/extensions/index.js +23 -5
  59. package/dist/core/extensions/merge.js +14 -0
  60. package/dist/core/extensions/paths.js +31 -0
  61. package/dist/core/extensions/schemas.js +8 -5
  62. package/dist/core/facades/cli-chat.js +6 -2
  63. package/dist/core/facades/cli-command-chat.js +1 -0
  64. package/dist/core/facades/cli-command-tool-names.js +2 -0
  65. package/dist/core/facades/cli-observability.js +1 -1
  66. package/dist/core/facades/cli-program-bootstrap.js +1 -0
  67. package/dist/core/facades/cli-run-handler.js +4 -2
  68. package/dist/core/facades/cli-run-persist-session.js +1 -0
  69. package/dist/core/facades/cli-serve.js +4 -4
  70. package/dist/core/facades/cli-utils-worktree.js +1 -1
  71. package/dist/core/failure/diagnostics.js +53 -1
  72. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  73. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  74. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  75. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  76. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  77. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  78. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  79. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  80. package/dist/core/grizzco/steps/answer.js +13 -14
  81. package/dist/core/grizzco/steps/autopilot.js +396 -0
  82. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  83. package/dist/core/grizzco/steps/explore.js +37 -21
  84. package/dist/core/grizzco/steps/generateReview.js +2 -5
  85. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  86. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  87. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  88. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  89. package/dist/core/grizzco/steps/patch.js +105 -146
  90. package/dist/core/grizzco/steps/plan.js +101 -25
  91. package/dist/core/grizzco/steps/preflight.js +5 -6
  92. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  93. package/dist/core/grizzco/steps/research.js +39 -36
  94. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  95. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  96. package/dist/core/grizzco/steps/verify.js +13 -21
  97. package/dist/core/interaction/orchestration/facade.js +1 -1
  98. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  99. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  100. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  101. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  102. package/dist/core/llm/ai-sdk/request-params.js +113 -1
  103. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  104. package/dist/core/llm/ai-sdk.js +112 -27
  105. package/dist/core/llm/capabilities.js +12 -0
  106. package/dist/core/llm/contracts/repair.js +36 -30
  107. package/dist/core/llm/errors.js +83 -2
  108. package/dist/core/llm/message-composition.js +7 -22
  109. package/dist/core/llm/phase-router.js +29 -10
  110. package/dist/core/llm/redact.js +28 -3
  111. package/dist/core/llm/registry.js +2 -0
  112. package/dist/core/llm/request-augmentation.js +55 -0
  113. package/dist/core/llm/request-envelope.js +334 -0
  114. package/dist/core/llm/shared-request-assembly.js +35 -0
  115. package/dist/core/llm/stream-utils.js +13 -4
  116. package/dist/core/llm/utils.js +18 -29
  117. package/dist/core/memory/relevant-retrieval.js +144 -0
  118. package/dist/core/observability/logger.js +11 -2
  119. package/dist/core/patch/diff.js +1 -0
  120. package/dist/core/prompts/registry.js +39 -2
  121. package/dist/core/prompts/runtime.js +50 -12
  122. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  123. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  124. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  125. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  126. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  127. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  128. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  129. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  130. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  131. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  132. package/dist/core/protocols/a2a/agent-card.js +5 -3
  133. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  134. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  135. package/dist/core/protocols/acp/formal-agent.js +300 -58
  136. package/dist/core/protocols/acp/handlers.js +5 -1
  137. package/dist/core/protocols/acp/permission-provider.js +1 -1
  138. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  139. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  140. package/dist/core/public-capabilities/projections.js +29 -0
  141. package/dist/core/public-capabilities/registry.js +26 -0
  142. package/dist/core/public-capabilities/types.js +2 -0
  143. package/dist/core/runtime/agent-server-runtime.js +47 -43
  144. package/dist/core/runtime/execution-profile.js +67 -0
  145. package/dist/core/session/artifact-state.js +160 -0
  146. package/dist/core/session/compaction/index.js +183 -0
  147. package/dist/core/session/compaction/microcompact.js +78 -0
  148. package/dist/core/session/compaction/tracking.js +48 -0
  149. package/dist/core/session/compaction/types.js +11 -0
  150. package/dist/core/session/compression.js +8 -0
  151. package/dist/core/session/manager.js +244 -8
  152. package/dist/core/session/pruning-strategy.js +55 -9
  153. package/dist/core/session/replacement-preview-provider.js +24 -0
  154. package/dist/core/session/replacement-state.js +131 -0
  155. package/dist/core/session/resume-repair/pipeline.js +79 -0
  156. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  157. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  158. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  159. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  160. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  161. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  162. package/dist/core/session/resume-repair/types.js +2 -0
  163. package/dist/core/session/summary-sync.js +164 -13
  164. package/dist/core/session/token-tracker.js +6 -0
  165. package/dist/core/skills/audit.js +34 -0
  166. package/dist/core/skills/bridge.js +84 -7
  167. package/dist/core/skills/discovery.js +94 -0
  168. package/dist/core/skills/feature-flags.js +52 -0
  169. package/dist/core/skills/index.js +1 -1
  170. package/dist/core/skills/loader.js +195 -20
  171. package/dist/core/skills/parser.js +296 -24
  172. package/dist/core/skills/permissions.js +117 -0
  173. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  174. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  175. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  176. package/dist/core/strata/layers/worktree.js +67 -10
  177. package/dist/core/strata/runtime/synchronizer.js +29 -2
  178. package/dist/core/streaming/stream-assembler.js +75 -31
  179. package/dist/core/sub-agent/context-snapshot.js +156 -0
  180. package/dist/core/sub-agent/core/loop.js +1 -1
  181. package/dist/core/sub-agent/core/manager.js +119 -20
  182. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  183. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  184. package/dist/core/sub-agent/registry-defaults.js +4 -0
  185. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  186. package/dist/core/sub-agent/types.js +134 -5
  187. package/dist/core/tools/audit.js +13 -4
  188. package/dist/core/tools/builtin/ast-grep.js +1 -1
  189. package/dist/core/tools/builtin/ast.js +1 -1
  190. package/dist/core/tools/builtin/benchmark.js +360 -0
  191. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  192. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  193. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  194. package/dist/core/tools/builtin/fs.js +256 -23
  195. package/dist/core/tools/builtin/git.js +2 -2
  196. package/dist/core/tools/builtin/index.js +51 -2
  197. package/dist/core/tools/builtin/interaction.js +8 -1
  198. package/dist/core/tools/builtin/plan.js +37 -15
  199. package/dist/core/tools/builtin/shell.js +1 -1
  200. package/dist/core/tools/loader.js +39 -16
  201. package/dist/core/tools/mapper.js +17 -3
  202. package/dist/core/tools/mcp/client.js +2 -1
  203. package/dist/core/tools/parallel/scheduler.js +35 -4
  204. package/dist/core/tools/permissions/permission-rules.js +5 -10
  205. package/dist/core/tools/policy.js +6 -1
  206. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  207. package/dist/core/tools/router.js +24 -6
  208. package/dist/core/tools/session.js +458 -48
  209. package/dist/core/tools/tool-visibility.js +62 -0
  210. package/dist/core/tools/types.js +9 -1
  211. package/dist/core/types/execution.js +4 -0
  212. package/dist/core/types/flow-mode.js +8 -0
  213. package/dist/core/utils/path.js +52 -0
  214. package/dist/core/verification/runner.js +4 -1
  215. package/dist/core/version.js +17 -0
  216. package/dist/languages/typescript/index.js +4 -1
  217. package/dist/locales/en.js +35 -2
  218. package/dist/utils/eol.js +1 -1
  219. package/package.json +14 -7
  220. package/scripts/fix-es-abstract-compat.js +77 -0
  221. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  222. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  223. package/dist/core/runtime/sidecar-paths.js +0 -47
  224. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -6,8 +6,11 @@ import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
6
6
  import { inferTurnStopReasonFromFailure } from '../../interaction/turn-stop-reason.js';
7
7
  import { recordAuditEvent } from '../../observability/audit-trail.js';
8
8
  import { readPlan } from '../../plan/index.js';
9
+ import { toAcpPublicModes } from '../../public-capabilities/projections.js';
10
+ import { buildPublicCapabilityRegistry } from '../../public-capabilities/registry.js';
9
11
  import { parseSlashInput } from '../../slash/parser.js';
10
12
  import { buildCanonicalExecutionRequest } from '../shared/execution-request.js';
13
+ import { parseAcpFlowMode } from '../shared/flow-mode-mapping.js';
11
14
  import { createAcpCommandRunner } from './acp-command-runner.js';
12
15
  import { createAcpFileSystem } from './acp-filesystem.js';
13
16
  import { createAcpSessionStore, isTerminalTaskEvent } from './handlers.js';
@@ -35,7 +38,8 @@ const ACP_PERMISSION_POLICY_CONFIG_ID = '_salmonloop_permission_policy';
35
38
  const ACP_MODE_CONFIG_ID = '_salmonloop_mode';
36
39
  const ACP_PERMISSION_POLICY_ASK = 'ask';
37
40
  const ACP_PERMISSION_POLICY_DENY_ALL = 'deny_all';
38
- const ACP_DEFAULT_MODE_ID = 'interactive';
41
+ const ACP_PERMISSION_POLICY_ALLOW_ALL = 'allow_all';
42
+ const ACP_DEFAULT_MODE_ID = 'autopilot';
39
43
  const ACP_SESSION_STORE_MAX_ENTRIES = 200;
40
44
  const ACP_SESSION_STORE_MAX_AGE_MS = 1000 * 60 * 60 * 24 * 30;
41
45
  const ACP_SESSION_STORE_LOCK_STALE_MS = 1000 * 30;
@@ -85,11 +89,35 @@ function buildJsonResourceContentBlock(data) {
85
89
  const ACP_AVAILABLE_COMMANDS = [
86
90
  { name: 'help', description: text.acp.slashHelpDescription },
87
91
  ];
92
+ const ACP_PUBLIC_MODES = toAcpPublicModes(buildPublicCapabilityRegistry());
93
+ const ACP_PUBLIC_MODE_IDS = new Set(ACP_PUBLIC_MODES.map((mode) => mode.id));
94
+ function resolveExposedAcpModeId(value, fallback = ACP_DEFAULT_MODE_ID) {
95
+ const resolvedModeId = parseAcpFlowMode(value);
96
+ if (resolvedModeId && ACP_PUBLIC_MODE_IDS.has(resolvedModeId)) {
97
+ return resolvedModeId;
98
+ }
99
+ return fallback;
100
+ }
88
101
  function formatResourceLink(block) {
89
102
  const title = block.title ?? block.name ?? block.uri;
90
103
  const description = block.description ? ` - ${block.description}` : '';
91
104
  return `Resource: ${title} (${block.uri})${description}`;
92
105
  }
106
+ function formatEmbeddedResource(block) {
107
+ const resource = block.resource;
108
+ const uri = typeof resource.uri === 'string' ? resource.uri : 'embedded-resource';
109
+ const mimeType = typeof resource.mimeType === 'string' ? resource.mimeType : undefined;
110
+ if (typeof resource.text === 'string') {
111
+ const header = mimeType
112
+ ? `Embedded resource: ${uri} (${mimeType})`
113
+ : `Embedded resource: ${uri}`;
114
+ return `${header}\n${resource.text}`;
115
+ }
116
+ const header = mimeType
117
+ ? `Embedded binary resource: ${uri} (${mimeType})`
118
+ : `Embedded binary resource: ${uri}`;
119
+ return header;
120
+ }
93
121
  function extractTextFromPrompt(prompt, capabilities) {
94
122
  const parts = [];
95
123
  for (const block of prompt) {
@@ -114,6 +142,7 @@ function extractTextFromPrompt(prompt, capabilities) {
114
142
  if (!capabilities.embeddedContext) {
115
143
  throw new RequestError(-32000, 'Prompt content type resource is not supported');
116
144
  }
145
+ parts.push(formatEmbeddedResource(block));
117
146
  break;
118
147
  default:
119
148
  throw new RequestError(-32602, 'Invalid params: unsupported content block type');
@@ -230,11 +259,10 @@ function loopEventToSessionUpdate(event) {
230
259
  return null;
231
260
  }
232
261
  }
233
- function createSessionRuntimeState() {
234
- return createSessionRuntimeStateFromPersisted();
235
- }
236
262
  function isPermissionPolicyValue(value) {
237
- return value === ACP_PERMISSION_POLICY_ASK || value === ACP_PERMISSION_POLICY_DENY_ALL;
263
+ return (value === ACP_PERMISSION_POLICY_ASK ||
264
+ value === ACP_PERMISSION_POLICY_DENY_ALL ||
265
+ value === ACP_PERMISSION_POLICY_ALLOW_ALL);
238
266
  }
239
267
  function buildConfigOptions(state) {
240
268
  return [
@@ -255,26 +283,24 @@ function buildConfigOptions(state) {
255
283
  name: text.acp.permissionPolicyDenyAllName,
256
284
  description: text.acp.permissionPolicyDenyAllDescription,
257
285
  },
286
+ {
287
+ value: ACP_PERMISSION_POLICY_ALLOW_ALL,
288
+ name: text.acp.permissionPolicyAllowAllName,
289
+ description: text.acp.permissionPolicyAllowAllDescription,
290
+ },
258
291
  ],
259
292
  },
260
293
  {
261
294
  type: 'select',
262
295
  id: ACP_MODE_CONFIG_ID,
263
- name: 'Session Mode',
264
- description: text.acp.modeInteractiveDescription,
296
+ name: 'Execution Flow',
297
+ description: 'Choose how the agent should execute this session.',
265
298
  currentValue: state.modeId,
266
- options: [
267
- {
268
- value: 'interactive',
269
- name: 'Interactive',
270
- description: text.acp.modeInteractiveDescription,
271
- },
272
- {
273
- value: 'yolo',
274
- name: 'YOLO',
275
- description: text.acp.modeYoloDescription,
276
- },
277
- ],
299
+ options: ACP_PUBLIC_MODES.map((mode) => ({
300
+ value: mode.id,
301
+ name: mode.name,
302
+ description: mode.description,
303
+ })),
278
304
  },
279
305
  ];
280
306
  }
@@ -313,27 +339,13 @@ function buildSessionInfoUpdateIfChanged(session, state) {
313
339
  updatedAt,
314
340
  };
315
341
  }
316
- function isSessionModeId(value) {
317
- return value === 'interactive' || value === 'yolo';
318
- }
319
342
  function buildCurrentModeUpdate(modeId) {
320
343
  return { sessionUpdate: 'current_mode_update', currentModeId: modeId };
321
344
  }
322
345
  function buildModesState(modeId) {
323
346
  return {
324
347
  currentModeId: modeId,
325
- availableModes: [
326
- {
327
- id: 'interactive',
328
- name: 'Interactive',
329
- description: text.acp.modeInteractiveDescription,
330
- },
331
- {
332
- id: 'yolo',
333
- name: 'YOLO',
334
- description: text.acp.modeYoloDescription,
335
- },
336
- ],
348
+ availableModes: ACP_PUBLIC_MODES.map((mode) => ({ ...mode })),
337
349
  };
338
350
  }
339
351
  function buildCurrentModeUpdateIfChanged(state) {
@@ -343,18 +355,28 @@ function buildCurrentModeUpdateIfChanged(state) {
343
355
  state.lastModeDigest = digest;
344
356
  return buildCurrentModeUpdate(state.modeId);
345
357
  }
358
+ function getLegacyPermissionPolicyForModeValue(value) {
359
+ const normalized = String(value ?? '')
360
+ .trim()
361
+ .toLowerCase();
362
+ if (normalized === 'interactive')
363
+ return ACP_PERMISSION_POLICY_ASK;
364
+ if (normalized === 'yolo')
365
+ return ACP_PERMISSION_POLICY_ALLOW_ALL;
366
+ return null;
367
+ }
346
368
  function getPermissionPolicyForAuthorization(state) {
347
- if (state.modeId === 'yolo')
348
- return 'allow_all';
349
369
  return state.permissionPolicy;
350
370
  }
351
371
  function createSessionRuntimeStateFromPersisted(input) {
372
+ const defaultPermissionPolicy = isPermissionPolicyValue(String(input?.defaultPermissionPolicy))
373
+ ? input?.defaultPermissionPolicy
374
+ : ACP_PERMISSION_POLICY_ASK;
352
375
  const permissionPolicy = isPermissionPolicyValue(String(input?.permissionPolicy))
353
376
  ? input?.permissionPolicy
354
- : ACP_PERMISSION_POLICY_ASK;
355
- const modeId = isSessionModeId(String(input?.modeId))
356
- ? input?.modeId
357
- : (input?.defaultModeId ?? ACP_DEFAULT_MODE_ID);
377
+ : defaultPermissionPolicy;
378
+ const defaultModeId = resolveExposedAcpModeId(input?.defaultModeId);
379
+ const modeId = resolveExposedAcpModeId(input?.modeId, defaultModeId);
358
380
  const state = {
359
381
  runtimePlanSessionId: null,
360
382
  runtimePlanPathHint: null,
@@ -444,6 +466,74 @@ function buildPlanUpdateFromCoreIfChanged(read, state) {
444
466
  entries,
445
467
  };
446
468
  }
469
+ function envListToRecord(env) {
470
+ const record = {};
471
+ for (const entry of env) {
472
+ record[entry.name] = entry.value;
473
+ }
474
+ return record;
475
+ }
476
+ function headersListToRecord(headers) {
477
+ const record = {};
478
+ for (const entry of headers) {
479
+ record[entry.name] = entry.value;
480
+ }
481
+ return record;
482
+ }
483
+ function acpMcpServersToResolved(mcpServers) {
484
+ if (!Array.isArray(mcpServers))
485
+ return [];
486
+ const resolved = [];
487
+ for (const server of mcpServers) {
488
+ const transportType = 'type' in server ? server.type : 'stdio';
489
+ if (transportType === 'sse' || transportType === 'acp') {
490
+ throw new RequestError(-32602, `Invalid params: unsupported MCP server transport "${transportType}"`);
491
+ }
492
+ if ('type' in server && server.type === 'http') {
493
+ const httpServer = server;
494
+ resolved.push({
495
+ name: httpServer.name,
496
+ enabled: true,
497
+ transport: 'http',
498
+ url: httpServer.url,
499
+ headers: headersListToRecord(httpServer.headers),
500
+ allowTools: ['*'],
501
+ allowResources: [],
502
+ scope: 'repo',
503
+ });
504
+ continue;
505
+ }
506
+ if (transportType !== 'stdio') {
507
+ throw new RequestError(-32602, `Invalid params: unsupported MCP server transport "${transportType}"`);
508
+ }
509
+ const stdioServer = server;
510
+ resolved.push({
511
+ name: stdioServer.name,
512
+ enabled: true,
513
+ transport: 'stdio',
514
+ command: stdioServer.command,
515
+ args: stdioServer.args,
516
+ env: envListToRecord(stdioServer.env),
517
+ allowTools: ['*'],
518
+ allowResources: [],
519
+ scope: 'repo',
520
+ });
521
+ }
522
+ return resolved;
523
+ }
524
+ function acpMcpServersToExtensions(mcpServers) {
525
+ const resolvedServers = acpMcpServersToResolved(mcpServers);
526
+ if (resolvedServers.length === 0)
527
+ return undefined;
528
+ return {
529
+ mcpServers: resolvedServers,
530
+ toolPlugins: [],
531
+ skillDiscovery: { paths: [], scope: 'repo' },
532
+ };
533
+ }
534
+ function validateAcpMcpServers(mcpServers) {
535
+ void acpMcpServersToResolved(mcpServers);
536
+ }
447
537
  function extractSlashInput(prompt) {
448
538
  if (prompt.length !== 1)
449
539
  return null;
@@ -499,8 +589,9 @@ export function createAcpFormalAgent(deps) {
499
589
  embeddedContext: deps.capabilityPolicy?.promptCapabilities?.embeddedContext ?? false,
500
590
  };
501
591
  const mcpCapabilities = {
502
- http: deps.capabilityPolicy?.mcpCapabilities?.http ?? false,
592
+ http: deps.capabilityPolicy?.mcpCapabilities?.http ?? true,
503
593
  sse: deps.capabilityPolicy?.mcpCapabilities?.sse ?? false,
594
+ acp: deps.capabilityPolicy?.mcpCapabilities?.acp ?? false,
504
595
  };
505
596
  const sessionPersistencePath = deps.sessionPersistencePath;
506
597
  const sessionStorePolicy = {
@@ -514,6 +605,7 @@ export function createAcpFormalAgent(deps) {
514
605
  const executionBinding = deps.executionBinding ?? 'local';
515
606
  let sessionsHydrated = false;
516
607
  let hydratePromise = null;
608
+ const deletedSessionIds = new Map();
517
609
  function parseTimestamp(value) {
518
610
  if (typeof value !== 'string' || value.length === 0)
519
611
  return 0;
@@ -527,6 +619,31 @@ export function createAcpFormalAgent(deps) {
527
619
  .sort((a, b) => parseTimestamp(b.updatedAt) - parseTimestamp(a.updatedAt))
528
620
  .slice(0, sessionStorePolicy.maxEntries);
529
621
  }
622
+ function normalizeDeletedSessionRecords(input) {
623
+ if (!Array.isArray(input))
624
+ return [];
625
+ const byId = new Map();
626
+ for (const entry of input) {
627
+ if (!entry || typeof entry !== 'object')
628
+ continue;
629
+ const record = entry;
630
+ if (typeof record.id !== 'string' || !record.id)
631
+ continue;
632
+ if (typeof record.deletedAt !== 'string' || !record.deletedAt)
633
+ continue;
634
+ const current = byId.get(record.id);
635
+ if (!current || parseTimestamp(record.deletedAt) > parseTimestamp(current.deletedAt)) {
636
+ byId.set(record.id, { id: record.id, deletedAt: record.deletedAt });
637
+ }
638
+ }
639
+ return Array.from(byId.values());
640
+ }
641
+ function pruneDeletedSessionRecords(records) {
642
+ const cutoff = Date.now() - sessionStorePolicy.maxAgeMs;
643
+ return normalizeDeletedSessionRecords(records)
644
+ .filter((record) => parseTimestamp(record.deletedAt) >= cutoff)
645
+ .sort((a, b) => parseTimestamp(b.deletedAt) - parseTimestamp(a.deletedAt));
646
+ }
530
647
  function normalizePersistedSessionStore(input) {
531
648
  if (!input || typeof input !== 'object') {
532
649
  return { schemaVersion: 2, sessions: [] };
@@ -546,13 +663,20 @@ export function createAcpFormalAgent(deps) {
546
663
  title: entry.title,
547
664
  taskId: undefined,
548
665
  history: [],
549
- permissionPolicy: ACP_PERMISSION_POLICY_ASK,
550
- modeId: deps.defaultModeId ?? ACP_DEFAULT_MODE_ID,
666
+ permissionPolicy: isPermissionPolicyValue(String(deps.defaultPermissionPolicy))
667
+ ? deps.defaultPermissionPolicy
668
+ : ACP_PERMISSION_POLICY_ASK,
669
+ modeId: resolveExposedAcpModeId(deps.defaultModeId),
551
670
  })),
671
+ deletedSessions: [],
552
672
  };
553
673
  }
554
674
  if (raw.schemaVersion === 2) {
555
- return { schemaVersion: 2, sessions: raw.sessions };
675
+ return {
676
+ schemaVersion: 2,
677
+ sessions: raw.sessions,
678
+ deletedSessions: pruneDeletedSessionRecords(raw.deletedSessions),
679
+ };
556
680
  }
557
681
  return { schemaVersion: 2, sessions: [] };
558
682
  }
@@ -608,6 +732,7 @@ export function createAcpFormalAgent(deps) {
608
732
  }
609
733
  }
610
734
  const payload = { schemaVersion: 2, sessions: prunedRecords };
735
+ const payloadDeletedSessions = pruneDeletedSessionRecords(Array.from(deletedSessionIds, ([id, deletedAt]) => ({ id, deletedAt })));
611
736
  const primaryRepoPath = prunedRecords[0]?.cwd;
612
737
  const lockAuditDetails = {
613
738
  lockPath,
@@ -691,13 +816,24 @@ export function createAcpFormalAgent(deps) {
691
816
  // ignore read failure; writing fresh payload is acceptable
692
817
  }
693
818
  const merged = new Map();
819
+ const mergedDeletedSessions = pruneDeletedSessionRecords([
820
+ ...(existing.deletedSessions ?? []),
821
+ ...payloadDeletedSessions,
822
+ ]);
823
+ const mergedDeletedIds = new Set(mergedDeletedSessions.map((record) => record.id));
824
+ for (const record of mergedDeletedSessions) {
825
+ deletedSessionIds.set(record.id, record.deletedAt);
826
+ }
694
827
  for (const entry of existing.sessions)
695
828
  merged.set(entry.id, entry);
696
829
  for (const entry of payload.sessions)
697
830
  merged.set(entry.id, entry);
831
+ for (const id of mergedDeletedIds)
832
+ merged.delete(id);
698
833
  const mergedPayload = {
699
834
  schemaVersion: 2,
700
835
  sessions: pruneSessionRecords(Array.from(merged.values())),
836
+ deletedSessions: mergedDeletedSessions,
701
837
  };
702
838
  await writeFile(tempPath, JSON.stringify(mergedPayload, null, 2), 'utf8');
703
839
  await rename(tempPath, sessionPersistencePath);
@@ -740,7 +876,13 @@ export function createAcpFormalAgent(deps) {
740
876
  try {
741
877
  const raw = await readFile(sessionPersistencePath, 'utf8');
742
878
  const parsed = normalizePersistedSessionStore(JSON.parse(raw));
879
+ const deletedIds = new Set(parsed.deletedSessions?.map((record) => record.id) ?? []);
880
+ for (const record of parsed.deletedSessions ?? []) {
881
+ deletedSessionIds.set(record.id, record.deletedAt);
882
+ }
743
883
  for (const stored of pruneSessionRecords(parsed.sessions)) {
884
+ if (deletedIds.has(stored.id))
885
+ continue;
744
886
  sessions.upsert({
745
887
  id: stored.id,
746
888
  cwd: stored.cwd,
@@ -757,6 +899,7 @@ export function createAcpFormalAgent(deps) {
757
899
  if (!sessionRuntime.has(stored.id)) {
758
900
  sessionRuntime.set(stored.id, createSessionRuntimeStateFromPersisted({
759
901
  permissionPolicy: stored.permissionPolicy,
902
+ defaultPermissionPolicy: deps.defaultPermissionPolicy,
760
903
  modeId: stored.modeId,
761
904
  defaultModeId: deps.defaultModeId,
762
905
  }));
@@ -876,28 +1019,58 @@ export function createAcpFormalAgent(deps) {
876
1019
  }
877
1020
  async function loadSessionInternal(params) {
878
1021
  await hydrateSessionsOnce();
1022
+ validateAcpMcpServers(params.mcpServers);
879
1023
  const session = sessions.get(params.sessionId);
880
1024
  if (!session) {
881
1025
  throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
882
1026
  }
883
1027
  if (session.cwd !== params.cwd) {
1028
+ throw new RequestError(-32602, 'Invalid params: cwd does not match session cwd');
1029
+ }
1030
+ if (params.mcpServers) {
1031
+ sessions.update(params.sessionId, (current) => ({
1032
+ ...current,
1033
+ mcpServers: params.mcpServers,
1034
+ }));
1035
+ await persistSessionsBestEffort();
1036
+ }
1037
+ return session;
1038
+ }
1039
+ async function resumeSessionInternal(params) {
1040
+ await hydrateSessionsOnce();
1041
+ validateAcpMcpServers(params.mcpServers);
1042
+ const session = sessions.get(params.sessionId);
1043
+ if (!session) {
1044
+ throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
1045
+ }
1046
+ if (session.cwd !== params.cwd) {
1047
+ throw new RequestError(-32602, 'Invalid params: cwd does not match session cwd');
1048
+ }
1049
+ if (params.mcpServers) {
884
1050
  sessions.update(params.sessionId, (current) => ({
885
1051
  ...current,
886
- cwd: params.cwd,
887
1052
  mcpServers: params.mcpServers ?? [],
888
1053
  }));
889
1054
  await persistSessionsBestEffort();
890
1055
  }
891
1056
  return session;
892
1057
  }
1058
+ function toSessionInfo(session) {
1059
+ return {
1060
+ sessionId: session.id,
1061
+ cwd: session.cwd,
1062
+ title: typeof session.title === 'string' && session.title.trim() ? session.title : null,
1063
+ updatedAt: session.updatedAt,
1064
+ };
1065
+ }
893
1066
  function ensureSessionRuntimeState(sessionId) {
894
1067
  const existing = sessionRuntime.get(sessionId);
895
1068
  if (existing)
896
1069
  return existing;
897
- const created = createSessionRuntimeState();
898
- if (deps.defaultModeId) {
899
- created.modeId = deps.defaultModeId;
900
- }
1070
+ const created = createSessionRuntimeStateFromPersisted({
1071
+ defaultPermissionPolicy: deps.defaultPermissionPolicy,
1072
+ defaultModeId: deps.defaultModeId,
1073
+ });
901
1074
  sessionRuntime.set(sessionId, created);
902
1075
  return created;
903
1076
  }
@@ -923,7 +1096,11 @@ export function createAcpFormalAgent(deps) {
923
1096
  loadSession: loadSessionCapability,
924
1097
  promptCapabilities: promptCapabilities,
925
1098
  mcpCapabilities: mcpCapabilities,
926
- sessionCapabilities: {},
1099
+ sessionCapabilities: {
1100
+ list: {},
1101
+ resume: {},
1102
+ close: {},
1103
+ },
927
1104
  },
928
1105
  };
929
1106
  },
@@ -935,6 +1112,7 @@ export function createAcpFormalAgent(deps) {
935
1112
  if (!isAbsolutePath(params.cwd)) {
936
1113
  throw new RequestError(-32602, 'Invalid params: cwd must be an absolute path');
937
1114
  }
1115
+ validateAcpMcpServers(params.mcpServers);
938
1116
  const session = sessions.create({
939
1117
  cwd: params.cwd,
940
1118
  mcpServers: params.mcpServers ?? [],
@@ -1004,12 +1182,10 @@ export function createAcpFormalAgent(deps) {
1004
1182
  if (modeUpdate)
1005
1183
  await emitSessionUpdate(session.id, modeUpdate);
1006
1184
  for (const entry of session.history) {
1007
- if (entry.role !== 'assistant')
1008
- continue;
1009
1185
  for (const block of entry.content) {
1010
1186
  if (block.type === 'text' && typeof block.text === 'string' && block.text.trim()) {
1011
1187
  await emitSessionUpdate(session.id, {
1012
- sessionUpdate: 'agent_message_chunk',
1188
+ sessionUpdate: entry.role === 'assistant' ? 'agent_message_chunk' : 'user_message_chunk',
1013
1189
  content: buildTextContentBlock(block.text),
1014
1190
  });
1015
1191
  }
@@ -1082,12 +1258,63 @@ export function createAcpFormalAgent(deps) {
1082
1258
  }
1083
1259
  return response;
1084
1260
  },
1261
+ async listSessions(params) {
1262
+ await hydrateSessionsOnce();
1263
+ if (typeof params.cwd === 'string' && params.cwd && !isAbsolutePath(params.cwd)) {
1264
+ throw new RequestError(-32602, 'Invalid params: cwd must be an absolute path');
1265
+ }
1266
+ const filtered = sessions
1267
+ .list()
1268
+ .filter((session) => !params.cwd || session.cwd === params.cwd)
1269
+ .sort((a, b) => parseTimestamp(b.updatedAt) - parseTimestamp(a.updatedAt));
1270
+ return {
1271
+ sessions: filtered.map(toSessionInfo),
1272
+ };
1273
+ },
1274
+ async resumeSession(params) {
1275
+ const session = await resumeSessionInternal({
1276
+ sessionId: params.sessionId,
1277
+ cwd: params.cwd,
1278
+ mcpServers: params.mcpServers,
1279
+ });
1280
+ const runtimeState = ensureSessionRuntimeState(session.id);
1281
+ runtimeState.lastSessionInfoDigest = null;
1282
+ await emitSessionInfoUpdateBestEffort(session.id);
1283
+ const commandsUpdate = buildAvailableCommandsUpdateIfChanged(runtimeState);
1284
+ if (commandsUpdate)
1285
+ await emitSessionUpdate(session.id, commandsUpdate);
1286
+ const modeUpdate = buildCurrentModeUpdateIfChanged(runtimeState);
1287
+ if (modeUpdate)
1288
+ await emitSessionUpdate(session.id, modeUpdate);
1289
+ return {
1290
+ configOptions: buildConfigOptions(runtimeState),
1291
+ modes: buildModesState(runtimeState.modeId),
1292
+ };
1293
+ },
1294
+ async closeSession(params) {
1295
+ await hydrateSessionsOnce();
1296
+ const session = sessions.get(params.sessionId);
1297
+ if (!session)
1298
+ return {};
1299
+ sessions.update(params.sessionId, (current) => ({ ...current, cancelRequested: true }));
1300
+ if (session.taskId) {
1301
+ await deps.facade.cancelTask(session.taskId);
1302
+ }
1303
+ deletedSessionIds.set(params.sessionId, new Date().toISOString());
1304
+ sessionRuntime.delete(params.sessionId);
1305
+ sessions.delete(params.sessionId);
1306
+ await persistSessionsBestEffort();
1307
+ return {};
1308
+ },
1085
1309
  async setSessionConfigOption(params) {
1086
1310
  await hydrateSessionsOnce();
1087
1311
  if (!sessions.get(params.sessionId)) {
1088
1312
  throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
1089
1313
  }
1090
1314
  const runtimeState = ensureSessionRuntimeState(params.sessionId);
1315
+ if (typeof params.value !== 'string') {
1316
+ throw new RequestError(-32602, `Invalid params: unsupported non-string value for "${params.configId}"`);
1317
+ }
1091
1318
  if (params.configId === ACP_PERMISSION_POLICY_CONFIG_ID) {
1092
1319
  if (!isPermissionPolicyValue(params.value)) {
1093
1320
  throw new RequestError(-32602, `Invalid params: unsupported value "${params.value}" for "${params.configId}"`);
@@ -1095,10 +1322,15 @@ export function createAcpFormalAgent(deps) {
1095
1322
  runtimeState.permissionPolicy = params.value;
1096
1323
  }
1097
1324
  else if (params.configId === ACP_MODE_CONFIG_ID) {
1098
- if (!isSessionModeId(params.value)) {
1325
+ const parsedModeId = parseAcpFlowMode(params.value);
1326
+ if (!parsedModeId || !ACP_PUBLIC_MODE_IDS.has(parsedModeId)) {
1099
1327
  throw new RequestError(-32602, `Invalid params: unsupported value "${params.value}" for "${params.configId}"`);
1100
1328
  }
1101
- runtimeState.modeId = params.value;
1329
+ runtimeState.modeId = parsedModeId;
1330
+ const legacyPermissionPolicy = getLegacyPermissionPolicyForModeValue(params.value);
1331
+ if (legacyPermissionPolicy) {
1332
+ runtimeState.permissionPolicy = legacyPermissionPolicy;
1333
+ }
1102
1334
  }
1103
1335
  else {
1104
1336
  throw new RequestError(-32602, `Invalid params: unsupported configId "${params.configId}"`);
@@ -1122,13 +1354,22 @@ export function createAcpFormalAgent(deps) {
1122
1354
  throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
1123
1355
  }
1124
1356
  const runtimeState = ensureSessionRuntimeState(params.sessionId);
1125
- if (!isSessionModeId(params.modeId)) {
1357
+ const resolvedModeId = parseAcpFlowMode(params.modeId);
1358
+ if (!resolvedModeId || !ACP_PUBLIC_MODE_IDS.has(resolvedModeId)) {
1126
1359
  throw new RequestError(-32602, `Invalid params: unsupported modeId "${params.modeId}"`);
1127
1360
  }
1128
- runtimeState.modeId = params.modeId;
1361
+ runtimeState.modeId = resolvedModeId;
1362
+ const legacyPermissionPolicy = getLegacyPermissionPolicyForModeValue(params.modeId);
1363
+ if (legacyPermissionPolicy) {
1364
+ runtimeState.permissionPolicy = legacyPermissionPolicy;
1365
+ }
1129
1366
  sessions.update(params.sessionId, (current) => ({ ...current }));
1130
1367
  await persistSessionsBestEffort();
1131
1368
  await emitSessionInfoUpdateBestEffort(params.sessionId);
1369
+ const configUpdate = buildConfigOptionUpdateIfChanged(runtimeState);
1370
+ if (configUpdate) {
1371
+ await emitSessionUpdate(params.sessionId, configUpdate);
1372
+ }
1132
1373
  // Send mode update notification
1133
1374
  const modeUpdate = buildCurrentModeUpdateIfChanged(runtimeState);
1134
1375
  if (modeUpdate) {
@@ -1209,7 +1450,7 @@ export function createAcpFormalAgent(deps) {
1209
1450
  }
1210
1451
  const pendingUpdates = [];
1211
1452
  const executionRequest = buildCanonicalExecutionRequest({
1212
- capability: 'patch',
1453
+ capability: runtimeState.modeId,
1213
1454
  instruction: promptText,
1214
1455
  checkpointSessionId: params.sessionId,
1215
1456
  repoPath: session.cwd,
@@ -1222,6 +1463,7 @@ export function createAcpFormalAgent(deps) {
1222
1463
  fileSystemOverride: effectiveExecutionBinding === 'client'
1223
1464
  ? createAcpFileSystem({ conn: deps.conn, sessionId: params.sessionId })
1224
1465
  : undefined,
1466
+ extensions: acpMcpServersToExtensions(session.mcpServers),
1225
1467
  authorizationProvider: createAcpToolAuthorizationProvider({
1226
1468
  conn: deps.conn,
1227
1469
  sessionId: params.sessionId,
@@ -30,7 +30,11 @@ export function createAcpSessionStore() {
30
30
  if (!current)
31
31
  return undefined;
32
32
  const updated = mutate(current);
33
- updated.updatedAt = new Date().toISOString();
33
+ const nextUpdatedAt = new Date().toISOString();
34
+ updated.updatedAt =
35
+ nextUpdatedAt > current.updatedAt
36
+ ? nextUpdatedAt
37
+ : new Date(Date.parse(current.updatedAt) + 1).toISOString();
34
38
  sessions.set(id, updated);
35
39
  return updated;
36
40
  },
@@ -104,7 +104,7 @@ export function createAcpToolAuthorizationProvider(params) {
104
104
  const permissionPolicy = params.getPermissionPolicy?.() ?? 'ask';
105
105
  if (permissionPolicy === 'allow_all') {
106
106
  await emitInProgressBestEffort(request.id);
107
- return { outcome: 'allow_session', source: 'auto', reason: 'session_mode:yolo' };
107
+ return { outcome: 'allow_session', source: 'auto', reason: 'session_config:allow_all' };
108
108
  }
109
109
  const hasSideEffects = request.sideEffects.some((effect) => effect !== 'fs_read');
110
110
  if (permissionPolicy === 'deny_all' && hasSideEffects) {
@@ -0,0 +1,23 @@
1
+ import { FLOW_MODE_PUBLIC_METADATA } from '../../public-capabilities/flow-mode-metadata.js';
2
+ import { FLOW_MODES, parseFlowMode } from '../../types/flow-mode.js';
3
+ export const SUPPORTED_PROTOCOL_FLOW_MODES = FLOW_MODES;
4
+ export function parseAcpFlowMode(value) {
5
+ const normalized = String(value ?? '')
6
+ .trim()
7
+ .toLowerCase();
8
+ if (normalized === 'interactive' || normalized === 'yolo') {
9
+ return 'autopilot';
10
+ }
11
+ return parseFlowMode(normalized);
12
+ }
13
+ export function parseA2ASkillFlowMode(value) {
14
+ return parseFlowMode(value);
15
+ }
16
+ export function buildA2AFlowSkills() {
17
+ return SUPPORTED_PROTOCOL_FLOW_MODES.map((mode) => ({
18
+ id: mode,
19
+ title: FLOW_MODE_PUBLIC_METADATA[mode].a2aTitle,
20
+ description: FLOW_MODE_PUBLIC_METADATA[mode].description,
21
+ }));
22
+ }
23
+ //# sourceMappingURL=flow-mode-mapping.js.map