@borgee/agents-host 0.2.28 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,6 +54,7 @@ agents-host start-managed <serverUrl> <apiKey> [agent-options]
54
54
  agents-host describe-managed <serverUrl>
55
55
  agents-host apply-managed <serverUrl> --stdin
56
56
  agents-host apply-managed <serverUrl> --spec-json <json>
57
+ agents-host cleanup-managed <serverUrl> [--purge]
57
58
  agents-host log <serverUrl> [--lines <n>] [--follow]
58
59
  agents-host log <serverUrl> --path
59
60
  agents-host start --config <path-to-host-config> [--debug]
@@ -77,7 +78,28 @@ RUNTIME_PROVIDER=claude \
77
78
  pnpm --filter @borgee/agents-host dev
78
79
  ```
79
80
 
80
- Codex remains opt-in. Select `RUNTIME_PROVIDER=codex` only when `AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES` includes `codex-provider`.
81
+ Codex now ships on the same default hosted-provider surface as Claude and Copilot. Select `RUNTIME_PROVIDER=codex` directly unless you are explicitly rolling that path back with `AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES=codex-provider`.
82
+
83
+ ### Shipped internal compatibility defaults and rollback controls
84
+
85
+ The current shipped internal compatibility surface resolves gates in this order:
86
+
87
+ 1. start from the shipped defaults: `copilot-provider-v2`, `codex-provider`, `managed-runtime-convergence`, and `policy-audit-enforcement`
88
+ 2. add any explicit entries from `AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES`
89
+ 3. subtract any explicit entries from `AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES`
90
+
91
+ The shipped default set now also includes `connections-state-layer`, `context-injection`, `skill-runtime`, `localhost-gateway`, `token-binding`, `collaboration-outcome-model`, `task-thread-collaboration-contract`, and `collaboration-capabilities-diagnostics`. `attention-follow-semantics`, `collaboration-skill-first`, and hosted `praestoclaw-provider` still remain opt-in.
92
+
93
+ Managed-runtime settings still preserve the managed rollback rule for `connections-state-layer` plus `token-binding`: when `managed-runtime-convergence` remains enabled, the managed surface forces that pair on; explicitly disabling `managed-runtime-convergence` removes that pair from the managed surface again unless you also explicitly re-enable them.
94
+
95
+ Managed runtime precedence stays intentional: detached daemon respawns always reuse the persisted `managed-runtime-settings.json` snapshot, bare `start-managed <serverUrl>` and `apply-managed` treat the caller's current env as an explicit request to update that managed runtime surface, and full `start-managed <serverUrl> <apiKey> ...` validates the agent upsert against the existing managed-root snapshot before ambient rollback env can reject an otherwise valid existing surface.
96
+ Legacy managed roots created before `managed-runtime-settings.json` existed need one bare `agents-host start-managed <serverUrl>` migration pass before full per-agent updates can rely on that snapshot-backed precedence.
97
+
98
+ Rollback and hardening controls stay explicit:
99
+
100
+ - `AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES=<comma-list>` disables shipped gates for rollback
101
+ - `AGENTS_HOST_INTERNAL_PROVIDER_IMPLEMENTATIONS=copilot:v1` forces Copilot back to the v1 adapter while its v2 gate stays enabled; stale `claude:v1|v2` entries are tolerated as no-ops for compatibility
102
+ - `AGENTS_HOST_INTERNAL_POLICY_MODE=enforce` hardens the shipped `policy-audit-enforcement` gate beyond its default `audit-only` mode
81
103
 
82
104
  Or with the CLI:
83
105
 
@@ -117,6 +139,7 @@ and resumable in that case.
117
139
  Each managed runtime root keeps:
118
140
 
119
141
  - `agents-host.yaml` and `agents/` as the stable generated local-config entrypoints
142
+ - `managed-runtime-settings.json` as the persisted managed runtime surface snapshot
120
143
  - `daemon.log` as the detached managed daemon stdout/stderr log file
121
144
  - `.state/` as the per-agent runtime state base
122
145
  - `ctl.sock` as the local control socket
@@ -210,8 +233,8 @@ Notes:
210
233
  | `BORGEE_AGENT_API_KEY` | positional `<apiKey>` | yes | — | Agent API key from the web UI |
211
234
  | `AGENTS_HOST_DEBUG` | `--debug` | no | off | Extra host/provider lifecycle logs. Set `AGENTS_HOST_DEBUG=1` to enable without the CLI flag. |
212
235
  | `BORGEE_AGENT_NAME` | `--name` | no | `Assistant` | Display name used in prompts |
213
- | `RUNTIME_PROVIDER` | `--provider` | no | `claude` | `claude`, `copilot`, or `codex` when `codex-provider` is enabled |
214
- | `CLAUDE_COMMAND` / `CLAUDE_ARGS` | `--claude-command` / `--claude-args` | no | `claude` / `--print` | Local Claude CLI command + args |
236
+ | `RUNTIME_PROVIDER` | `--provider` | no | `claude` | `claude`, `copilot`, or `codex` |
237
+ | `CLAUDE_COMMAND` / `CLAUDE_ARGS` | `--claude-command` / `--claude-args` | no | `claude-agent-acp` / empty | Local Claude ACP adapter command + args. The shipped bundled `claude-agent-acp` path requires empty `CLAUDE_ARGS`; non-empty args are only valid when `CLAUDE_COMMAND` points at a custom ACP adapter command. Legacy `claude --print --permission-mode bypassPermissions` is tolerated as a compatibility alias. |
215
238
  | `CODEX_COMMAND` / `CODEX_ARGS` | `--codex-command` / `--codex-args` | no | `codex-acp` / empty | Local Codex ACP adapter command + args. The default launch resolves the bundled `@agentclientprotocol/codex-acp` entrypoint. |
216
239
  | `COPILOT_COMMAND` / `COPILOT_ARGS` | `--copilot-command` / `--copilot-args` | no | `copilot` / parsed but ignored by ACP runtime | Local Copilot CLI command. The persistent Copilot ACP prototype always launches `copilot --acp`. |
217
240
  | `COPILOT_SESSION_TTL_MINUTES` | `--copilot-session-ttl-minutes` | no | `2880` | Idle TTL for per-channel Copilot ACP sessions |
@@ -294,8 +317,8 @@ The spec shape is:
294
317
  "host": {
295
318
  "borgeeBaseUrl": "https://borgee.example.com",
296
319
  "defaults": {
297
- "claudeCommand": "claude",
298
- "claudeArgs": ["--print", "--permission-mode", "bypassPermissions"],
320
+ "claudeCommand": "claude-agent-acp",
321
+ "claudeArgs": [],
299
322
  "codexCommand": "codex-acp",
300
323
  "codexArgs": [],
301
324
  "copilotCommand": "copilot",
@@ -361,11 +384,8 @@ paths are also allowed.
361
384
  borgeeBaseUrl: https://borgee.example.com
362
385
  agentsDir: ./agents
363
386
  defaults:
364
- claudeCommand: claude
365
- claudeArgs:
366
- - --print
367
- - --permission-mode
368
- - bypassPermissions
387
+ claudeCommand: claude-agent-acp
388
+ claudeArgs: []
369
389
  codexCommand: codex-acp
370
390
  codexArgs: []
371
391
  copilotCommand: copilot
@@ -401,12 +421,6 @@ name: Support Bot
401
421
  apiKey: bgr_support_xxxxxxxx
402
422
  provider: claude
403
423
  enabled: true
404
- claudeArgs:
405
- - --print
406
- - --permission-mode
407
- - bypassPermissions
408
- - --model
409
- - sonnet
410
424
  ```
411
425
 
412
426
  Agent config fields:
@@ -414,8 +428,9 @@ Agent config fields:
414
428
  - `key` (**required**): stable identity used for diffing and reload decisions
415
429
  - `name` (**required**)
416
430
  - `apiKey` (**required**)
417
- - `provider` (**required**): `claude`, `copilot`, or gated `codex`
431
+ - `provider` (**required**): `claude`, `copilot`, or `codex` (roll back Codex only with `AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES=codex-provider`)
418
432
  - `enabled` (optional, default `true`)
433
+ - `claudeCommand` / `claudeArgs` (optional): leave unset to use the shipped bundled `claude-agent-acp` path. On that bundled path `claudeArgs` must stay empty. Non-empty `claudeArgs` are only supported when `claudeCommand` points at a custom ACP adapter command.
419
434
  - optional overrides for `claudeCommand`, `claudeArgs`, `codexCommand`,
420
435
  `codexArgs`, `copilotCommand`, `copilotArgs`, and
421
436
  `copilotSessionTtlMinutes`
@@ -455,10 +470,16 @@ successfully applied snapshot running.
455
470
  Each Borgee channel is mapped 1:1 to a provider-native session while it stays
456
471
  active:
457
472
 
458
- - **Claude**: first turn for a channel uses `--session-id <uuid>`; every turn
459
- after uses `-r/--resume <uuid>`. The host persists the channel→session map in
460
- its state root, serializes cross-channel map rewrites, and retries once with a
461
- fresh session immediately if Claude reports a stale `--resume` target.
473
+ - **Claude**: one persistent `@agentclientprotocol/claude-agent-acp` subprocess
474
+ is shared by a single `AgentsHost`, with one ACP session per Borgee channel.
475
+ Same-channel turns are serialized; different channels keep isolated ACP
476
+ sessions. Ordinary threads keep the shared runtime workspace cwd, while
477
+ eligible `task_assignment` threads switch the real ACP session cwd to that
478
+ same hidden local task-scoped workspace. The host persists the
479
+ channel→ACP-session map in its state root, restores same-host continuity with
480
+ ACP `session/resume` when available, retries stale restores with a fresh
481
+ session immediately, and injects the per-channel local context paths through
482
+ Claude ACP `_meta.systemPrompt.append`.
462
483
  - **Codex**: one persistent `@agentclientprotocol/codex-acp` subprocess is
463
484
  shared by a single `AgentsHost`, with one ACP session per Borgee channel. When
464
485
  the current turn has a materialized channel context payload, Codex refreshes a
@@ -481,13 +502,6 @@ active:
481
502
  state root and restores same-host continuity with ACP `session/resume` when
482
503
  available, otherwise `session/load` plus local replay cleanup on runtimes
483
504
  that advertise only load support.
484
- - **Claude**: Claude still keeps one provider-native session mapping per Borgee
485
- channel, but unlike the ACP-backed adapters it spawns a fresh CLI process for
486
- each turn. Ordinary threads inherit the host runtime cwd; eligible
487
- `task_assignment` threads run the CLI process with the hidden local
488
- task-scoped workspace as the process cwd, again without implying that the
489
- target repository is already checked out there.
490
-
491
505
  There is no separate transcript/history store on our side. Claude, Codex, and
492
506
  Copilot only persist the native session ids they should try to resume later;
493
507
  cross-host continuity still does not exist in this runtime.
@@ -128,7 +128,7 @@ export declare class AgentsHost {
128
128
  private resolveVisibleTurnBody;
129
129
  private shouldRelayProtocolReply;
130
130
  private normalizeMessage;
131
- private markAssignedTaskInProgress;
131
+ private resolveTaskAssignmentDelivery;
132
132
  private ensureSelfAgentId;
133
133
  private classifyAuthor;
134
134
  private refreshParticipantDirectory;
@@ -527,6 +527,11 @@ export class AgentsHost {
527
527
  if (!normalized) {
528
528
  return;
529
529
  }
530
+ const effective = await this.resolveTaskAssignmentDelivery(msg, normalized);
531
+ if (!effective) {
532
+ return;
533
+ }
534
+ msg = effective;
530
535
  let taskThreadContext = { state: 'inactive' };
531
536
  let attentionSnapshot;
532
537
  if (this.attentionFollowSemanticsEnabled
@@ -542,9 +547,6 @@ export class AgentsHost {
542
547
  }
543
548
  }
544
549
  }
545
- if (msg.message_type === 'task_assignment') {
546
- await this.markAssignedTaskInProgress(msg.channel_id, normalized.content);
547
- }
548
550
  await this.runUngatedTurn(msg, normalized.authorId, normalized.content, taskThreadContext, attentionSnapshot);
549
551
  }
550
552
  async handleCollaborationMessage(msg) {
@@ -552,9 +554,11 @@ export class AgentsHost {
552
554
  if (!normalized) {
553
555
  return;
554
556
  }
555
- if (msg.message_type === 'task_assignment') {
556
- await this.markAssignedTaskInProgress(msg.channel_id, normalized.content);
557
+ const effective = await this.resolveTaskAssignmentDelivery(msg, normalized);
558
+ if (!effective) {
559
+ return;
557
560
  }
561
+ msg = effective;
558
562
  const channelState = this.ensureCollaborationChannelState(msg.channel_id);
559
563
  await this.ensureProtocolStateLoaded(msg.channel_id, channelState);
560
564
  await this.ensureAttentionSnapshotLoaded(msg.channel_id, channelState);
@@ -2640,28 +2644,51 @@ export class AgentsHost {
2640
2644
  content,
2641
2645
  };
2642
2646
  }
2643
- async markAssignedTaskInProgress(threadId, content) {
2647
+ // Only a task assigned to this agent drives a task-assignment turn: it keeps the message a
2648
+ // task_assignment (and best-effort flips an open task to in_progress). An unassigned or
2649
+ // other-assigned task_assignment degrades to an ordinary message, and this agent's own echo of
2650
+ // it drops, so the creator never auto-starts work it was not assigned.
2651
+ async resolveTaskAssignmentDelivery(msg, normalized) {
2652
+ if (msg.message_type !== 'task_assignment') {
2653
+ return msg;
2654
+ }
2655
+ const selfAgentId = await this.ensureSelfAgentId();
2656
+ let task;
2644
2657
  try {
2645
- const selfAgentId = await this.ensureSelfAgentId();
2646
- const task = await waitForTaskForThread(this.borgee, threadId, {
2647
- preferredTaskId: extractTaskIdFromTaskAssignmentContent(content),
2658
+ task = await waitForTaskForThread(this.borgee, msg.channel_id, {
2659
+ preferredTaskId: extractTaskIdFromTaskAssignmentContent(normalized.content),
2648
2660
  attempts: 10,
2649
2661
  delayMs: 250,
2650
2662
  });
2651
- if (!task || task.status !== 'open' || task.assigneeId !== selfAgentId) {
2652
- return;
2653
- }
2654
- await this.borgee.updateTask({
2655
- taskId: task.id,
2656
- status: 'in_progress',
2657
- });
2658
2663
  }
2659
2664
  catch (error) {
2660
- this.logger.debugError('best-effort task auto-start failed', {
2661
- channelId: threadId,
2665
+ this.logger.debugError('task-assignment resolution failed', {
2666
+ channelId: msg.channel_id,
2662
2667
  error,
2663
2668
  });
2669
+ task = null;
2670
+ }
2671
+ if (task && task.assigneeId === selfAgentId) {
2672
+ if (task.status === 'open') {
2673
+ try {
2674
+ await this.borgee.updateTask({
2675
+ taskId: task.id,
2676
+ status: 'in_progress',
2677
+ });
2678
+ }
2679
+ catch (error) {
2680
+ this.logger.debugError('best-effort task auto-start failed', {
2681
+ channelId: msg.channel_id,
2682
+ error,
2683
+ });
2684
+ }
2685
+ }
2686
+ return msg;
2687
+ }
2688
+ if (normalized.authorId === selfAgentId) {
2689
+ return null;
2664
2690
  }
2691
+ return { ...msg, message_type: 'default' };
2665
2692
  }
2666
2693
  async ensureSelfAgentId() {
2667
2694
  if (this.selfAgentId) {
@@ -83,4 +83,4 @@ export declare function parseLogArgs(argv: string[]): ParsedLogArgs;
83
83
  export declare function parseDescribeManagedArgs(argv: string[]): ParsedDescribeManagedArgs;
84
84
  export declare function parseCleanupManagedArgs(argv: string[]): ParsedCleanupManagedArgs;
85
85
  export declare function parseApplyManagedArgs(argv: string[]): ParsedApplyManagedArgs;
86
- export declare const USAGE = "Usage:\n agents-host start <serverUrl> <apiKey> [options]\n agents-host start-managed <serverUrl>\n agents-host start-managed <serverUrl> <apiKey> [agent-options]\n agents-host describe-managed <serverUrl>\n agents-host cleanup-managed <serverUrl> [--purge]\n agents-host apply-managed <serverUrl> --stdin\n agents-host apply-managed <serverUrl> --spec-json <json>\n agents-host log <serverUrl> [--lines <n>] [--follow]\n agents-host log <serverUrl> --path\n agents-host start --config <path-to-host-config> [--debug]\n agents-host validate --config <path-to-host-config>\n agents-host describe --config <path-to-host-config>\n agents-host print-layout --root <dir>\n agents-host generate-config --root <dir> --stdin\n agents-host generate-config --root <dir> --spec-json <json>\n\nForeground start options:\n --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)\n\nSingle-agent agent options:\n --name <name> Display name (default: Assistant)\n --provider <claude|codex|copilot>\n Runtime provider (default: claude)\n --claude-command <cmd> Local Claude CLI command (default: claude)\n --claude-args <args> Local Claude CLI args (default: --print --permission-mode bypassPermissions)\n --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)\n --codex-args <args> Local Codex ACP adapter args\n --copilot-command <cmd> Local Copilot CLI command (default: copilot)\n --copilot-args <args> Ignored by the Copilot ACP prototype\n --copilot-session-ttl-minutes <minutes>\n Idle session TTL for Copilot ACP sessions (default: 2880)\n\nCommand summary:\n start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)\n start --config <path> Start agents + supervisor + watchers from a host config file\n start-managed <serverUrl> Restart or resume an existing per-serverUrl managed daemon\n start-managed <serverUrl> <apiKey>\n Upsert one agent into the per-serverUrl local daemon and exit after reconcile\n describe-managed <serverUrl> Print managed-runtime spec JSON for machine callers\n cleanup-managed <serverUrl> Stop one managed-runtime daemon locally; --purge also removes its runtime root\n apply-managed <serverUrl> Apply one managed-runtime full-set spec for machine callers\n log <serverUrl> Print the managed daemon log tail for one server runtime\n validate --config <path> Validate local-config files without starting agents or watchers\n describe --config <path> Print the current managed full-set spec as JSON\n print-layout --root <dir> Print the canonical default local-config layout as JSON\n generate-config --root <dir> --stdin Materialize canonical local-config files from stdin\n generate-config --root <dir> --spec-json <json>\n Compatibility input; JSON is exposed in process arguments\n\nExamples:\n agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug\n agents-host start-managed https://borgee.example.com\n agents-host start-managed https://borgee.example.com bgr_xxxxxxxx --provider copilot\n agents-host describe-managed https://borgee.example.com\n agents-host cleanup-managed https://borgee.example.com --purge\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host apply-managed https://borgee.example.com --stdin\n agents-host log https://borgee.example.com --lines 200 --follow\n agents-host log https://borgee.example.com --path\n agents-host start --config ./agents-host.yaml --debug\n agents-host validate --config ./agents-host.yaml\n agents-host describe --config ./agents-host.yaml\n agents-host print-layout --root ./runtime-root\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host generate-config --root ./runtime-root --stdin\n";
86
+ export declare const USAGE = "Usage:\n agents-host start <serverUrl> <apiKey> [options]\n agents-host start-managed <serverUrl>\n agents-host start-managed <serverUrl> <apiKey> [agent-options]\n agents-host describe-managed <serverUrl>\n agents-host cleanup-managed <serverUrl> [--purge]\n agents-host apply-managed <serverUrl> --stdin\n agents-host apply-managed <serverUrl> --spec-json <json>\n agents-host log <serverUrl> [--lines <n>] [--follow]\n agents-host log <serverUrl> --path\n agents-host start --config <path-to-host-config> [--debug]\n agents-host validate --config <path-to-host-config>\n agents-host describe --config <path-to-host-config>\n agents-host print-layout --root <dir>\n agents-host generate-config --root <dir> --stdin\n agents-host generate-config --root <dir> --spec-json <json>\n\nForeground start options:\n --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)\n\nSingle-agent agent options:\n --name <name> Display name (default: Assistant)\n --provider <claude|codex|copilot>\n Runtime provider (default: claude)\n --claude-command <cmd> Local Claude ACP adapter command (default: claude-agent-acp)\n --claude-args <args> Local Claude ACP adapter args (empty on the shipped bundled path)\n --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)\n --codex-args <args> Local Codex ACP adapter args\n --copilot-command <cmd> Local Copilot CLI command (default: copilot)\n --copilot-args <args> Ignored by the Copilot ACP prototype\n --copilot-session-ttl-minutes <minutes>\n Idle session TTL for Copilot ACP sessions (default: 2880)\n\nCommand summary:\n start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)\n start --config <path> Start agents + supervisor + watchers from a host config file\n start-managed <serverUrl> Restart or resume an existing per-serverUrl managed daemon\n start-managed <serverUrl> <apiKey>\n Upsert one agent into the per-serverUrl local daemon and exit after reconcile\n describe-managed <serverUrl> Print managed-runtime spec JSON for machine callers\n cleanup-managed <serverUrl> Stop one managed-runtime daemon locally; --purge also removes its runtime root\n apply-managed <serverUrl> Apply one managed-runtime full-set spec for machine callers\n log <serverUrl> Print the managed daemon log tail for one server runtime\n validate --config <path> Validate local-config files without starting agents or watchers\n describe --config <path> Print the current managed full-set spec as JSON\n print-layout --root <dir> Print the canonical default local-config layout as JSON\n generate-config --root <dir> --stdin Materialize canonical local-config files from stdin\n generate-config --root <dir> --spec-json <json>\n Compatibility input; JSON is exposed in process arguments\n\nExamples:\n agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug\n agents-host start-managed https://borgee.example.com\n agents-host start-managed https://borgee.example.com bgr_xxxxxxxx --provider copilot\n agents-host describe-managed https://borgee.example.com\n agents-host cleanup-managed https://borgee.example.com --purge\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host apply-managed https://borgee.example.com --stdin\n agents-host log https://borgee.example.com --lines 200 --follow\n agents-host log https://borgee.example.com --path\n agents-host start --config ./agents-host.yaml --debug\n agents-host validate --config ./agents-host.yaml\n agents-host describe --config ./agents-host.yaml\n agents-host print-layout --root ./runtime-root\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host generate-config --root ./runtime-root --stdin\n";
package/dist/cli-args.js CHANGED
@@ -494,8 +494,8 @@ Single-agent agent options:
494
494
  --name <name> Display name (default: Assistant)
495
495
  --provider <claude|codex|copilot>
496
496
  Runtime provider (default: claude)
497
- --claude-command <cmd> Local Claude CLI command (default: claude)
498
- --claude-args <args> Local Claude CLI args (default: --print --permission-mode bypassPermissions)
497
+ --claude-command <cmd> Local Claude ACP adapter command (default: claude-agent-acp)
498
+ --claude-args <args> Local Claude ACP adapter args (empty on the shipped bundled path)
499
499
  --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)
500
500
  --codex-args <args> Local Codex ACP adapter args
501
501
  --copilot-command <cmd> Local Copilot CLI command (default: copilot)
@@ -14,6 +14,7 @@ export declare const TOKEN_BINDING_COMPATIBILITY_GATE = "token-binding";
14
14
  export declare const POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE = "policy-audit-enforcement";
15
15
  export declare const MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE = "managed-runtime-convergence";
16
16
  export declare const COMPATIBILITY_GATES_ENV = "AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES";
17
+ export declare const INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV = "AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES";
17
18
  export declare const INTERNAL_POLICY_MODE_ENV = "AGENTS_HOST_INTERNAL_POLICY_MODE";
18
19
  export declare const INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV = "AGENTS_HOST_INTERNAL_PROVIDER_IMPLEMENTATIONS";
19
20
  export declare const MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION = 1;
@@ -27,8 +28,10 @@ export interface ManagedRuntimeSettingsSnapshot {
27
28
  providerImplementationOverrides: string[];
28
29
  internalPolicyMode: InternalPolicyMode;
29
30
  }
31
+ export declare const DEFAULT_INTERNAL_COMPATIBILITY_GATES: readonly string[];
30
32
  export declare function parseCompatibilityGates(rawValue: string | undefined): ReadonlySet<string>;
31
- export declare function resolveInternalCompatibilityGates(): ReadonlySet<string>;
33
+ export declare function resolveInternalCompatibilityGates(env?: NodeJS.ProcessEnv): ReadonlySet<string>;
34
+ export declare function resolveDisabledDefaultCompatibilityGates(compatibilityGates: Iterable<string>): string[];
32
35
  export declare function normalizeInternalProviderImplementationOverrides(rawValue: string | undefined): string[];
33
36
  export declare function parseInternalProviderImplementationOverrides(rawValue: string | undefined): InternalProviderImplementationOverrides;
34
37
  export declare function resolveManagedRuntimeProviderImplementationOverrides(env?: NodeJS.ProcessEnv, compatibilityGates?: string[]): string[];
@@ -15,10 +15,25 @@ export const TOKEN_BINDING_COMPATIBILITY_GATE = 'token-binding';
15
15
  export const POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE = 'policy-audit-enforcement';
16
16
  export const MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE = 'managed-runtime-convergence';
17
17
  export const COMPATIBILITY_GATES_ENV = 'AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES';
18
+ export const INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV = 'AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES';
18
19
  export const INTERNAL_POLICY_MODE_ENV = 'AGENTS_HOST_INTERNAL_POLICY_MODE';
19
20
  export const INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV = 'AGENTS_HOST_INTERNAL_PROVIDER_IMPLEMENTATIONS';
20
21
  export const MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION = 1;
21
22
  const VALID_PROVIDER_IMPLEMENTATION_ENTRIES = '"claude:v1", "claude:v2", "copilot:v1", or "copilot:v2"';
23
+ export const DEFAULT_INTERNAL_COMPATIBILITY_GATES = Object.freeze([
24
+ COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE,
25
+ COLLABORATION_OUTCOME_MODEL_COMPATIBILITY_GATE,
26
+ CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE,
27
+ CONTEXT_INJECTION_COMPATIBILITY_GATE,
28
+ CODEX_PROVIDER_COMPATIBILITY_GATE,
29
+ COPILOT_PROVIDER_V2_COMPATIBILITY_GATE,
30
+ LOCALHOST_GATEWAY_COMPATIBILITY_GATE,
31
+ MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE,
32
+ POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE,
33
+ SKILL_RUNTIME_COMPATIBILITY_GATE,
34
+ TASK_THREAD_COLLABORATION_CONTRACT_COMPATIBILITY_GATE,
35
+ TOKEN_BINDING_COMPATIBILITY_GATE,
36
+ ]);
22
37
  const PROVIDER_IMPLEMENTATION_COMPATIBILITY_GATES = {
23
38
  claude: CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE,
24
39
  copilot: COPILOT_PROVIDER_V2_COMPATIBILITY_GATE,
@@ -50,8 +65,19 @@ export function parseCompatibilityGates(rawValue) {
50
65
  }
51
66
  return gates;
52
67
  }
53
- export function resolveInternalCompatibilityGates() {
54
- return parseCompatibilityGates(process.env[COMPATIBILITY_GATES_ENV]);
68
+ export function resolveInternalCompatibilityGates(env = process.env) {
69
+ const compatibilityGates = new Set(DEFAULT_INTERNAL_COMPATIBILITY_GATES);
70
+ for (const gate of parseCompatibilityGates(env[COMPATIBILITY_GATES_ENV])) {
71
+ compatibilityGates.add(gate);
72
+ }
73
+ for (const gate of parseCompatibilityGates(env[INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV])) {
74
+ compatibilityGates.delete(gate);
75
+ }
76
+ return compatibilityGates;
77
+ }
78
+ export function resolveDisabledDefaultCompatibilityGates(compatibilityGates) {
79
+ const enabledGates = new Set(compatibilityGates);
80
+ return DEFAULT_INTERNAL_COMPATIBILITY_GATES.filter((gate) => !enabledGates.has(gate));
55
81
  }
56
82
  export function normalizeInternalProviderImplementationOverrides(rawValue) {
57
83
  if (!rawValue || rawValue.trim().length === 0) {
@@ -91,15 +117,28 @@ export function resolveManagedRuntimeProviderImplementationOverrides(env = proce
91
117
  const enabledGates = new Set(compatibilityGates);
92
118
  return normalizeInternalProviderImplementationOverrides(env[INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV]).filter((assignment) => {
93
119
  const [provider] = assignment.split(':');
120
+ if (provider === 'claude') {
121
+ return false;
122
+ }
94
123
  return enabledGates.has(PROVIDER_IMPLEMENTATION_COMPATIBILITY_GATES[provider]);
95
124
  });
96
125
  }
97
126
  export function resolveManagedRuntimeCompatibilityGates(env = process.env) {
98
- const compatibilityGates = new Set(parseCompatibilityGates(env[COMPATIBILITY_GATES_ENV]));
127
+ const compatibilityGates = new Set(resolveInternalCompatibilityGates(env));
128
+ compatibilityGates.delete(CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE);
129
+ const explicitlyEnabledGates = parseCompatibilityGates(env[COMPATIBILITY_GATES_ENV]);
99
130
  if (compatibilityGates.has(MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE)) {
100
131
  compatibilityGates.add(CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE);
101
132
  compatibilityGates.add(TOKEN_BINDING_COMPATIBILITY_GATE);
102
133
  }
134
+ else {
135
+ if (!explicitlyEnabledGates.has(CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE)) {
136
+ compatibilityGates.delete(CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE);
137
+ }
138
+ if (!explicitlyEnabledGates.has(TOKEN_BINDING_COMPATIBILITY_GATE)) {
139
+ compatibilityGates.delete(TOKEN_BINDING_COMPATIBILITY_GATE);
140
+ }
141
+ }
103
142
  return sortUniqueStrings(compatibilityGates);
104
143
  }
105
144
  export function resolveInternalPolicyMode(gateEnabled, env = process.env) {
package/dist/config.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { AgentsHostConfig, ProviderCommandConfig, ProviderKind } from './types.js';
2
2
  export declare const MAX_COPILOT_SESSION_TTL_MINUTES: number;
3
3
  export declare const DEFAULT_COPILOT_SESSION_TTL_MINUTES: number;
4
+ export declare const DEFAULT_CLAUDE_COMMAND = "claude-agent-acp";
5
+ export declare const LEGACY_CLAUDE_COMMAND = "claude";
6
+ export declare const LEGACY_CLAUDE_ONE_SHOT_ARGS: readonly ["--print", "--permission-mode", "bypassPermissions"];
4
7
  export declare const DEFAULT_PROVIDER_COMMAND_CONFIG: ProviderCommandConfig;
5
8
  export declare function requireNonEmptyString(value: unknown, message: string): string;
6
9
  export declare function optionalNonEmptyString(value: unknown, fieldName: string, sourceLabel: string): string | undefined;
@@ -9,5 +12,10 @@ export declare function optionalStringArray(value: unknown, fieldName: string, s
9
12
  export declare function parseCopilotSessionTtlMinutesValue(value: unknown, sourceLabel: string): number;
10
13
  export declare function resolveProvider(rawValue: string, sourceLabel: string): ProviderKind;
11
14
  export declare function assertProviderCompatibility(provider: ProviderKind, sourceLabel: string, env?: NodeJS.ProcessEnv): void;
15
+ export declare function isLegacyClaudeCompatibilityAlias(command: string, args: string[]): boolean;
16
+ export declare function isLegacyClaudeBinaryCommand(command: string): boolean;
17
+ export declare function hasLegacyClaudeOneShotArgs(args: string[]): boolean;
18
+ export declare function assertClaudeCommandCompatibility(command: string, args: string[], sourceLabel: string): void;
19
+ export declare function assertProviderCommandCompatibility(provider: ProviderKind, config: Pick<ProviderCommandConfig, 'claudeCommand' | 'claudeArgs'>, sourceLabel: string): void;
12
20
  export declare function resolveProviderCommandConfig(overrides?: Partial<ProviderCommandConfig>): ProviderCommandConfig;
13
21
  export declare function loadConfigFromEnv(env?: NodeJS.ProcessEnv): AgentsHostConfig;
package/dist/config.js CHANGED
@@ -1,11 +1,15 @@
1
+ import { basename } from 'node:path';
1
2
  import { resolveSingleAgentStateRoot } from './state-paths.js';
2
- import { CODEX_PROVIDER_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, parseCompatibilityGates, } from './compatibility-gates.js';
3
+ import { CODEX_PROVIDER_COMPATIBILITY_GATE, resolveInternalCompatibilityGates, } from './compatibility-gates.js';
3
4
  const MAX_TIMER_DELAY_MS = 2_147_483_647;
4
5
  export const MAX_COPILOT_SESSION_TTL_MINUTES = MAX_TIMER_DELAY_MS / 60_000;
5
6
  export const DEFAULT_COPILOT_SESSION_TTL_MINUTES = 2 * 24 * 60;
7
+ export const DEFAULT_CLAUDE_COMMAND = 'claude-agent-acp';
8
+ export const LEGACY_CLAUDE_COMMAND = 'claude';
9
+ export const LEGACY_CLAUDE_ONE_SHOT_ARGS = ['--print', '--permission-mode', 'bypassPermissions'];
6
10
  export const DEFAULT_PROVIDER_COMMAND_CONFIG = {
7
- claudeCommand: 'claude',
8
- claudeArgs: ['--print', '--permission-mode', 'bypassPermissions'],
11
+ claudeCommand: DEFAULT_CLAUDE_COMMAND,
12
+ claudeArgs: [],
9
13
  codexCommand: 'codex-acp',
10
14
  codexArgs: [],
11
15
  copilotCommand: 'copilot',
@@ -69,9 +73,41 @@ export function assertProviderCompatibility(provider, sourceLabel, env = process
69
73
  if (provider !== 'codex') {
70
74
  return;
71
75
  }
72
- const compatibilityGates = parseCompatibilityGates(env[COMPATIBILITY_GATES_ENV]);
76
+ const compatibilityGates = resolveInternalCompatibilityGates(env);
73
77
  if (!compatibilityGates.has(CODEX_PROVIDER_COMPATIBILITY_GATE)) {
74
- throw new Error(`${sourceLabel}: provider "codex" requires ${COMPATIBILITY_GATES_ENV} to include ${CODEX_PROVIDER_COMPATIBILITY_GATE}`);
78
+ throw new Error(`${sourceLabel}: provider "codex" requires the shipped ${CODEX_PROVIDER_COMPATIBILITY_GATE} path to remain enabled`);
79
+ }
80
+ }
81
+ export function isLegacyClaudeCompatibilityAlias(command, args) {
82
+ if (command !== LEGACY_CLAUDE_COMMAND || args.length !== LEGACY_CLAUDE_ONE_SHOT_ARGS.length) {
83
+ return false;
84
+ }
85
+ return args.every((arg, index) => arg === LEGACY_CLAUDE_ONE_SHOT_ARGS[index]);
86
+ }
87
+ export function isLegacyClaudeBinaryCommand(command) {
88
+ return basename(command) === LEGACY_CLAUDE_COMMAND;
89
+ }
90
+ export function hasLegacyClaudeOneShotArgs(args) {
91
+ return (args.length === LEGACY_CLAUDE_ONE_SHOT_ARGS.length
92
+ && args.every((arg, index) => arg === LEGACY_CLAUDE_ONE_SHOT_ARGS[index]));
93
+ }
94
+ export function assertClaudeCommandCompatibility(command, args, sourceLabel) {
95
+ if (command === DEFAULT_CLAUDE_COMMAND) {
96
+ if (args.length > 0) {
97
+ throw new Error(`Invalid Claude config in ${sourceLabel}: the shipped ${DEFAULT_CLAUDE_COMMAND} path does not accept CLAUDE_ARGS/claudeArgs. Leave them empty or point claudeCommand/CLAUDE_COMMAND at a custom ACP adapter command instead.`);
98
+ }
99
+ return;
100
+ }
101
+ if (isLegacyClaudeBinaryCommand(command)) {
102
+ if (isLegacyClaudeCompatibilityAlias(command, args)) {
103
+ return;
104
+ }
105
+ throw new Error(`Invalid Claude config in ${sourceLabel}: raw "${LEGACY_CLAUDE_COMMAND}" commands no longer speak ACP directly. Use the shipped ${DEFAULT_CLAUDE_COMMAND} default, keep only the exact legacy compatibility alias "${LEGACY_CLAUDE_COMMAND} ${LEGACY_CLAUDE_ONE_SHOT_ARGS.join(' ')}", or point claudeCommand/CLAUDE_COMMAND at a real ACP adapter command.`);
106
+ }
107
+ }
108
+ export function assertProviderCommandCompatibility(provider, config, sourceLabel) {
109
+ if (provider === 'claude') {
110
+ assertClaudeCommandCompatibility(config.claudeCommand, config.claudeArgs, sourceLabel);
75
111
  }
76
112
  }
77
113
  export function resolveProviderCommandConfig(overrides = {}) {
@@ -89,20 +125,22 @@ export function loadConfigFromEnv(env = process.env) {
89
125
  const provider = resolveProvider(envOr('RUNTIME_PROVIDER', 'claude', env), 'Unsupported RUNTIME_PROVIDER');
90
126
  assertProviderCompatibility(provider, 'Unsupported RUNTIME_PROVIDER', env);
91
127
  const agentApiKey = requireEnv('BORGEE_AGENT_API_KEY', env);
128
+ const providerConfig = resolveProviderCommandConfig({
129
+ claudeCommand: envOr('CLAUDE_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand, env),
130
+ claudeArgs: parseArgs(envOr('CLAUDE_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs.join(' '), env)),
131
+ codexCommand: envOr('CODEX_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.codexCommand, env),
132
+ codexArgs: parseArgs(envOr('CODEX_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.codexArgs.join(' '), env)),
133
+ copilotCommand: envOr('COPILOT_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand, env),
134
+ copilotArgs: parseArgs(envOr('COPILOT_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs.join(' '), env)),
135
+ copilotSessionTtlMinutes: env.COPILOT_SESSION_TTL_MINUTES && env.COPILOT_SESSION_TTL_MINUTES.trim().length > 0
136
+ ? parseCopilotSessionTtlMinutesValue(env.COPILOT_SESSION_TTL_MINUTES, 'environment variable COPILOT_SESSION_TTL_MINUTES')
137
+ : DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
138
+ });
139
+ assertProviderCommandCompatibility(provider, providerConfig, 'environment variables');
92
140
  return {
93
141
  borgeeBaseUrl: requireEnv('BORGEE_BASE_URL', env),
94
142
  stateRootDir: resolveSingleAgentStateRoot(env, undefined, agentApiKey),
95
- ...resolveProviderCommandConfig({
96
- claudeCommand: envOr('CLAUDE_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand, env),
97
- claudeArgs: parseArgs(envOr('CLAUDE_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs.join(' '), env)),
98
- codexCommand: envOr('CODEX_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.codexCommand, env),
99
- codexArgs: parseArgs(envOr('CODEX_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.codexArgs.join(' '), env)),
100
- copilotCommand: envOr('COPILOT_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand, env),
101
- copilotArgs: parseArgs(envOr('COPILOT_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs.join(' '), env)),
102
- copilotSessionTtlMinutes: env.COPILOT_SESSION_TTL_MINUTES && env.COPILOT_SESSION_TTL_MINUTES.trim().length > 0
103
- ? parseCopilotSessionTtlMinutesValue(env.COPILOT_SESSION_TTL_MINUTES, 'environment variable COPILOT_SESSION_TTL_MINUTES')
104
- : DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
105
- }),
143
+ ...providerConfig,
106
144
  agent: {
107
145
  agentApiKey,
108
146
  agentName: envOr('BORGEE_AGENT_NAME', 'Assistant', env),
@@ -778,8 +778,12 @@ function parseUpdateTaskInput(taskId, body) {
778
778
  status: readOptionalString(body.status),
779
779
  assigneeId: readOptionalString(body.assigneeId) ?? readOptionalString(body.assignee_id),
780
780
  title: readOptionalString(body.title),
781
+ description: readOptionalString(body.description),
781
782
  };
782
- if (input.status === undefined && input.assigneeId === undefined && input.title === undefined) {
783
+ if (input.status === undefined &&
784
+ input.assigneeId === undefined &&
785
+ input.title === undefined &&
786
+ input.description === undefined) {
783
787
  throw new GatewayHttpError(400, { error: 'no_updates' }, 'bad-request');
784
788
  }
785
789
  return input;
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
4
4
  import { parseDocument, stringify } from 'yaml';
5
- import { assertProviderCompatibility, optionalNonEmptyString, optionalStringArray, parseCopilotSessionTtlMinutesValue, requireNonEmptyString, resolveProvider, resolveProviderCommandConfig, } from './config.js';
5
+ import { assertProviderCompatibility, assertProviderCommandCompatibility, optionalNonEmptyString, optionalStringArray, parseCopilotSessionTtlMinutesValue, requireNonEmptyString, resolveProvider, resolveProviderCommandConfig, } from './config.js';
6
6
  import { resolveLocalConfigAgentStateRoot } from './state-paths.js';
7
7
  const SUPPORTED_CONFIG_EXTENSIONS = new Set(['.json', '.yaml', '.yml']);
8
8
  export const DEFAULT_LOCAL_HOST_CONFIG_FILENAME = 'agents-host.yaml';
@@ -186,6 +186,8 @@ export function parseGenerateConfigSpec(value, sourceLabel, env = process.env) {
186
186
  throw new Error(`Invalid generate-config agents[${index}] in ${sourceLabel}: expected an object`);
187
187
  }
188
188
  const agent = parseAgentConfigFile(`generate-config agents[${index}] in ${sourceLabel}`, entry, env);
189
+ const providerConfig = resolveAgentProviderCommandConfig(host.defaults, agent);
190
+ assertProviderCommandCompatibility(agent.provider, providerConfig, `generate-config agents[${index}] in ${sourceLabel}`);
189
191
  const existingIndex = seenKeys.get(agent.key);
190
192
  if (existingIndex !== undefined) {
191
193
  throw new Error(`Duplicate generate-config agent key "${agent.key}" in ${sourceLabel} at indexes ${existingIndex} and ${index}`);
@@ -598,6 +600,7 @@ function buildManagedAgentSnapshot(host, stateRootBaseDir, sourcePath, agent) {
598
600
  ...host.defaults,
599
601
  ...parseProviderCommandOverrides(agent, `agent config ${sourcePath}`),
600
602
  });
603
+ assertProviderCommandCompatibility(agent.provider, providerConfig, `agent config ${sourcePath}`);
601
604
  const config = {
602
605
  borgeeBaseUrl: host.borgeeBaseUrl,
603
606
  stateRootDir: resolveLocalConfigAgentStateRoot(stateRootBaseDir, agent.key),
@@ -614,6 +617,20 @@ function buildManagedAgentSnapshot(host, stateRootBaseDir, sourcePath, agent) {
614
617
  config,
615
618
  };
616
619
  }
620
+ function resolveAgentProviderCommandConfig(hostDefaults, agent) {
621
+ return resolveProviderCommandConfig({
622
+ ...hostDefaults,
623
+ ...(agent.claudeCommand !== undefined ? { claudeCommand: agent.claudeCommand } : {}),
624
+ ...(agent.claudeArgs !== undefined ? { claudeArgs: agent.claudeArgs } : {}),
625
+ ...(agent.codexCommand !== undefined ? { codexCommand: agent.codexCommand } : {}),
626
+ ...(agent.codexArgs !== undefined ? { codexArgs: agent.codexArgs } : {}),
627
+ ...(agent.copilotCommand !== undefined ? { copilotCommand: agent.copilotCommand } : {}),
628
+ ...(agent.copilotArgs !== undefined ? { copilotArgs: agent.copilotArgs } : {}),
629
+ ...(agent.copilotSessionTtlMinutes !== undefined
630
+ ? { copilotSessionTtlMinutes: agent.copilotSessionTtlMinutes }
631
+ : {}),
632
+ });
633
+ }
617
634
  export async function loadLocalConfigSnapshot(hostConfigPath, deps = {}) {
618
635
  const fileSystem = deps.fileSystem ?? nodeFileSystem;
619
636
  const absoluteHostConfigPath = resolve(hostConfigPath);
@@ -698,6 +715,8 @@ export async function loadLocalConfigGenerateSpec(hostConfigPath, deps = {}) {
698
715
  }
699
716
  const agentRecord = await loadParsedDocument(fileSystem, agentFilePath, 'agent config');
700
717
  const agentConfig = parseAgentConfigFile(agentFilePath, agentRecord, deps.env);
718
+ const providerConfig = resolveAgentProviderCommandConfig(hostConfig.defaults, agentConfig);
719
+ assertProviderCommandCompatibility(agentConfig.provider, providerConfig, `agent config ${agentFilePath}`);
701
720
  const existingPath = seenKeys.get(agentConfig.key);
702
721
  if (existingPath) {
703
722
  throw new Error(`Duplicate agent key "${agentConfig.key}" in ${existingPath} and ${agentFilePath}`);