@ours.network/fleet 0.18.0-nightly.6 → 0.18.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.
- package/README.md +111 -43
- package/dist/application/fleet-query-service.js +12 -0
- package/dist/application/role-creation-service.js +3 -1
- package/dist/application/types.d.ts +11 -0
- package/dist/briefing.js +9 -2
- package/dist/build-info.json +7 -6
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +3 -0
- package/dist/cli.js +83 -7
- package/dist/config.d.ts +11 -3
- package/dist/config.js +40 -15
- package/dist/creation.d.ts +14 -15
- package/dist/creation.js +19 -13
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +117 -35
- package/dist/doctor.d.ts +1 -5
- package/dist/doctor.js +11 -18
- package/dist/fleet-proxy.d.ts +5 -0
- package/dist/harness/acp-agent.js +11 -6
- package/dist/harness/claude-code.js +204 -11
- package/dist/harness/codex.d.ts +4 -1
- package/dist/harness/codex.js +74 -12
- package/dist/harness/types.d.ts +54 -4
- package/dist/harness-plugins.d.ts +48 -0
- package/dist/harness-plugins.js +309 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/loops/manager.d.ts +30 -1
- package/dist/loops/manager.js +69 -6
- package/dist/loops/state.d.ts +18 -0
- package/dist/loops/state.js +4 -0
- package/dist/model-env.d.ts +71 -0
- package/dist/model-env.js +106 -0
- package/dist/monitor.js +1 -1
- package/dist/ops.js +1 -1
- package/dist/owner-channel/attachments.d.ts +2 -25
- package/dist/owner-channel/attachments.js +5 -61
- package/dist/owner-channel/channel.d.ts +30 -29
- package/dist/owner-channel/channel.js +291 -291
- package/dist/owner-channel/mcp.d.ts +24 -0
- package/dist/owner-channel/mcp.js +145 -0
- package/dist/owner-channel/notices.d.ts +7 -0
- package/dist/owner-channel/notices.js +9 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/runner.d.ts +48 -0
- package/dist/runner.js +237 -85
- package/dist/session/acp.d.ts +104 -0
- package/dist/session/acp.js +213 -10
- package/dist/session/activity.d.ts +31 -0
- package/dist/session/activity.js +48 -0
- package/dist/session/conversation-normalizer.d.ts +6 -0
- package/dist/session/conversation-normalizer.js +153 -10
- package/dist/session/conversation-types.d.ts +23 -4
- package/dist/session/types.d.ts +35 -0
- package/dist/spawn.js +29 -17
- package/dist/supervisor/systemd.js +2 -29
- package/dist/watchdog/briefing.js +7 -0
- package/dist/web-app/assets/{TerminalView-BAVk1Bot.js → TerminalView-C_G1ID2P.js} +1 -1
- package/dist/web-app/assets/{index-C3S-xFRU.js → index-BCBK78hw.js} +5 -5
- package/dist/web-app/index.html +1 -1
- package/dist/worklog.d.ts +7 -1
- package/dist/worklog.js +191 -39
- package/package.json +1 -3
- package/dist/owner-channel/message-recovery.d.ts +0 -25
- package/dist/owner-channel/message-recovery.js +0 -114
- package/dist/owner-channel/ours-client.d.ts +0 -148
- package/dist/owner-channel/ours-client.js +0 -231
package/dist/docs.js
CHANGED
|
@@ -27,11 +27,34 @@ Default configuration is \`~/fleet.yaml\` plus sorted \`~/fleet.d/*.yaml\` role
|
|
|
27
27
|
drop-ins. An explicit \`-c FILE\` replaces \`~/fleet.yaml\`; fleet.d still adds
|
|
28
28
|
roles. Validate with \`config\` and \`doctor\` before starting or restarting.
|
|
29
29
|
|
|
30
|
+
## Deterministic harness plugins
|
|
31
|
+
|
|
32
|
+
Harness plugin channels are configured independently and default to \`stable\`:
|
|
33
|
+
|
|
34
|
+
\`\`\`yaml
|
|
35
|
+
harnesses:
|
|
36
|
+
codex: { plugin_channel: nightly }
|
|
37
|
+
claude-code: { plugin_channel: stable }
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
\`ours-fleet plugins install [codex|claude-code...]\` resolves an npm dist-tag
|
|
41
|
+
once only when no same-channel lock exists. \`plugins update\` explicitly resolves
|
|
42
|
+
again. Both persist the exact semver under
|
|
43
|
+
\`~/.ours-fleet/harness-plugins/<harness>/plugin-lock.json\`, generate a local
|
|
44
|
+
marketplace whose npm source contains that exact version, and install from that
|
|
45
|
+
marketplace. \`plugins status\` reports the configured channel, exact lock, and
|
|
46
|
+
channel mismatches. Stable resolves \`latest\` once and pins it; nightly resolves
|
|
47
|
+
\`nightly\` once and requires a \`-nightly.N\` prerelease. Starts, restarts, and
|
|
48
|
+
reconciliation only re-materialize the marketplace from the lock: they never
|
|
49
|
+
query a dist-tag, run an installer, or advance a version.
|
|
50
|
+
An explicit nightly role refuses to launch until that harness has a nightly lock;
|
|
51
|
+
a configured/locked channel mismatch also fails with the exact update command.
|
|
52
|
+
|
|
30
53
|
The CLI never writes the base file: \`spawn\` writes \`~/fleet.d/Name.yaml\`. The
|
|
31
54
|
web console does write it, as a whole document — its setup wizard and
|
|
32
55
|
configuration editor may create, change or remove any top-level block, including
|
|
33
|
-
\`vars:\`, \`defaults:\`, \`roles:\`, \`watchdogs:\` and \`loops:\`. Only the base
|
|
34
|
-
file may hold \`defaults:\`, \`watchdogs:\` and \`loops:\`; a fleet.d drop-in may
|
|
56
|
+
\`vars:\`, \`defaults:\`, \`harnesses:\`, \`roles:\`, \`watchdogs:\` and \`loops:\`. Only the base
|
|
57
|
+
file may hold \`defaults:\`, \`harnesses:\`, \`watchdogs:\` and \`loops:\`; a fleet.d drop-in may
|
|
35
58
|
declare \`roles:\` and nothing else. Unrecognised top-level keys are round-tripped
|
|
36
59
|
untouched. Console edits are applied as surgical splices against the file's exact
|
|
37
60
|
bytes, so an unchanged save is byte-identical and lines outside the edit keep their
|
|
@@ -270,6 +293,9 @@ roles:
|
|
|
270
293
|
add_dirs: [/data/shared]
|
|
271
294
|
config:
|
|
272
295
|
model_reasoning_effort: high
|
|
296
|
+
mcp_servers: # claude-code: per-role MCP servers, additive by default
|
|
297
|
+
ours: { command: ours-mcp, args: [proxy] }
|
|
298
|
+
mcp_servers_only: false # true = ONLY these; drops user/project/plugin servers
|
|
273
299
|
bio: Public role card and when peers should engage it.
|
|
274
300
|
persona: Local operating contract, boundaries, and escalation policy.
|
|
275
301
|
briefing_file: /absolute/custom-briefing.md
|
|
@@ -314,6 +340,11 @@ Role values override defaults. \`\${name}\` substitutes entries from \`vars\`.
|
|
|
314
340
|
Other role fields include \`max_tokens\`, \`autocompact_pct\`, and \`isolation\`.
|
|
315
341
|
Use README.md for the complete isolation policy and resource-cap schema.
|
|
316
342
|
|
|
343
|
+
Supervised roles connect to the operator-configured ours daemon; they do not own its
|
|
344
|
+
lifecycle. Fleet forces \`OURS_AUTOSTART=0\` in tmux and ACP child processes after role
|
|
345
|
+
environment overlays. Start the shared daemon only through an explicit operator or
|
|
346
|
+
installer/setup flow.
|
|
347
|
+
|
|
317
348
|
## Permissions
|
|
318
349
|
|
|
319
350
|
Prefer the harness-neutral \`permissions\` block:
|
|
@@ -399,10 +430,14 @@ permissions through its harness and check the result against a fixed floor:
|
|
|
399
430
|
deny those requests with nobody to see it; with \`unattended: wait\` it warns,
|
|
400
431
|
because a human can still attach and answer.
|
|
401
432
|
|
|
402
|
-
Security meaning: \`ask\` maps to Codex \`untrusted\` and Claude \`default
|
|
403
|
-
\`auto\`
|
|
404
|
-
\`approval: allow\`
|
|
405
|
-
|
|
433
|
+
Security meaning: \`ask\` maps to Codex \`untrusted\` and Claude \`default\`.
|
|
434
|
+
\`auto\` selects Codex ACP \`agent\` (\`on-request\` + \`workspace-write\`) and
|
|
435
|
+
Claude \`acceptEdits\`. \`approval: allow\` selects Codex ACP's fully
|
|
436
|
+
non-interactive yolo mode, reported as \`agent-full-access\` (\`never\` +
|
|
437
|
+
\`danger-full-access\`), and Claude \`bypassPermissions\`. Codex tmux retains
|
|
438
|
+
independent approval and sandbox flags: \`auto\` is \`on-request\`, \`allow\`
|
|
439
|
+
is \`never\`, and \`filesystem\` still selects the sandbox. These modes genuinely
|
|
440
|
+
permit the actions the role was authorized to take —
|
|
406
441
|
\`dontAsk\` only suppresses the prompt while still refusing the action. Nothing
|
|
407
442
|
other than an explicit \`allow\` becomes non-interactive. Legacy \`deny\` keeps
|
|
408
443
|
its conservative Codex \`on-request\` / Claude \`plan\` translation. \`allow\` is therefore a real grant and
|
|
@@ -413,20 +448,45 @@ ACP carries agent-advertised session mode IDs and \`session/set_mode\`, but thos
|
|
|
413
448
|
IDs are agent-specific and ACP defines no portable permission-policy capability.
|
|
414
449
|
Fleet therefore uses the ACP primitive where an adapter exposes a matching mode
|
|
415
450
|
and otherwise performs the harness translation above. The bundled Codex ACP
|
|
416
|
-
adapter couples approval and sandboxing in its advertised mode IDs
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
\`
|
|
420
|
-
|
|
421
|
-
|
|
451
|
+
adapter couples approval and sandboxing in its advertised mode IDs. Neutral
|
|
452
|
+
\`allow\` therefore selects \`agent-full-access\` and widens \`filesystem:
|
|
453
|
+
workspace\` or \`read-only\` to \`danger-full-access\`; neutral \`auto\` selects
|
|
454
|
+
\`agent\` and \`workspace-write\` even when the neutral filesystem value differs.
|
|
455
|
+
An explicit \`harness_options.sandbox\` selects its corresponding ACP preset and
|
|
456
|
+
still wins, as does an explicit native approval override. \`config\` and
|
|
457
|
+
\`doctor\` report a coupled-mode mismatch as approximate. Use per-role
|
|
458
|
+
\`isolation:\` as the outer boundary for an \`allow\` ACP role. The live session
|
|
459
|
+
reports both its effective normalized mode and the exact native mode selected.
|
|
422
460
|
|
|
423
461
|
See also: \`spawn --approval/--filesystem/--unattended\` set this intent at
|
|
424
462
|
creation, and \`ours-fleet config\` prints each role's neutral settings, their
|
|
425
463
|
native translation, and any warning — the same text \`doctor\` reports.
|
|
426
464
|
|
|
427
465
|
Claude \`harness_options\`: \`permission_mode\` (default, acceptEdits, plan,
|
|
428
|
-
dontAsk, bypassPermissions), \`plugins\`, \`mem_palace\`,
|
|
429
|
-
\`mem_palace_midsession_autosave\`.
|
|
466
|
+
dontAsk, bypassPermissions), \`plugins\`, \`mem_palace\`,
|
|
467
|
+
\`mem_palace_midsession_autosave\`, \`mcp_servers\` and \`mcp_servers_only\`.
|
|
468
|
+
|
|
469
|
+
\`mcp_servers\` declares MCP servers for the role, in \`.mcp.json\`'s own shape
|
|
470
|
+
(a map of name to \`{ command, args, env }\`, or \`{ type: http|sse, url,
|
|
471
|
+
headers }\`). By default they are ADDED to whatever the OS user running the role
|
|
472
|
+
already has configured, on both session types: tmux passes \`--mcp-config\`, and
|
|
473
|
+
ACP sends them in \`session/new\`.
|
|
474
|
+
|
|
475
|
+
\`mcp_servers_only: true\` makes the declared set EXCLUSIVE — \`--strict-mcp-config\`
|
|
476
|
+
on tmux, \`strictMcpConfig\` on ACP. It is all-or-nothing and it ignores every
|
|
477
|
+
other MCP configuration: project \`.mcp.json\`, user settings, and **plugins**.
|
|
478
|
+
The ours connector is normally installed as a plugin, so a strict role that does
|
|
479
|
+
not re-declare it has no \`send_message\` and no \`get_messages\` — it cannot even
|
|
480
|
+
report that it has gone mute. Fleet therefore refuses a strict role whose
|
|
481
|
+
\`mcp_servers\` does not name the connector; declare it explicitly, e.g.
|
|
482
|
+
\`ours: { command: ours-mcp, args: [proxy] }\`.
|
|
483
|
+
|
|
484
|
+
Both options, and \`plugins\`, reach an ACP session through the bundled Claude ACP
|
|
485
|
+
agent's \`_meta\` vocabulary. A role that sets \`session_options.acp.command\` runs
|
|
486
|
+
an agent fleet did not choose and cannot be promised them, so that combination is
|
|
487
|
+
refused at validation rather than accepted and dropped. This narrows a role's
|
|
488
|
+
tool surface; it does not stop the harness deferring tool schemas, which is the
|
|
489
|
+
harness's own decision.
|
|
430
490
|
|
|
431
491
|
Codex \`harness_options\`: \`launcher\` (auto, ours-codex, codex), \`sandbox\`
|
|
432
492
|
(read-only, workspace-write, danger-full-access), \`approval\` or
|
|
@@ -514,12 +574,11 @@ owner of that authenticated source wire instead of the latest conversation.
|
|
|
514
574
|
Every other CID is rejected and warned about without reflecting its body. Fleet sends
|
|
515
575
|
accepted/queued/progress/interrupted/failure notices and routes the ACP turn's
|
|
516
576
|
final assistant text back to the authenticated sender with its source wire ID.
|
|
517
|
-
For file replies
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
pair it with a reply-linked caption; fleet, not the agent, chooses the owner.
|
|
577
|
+
For file replies of every kind — a response artifact, a proactive note, or an
|
|
578
|
+
in-turn attachment — the agent calls ours \`send_file\` to the channel identity
|
|
579
|
+
and may pair it with a reply-linked caption; fleet, not the agent, chooses the
|
|
580
|
+
owner. That is the only delivery route an agent is given: a tool call either
|
|
581
|
+
delivers or reports an error, where a file written to disk does neither.
|
|
523
582
|
Owner messages whose trimmed text starts with \`/\` are deterministic
|
|
524
583
|
supervisor commands and never enter the model: \`/help\` (alias \`/commands\`),
|
|
525
584
|
\`/status\`, \`/comments [status|on|off]\`, \`/interrupt\`, \`/clear\`,
|
|
@@ -552,18 +611,13 @@ their content signature must match the declared MIME, and symlinks or non-regula
|
|
|
552
611
|
paths fail closed. Sanitized copies live only in a mode-0700 request directory as
|
|
553
612
|
mode-0600 files and are removed after completion or bounded stale retention.
|
|
554
613
|
|
|
555
|
-
Voice prompts include a bounded transcript only when
|
|
614
|
+
Voice prompts include a bounded transcript only when ours-mcp reports success.
|
|
556
615
|
Failure or unavailability is explicit and preserves the private audio path as the
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
mismatch, and loads a crash-recovered body only through \`getHistoryItem\`.
|
|
563
|
-
The attachment crash journal contains only authenticated CID and wire routing
|
|
564
|
-
data; it never stores captions, filenames, paths, transcript text, or bytes.
|
|
565
|
-
Journaled read files resume through \`getFileInfo\` and \`fetchFile\`. A claimed
|
|
566
|
-
agent caption is loaded from history and rejoined before the group is admitted. Fleet
|
|
616
|
+
fallback. Run \`ours-mcp voice-status --json\` to inspect the host configuration.
|
|
617
|
+
A mode-0600 crash journal contains only authenticated CID and wire routing data;
|
|
618
|
+
it never stores captions, filenames, paths, transcript text, or bytes. Journaled
|
|
619
|
+
post-retrieval files resume selectively through \`save_file\`. A deferred agent
|
|
620
|
+
caption is replayed with its processed files before the group is admitted. Fleet
|
|
567
621
|
resolves one authenticated owner route before retrieving bytes, admits every file
|
|
568
622
|
before emitting the caption or any file, and sends every part to that same route.
|
|
569
623
|
Unknown correlated routes remain queued without retrieval and receive one bounded
|
|
@@ -666,10 +720,38 @@ escape hatch.
|
|
|
666
720
|
|
|
667
721
|
## Bounded worklogs, auth proxy, and model recovery
|
|
668
722
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
723
|
+
WORKLOG rotation is enabled by default with
|
|
724
|
+
\`worklog: { max_kb: 1024, keep_tail_kb: 256, max_archives: 12 }\`. Maps may
|
|
725
|
+
override individual values; \`worklog: false\` on a role or in defaults opts out.
|
|
726
|
+
Fleet rotates only at that role's launch/resume lifecycle boundary. Concurrent
|
|
727
|
+
changes defer rotation. The active file keeps a bounded UTF-8 tail and advances
|
|
728
|
+
to a line boundary when a complete line fits. If one logical line alone exceeds
|
|
729
|
+
the budget, its newest suffix remains and the rotation manifest records the
|
|
730
|
+
mid-line start and omitted byte count. The complete prior inode receives a
|
|
731
|
+
collision-safe UTC archive name, and
|
|
732
|
+
\`.worklog-rotation.json\` records restart provenance. \`max_archives\` bounds
|
|
733
|
+
recent archives beside WORKLOG.md; older complete archives move to
|
|
734
|
+
\`WORKLOG.archives/\` without deletion. All archives share the role's sensitive
|
|
735
|
+
state boundary. Fleet refuses a symlinked/non-regular live log or a symlinked
|
|
736
|
+
cold-archive boundary before replacing the live path and best-effort removes a
|
|
737
|
+
duplicate publication left by a detected failure while the original inode is
|
|
738
|
+
still available. The manifest records SHA-256 digests for the archive and live
|
|
739
|
+
bytes observed when it is written. These checks address ordinary path hazards,
|
|
740
|
+
not intentional path mutation by a malicious concurrent process with the same
|
|
741
|
+
Unix authority; that is outside the threat model and requires OS-level isolation.
|
|
742
|
+
|
|
743
|
+
ACP tool diffs are bounded before entering web conversation events. Existing
|
|
744
|
+
small before/after diffs are unchanged. Oversized whole-file snapshots are
|
|
745
|
+
reduced to the actual changed region plus path, operation, original byte counts,
|
|
746
|
+
digest, and omission metadata. Each retained side is a newest-content UTF-8 tail
|
|
747
|
+
of at most 64 KiB, advanced to a line boundary when a complete line fits. An
|
|
748
|
+
overlong single line keeps its newest suffix and explicitly records a mid-line
|
|
749
|
+
start. Paths retain at most a 4 KiB suffix with byte count, digest, and omitted
|
|
750
|
+
prefix metadata; the complete normalized update is capped at 320 KiB. A large
|
|
751
|
+
append therefore retains current appended content, not the historical prefix.
|
|
752
|
+
The live web-console transcript includes only the current runner generation and
|
|
753
|
+
excludes adapter session/load replay. Replayed events remain durable with
|
|
754
|
+
agent_replay provenance for diagnosis and recovery.
|
|
673
755
|
|
|
674
756
|
\`auth_proxy: { kind: anthropic, base_url, required, health_url }\` is Claude-only
|
|
675
757
|
and loopback-only. Fleet injects only ANTHROPIC_BASE_URL and doctor rejects
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { type AttachOursClientOptions, type OursClient } from '@ours.network/sdk/client';
|
|
2
1
|
import { type Exec } from './exec.js';
|
|
3
2
|
import type { YamlMode } from './config-yaml.js';
|
|
4
3
|
import { type FetchLike } from './monitor.js';
|
|
5
4
|
import type { PrereqReport } from './harness/types.js';
|
|
6
|
-
type DoctorDaemonClient = Pick<OursClient, 'version'>;
|
|
7
|
-
type AttachDoctorDaemon = (options: AttachOursClientOptions) => Promise<DoctorDaemonClient>;
|
|
8
5
|
/** Host-level + per-harness prerequisite report with actionable messages. */
|
|
9
6
|
export declare function doctor(opts?: {
|
|
10
7
|
harness?: string;
|
|
@@ -15,5 +12,4 @@ export declare function doctor(opts?: {
|
|
|
15
12
|
path?: string;
|
|
16
13
|
argv1?: string;
|
|
17
14
|
};
|
|
18
|
-
}, exec?: Exec, platform?: NodeJS.Platform, fetchImpl?: FetchLike
|
|
19
|
-
export {};
|
|
15
|
+
}, exec?: Exec, platform?: NodeJS.Platform, fetchImpl?: FetchLike): Promise<PrereqReport>;
|
package/dist/doctor.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { userInfo } from 'node:os';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { attachOursClient, } from '@ours.network/sdk/client';
|
|
4
3
|
import { realExec } from './exec.js';
|
|
5
4
|
import { isolationContextFor, loadConfig } from './config.js';
|
|
6
5
|
import { getAdapter, productionAdapters } from './harness/registry.js';
|
|
@@ -77,7 +76,7 @@ function installChecks(scan) {
|
|
|
77
76
|
];
|
|
78
77
|
}
|
|
79
78
|
/** Host-level + per-harness prerequisite report with actionable messages. */
|
|
80
|
-
export async function doctor(opts = {}, exec = realExec, platform = process.platform, fetchImpl = (u, i) => globalThis.fetch(u, i)
|
|
79
|
+
export async function doctor(opts = {}, exec = realExec, platform = process.platform, fetchImpl = (u, i) => globalThis.fetch(u, i)) {
|
|
81
80
|
const checks = [];
|
|
82
81
|
// First: which artifact is producing this report. Everything below is only as
|
|
83
82
|
// trustworthy as the answer, and a second install with the same semver and
|
|
@@ -113,22 +112,16 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
113
112
|
detail: tmux.code === 0 ? tmux.stdout.trim() : 'not found — apt install tmux / brew install tmux',
|
|
114
113
|
});
|
|
115
114
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
if (info.name !== 'ours' || typeof info.version !== 'string')
|
|
124
|
-
throw new Error('the selected endpoint did not return a valid ours daemon identity');
|
|
125
|
-
checks.push({ name: 'ours daemon', ok: true, detail: `running (${info.version})` });
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
115
|
+
const mcp = await exec('ours-mcp', ['--version']);
|
|
116
|
+
checks.push({
|
|
117
|
+
name: 'ours-mcp', ok: mcp.code === 0,
|
|
118
|
+
detail: mcp.code === 0 ? mcp.stdout.trim() : 'not found — npm i -g @ours.network/mcp',
|
|
119
|
+
});
|
|
120
|
+
if (mcp.code === 0) {
|
|
121
|
+
const st = await exec('ours-mcp', ['status']);
|
|
128
122
|
checks.push({
|
|
129
|
-
name: 'ours daemon', ok:
|
|
130
|
-
detail:
|
|
131
|
-
+ `[${error?.message ?? String(error)}]`,
|
|
123
|
+
name: 'ours-mcp daemon', ok: st.code === 0,
|
|
124
|
+
detail: st.code === 0 ? 'running' : 'not running — start it with: ours-mcp start',
|
|
132
125
|
});
|
|
133
126
|
}
|
|
134
127
|
if (platform === 'linux') {
|
|
@@ -295,7 +288,7 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
295
288
|
}
|
|
296
289
|
catch (e) {
|
|
297
290
|
detail = `unreachable on :${endpoint.port} — monitored roles run degraded until it is up ` +
|
|
298
|
-
`(start it: ours
|
|
291
|
+
`(start it: ours-mcp start) [${e?.message ?? e}]`;
|
|
299
292
|
}
|
|
300
293
|
checks.push({ name: checkName, ok, detail });
|
|
301
294
|
}
|
package/dist/fleet-proxy.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export interface ManagedFleetSpawnResult {
|
|
|
12
12
|
session: 'tmux' | 'acp';
|
|
13
13
|
model?: string;
|
|
14
14
|
monitor: Pick<MonitorConfig, 'mode' | 'interrupt'>;
|
|
15
|
+
/** Adapter-resolved portable policy and exact native runtime mode. */
|
|
16
|
+
permissionMode?: {
|
|
17
|
+
fleetMode: 'ask' | 'auto' | 'allow';
|
|
18
|
+
nativeMode: string;
|
|
19
|
+
};
|
|
15
20
|
inherited: string[];
|
|
16
21
|
creationActionId: string;
|
|
17
22
|
}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
export function resolveBundledAcpAgent(packageName, binName, fallbackCommand) {
|
|
6
6
|
try {
|
|
7
7
|
const manifestPath = require.resolve(`${packageName}/package.json`);
|
|
8
8
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
9
|
-
const
|
|
9
|
+
const declaredEntrypoint = typeof manifest.bin === 'string'
|
|
10
10
|
? manifest.bin
|
|
11
11
|
: manifest.bin?.[binName];
|
|
12
|
-
if (!
|
|
12
|
+
if (!declaredEntrypoint)
|
|
13
13
|
return { argv: [fallbackCommand], bundled: false };
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const packageRoot = realpathSync(dirname(manifestPath));
|
|
15
|
+
const entrypoint = realpathSync(resolve(packageRoot, declaredEntrypoint));
|
|
16
|
+
const entrypointFromRoot = relative(packageRoot, entrypoint);
|
|
17
|
+
if (entrypointFromRoot === '..'
|
|
18
|
+
|| entrypointFromRoot.startsWith(`..${sep}`)
|
|
19
|
+
|| isAbsolute(entrypointFromRoot)
|
|
20
|
+
|| !statSync(entrypoint).isFile())
|
|
16
21
|
return { argv: [fallbackCommand], bundled: false };
|
|
17
22
|
return {
|
|
18
23
|
argv: [process.execPath, entrypoint], bundled: true, manifestPath,
|
|
@@ -6,8 +6,99 @@ import { registerAdapter } from './registry.js';
|
|
|
6
6
|
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
7
7
|
import { harnessRuntimeDir } from '../isolation/policy.js';
|
|
8
8
|
import { bundledAcpAgent } from './acp-agent.js';
|
|
9
|
-
|
|
9
|
+
import { restoreLockedHarnessMarketplace } from '../harness-plugins.js';
|
|
10
|
+
const OPTION_KEYS = [
|
|
11
|
+
'plugins', 'mem_palace', 'mem_palace_midsession_autosave', 'permission_mode', 'effort',
|
|
12
|
+
'mcp_servers', 'mcp_servers_only',
|
|
13
|
+
];
|
|
10
14
|
const EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
15
|
+
/** `.mcp.json` server types. Absent means stdio, as the file format has it. */
|
|
16
|
+
const MCP_SERVER_TYPES = ['stdio', 'http', 'sse'];
|
|
17
|
+
/** A role that names its own ACP command runs a process fleet did not choose. */
|
|
18
|
+
const customAcpCommand = (role) => role.session === 'acp' && role.session_options?.acp?.command != null;
|
|
19
|
+
/**
|
|
20
|
+
* Does this server set include the ours connector?
|
|
21
|
+
*
|
|
22
|
+
* Load-bearing, and the reason it is a check rather than a doc line:
|
|
23
|
+
* `mcp_servers_only` maps to `--strict-mcp-config`, which ignores EVERY other MCP
|
|
24
|
+
* configuration — project `.mcp.json`, user settings, and **plugins**. On a
|
|
25
|
+
* normal install the ours connector arrives as a plugin
|
|
26
|
+
* (`~/.claude/plugins/.../plugin.json` declares `ours`), so a role that turns
|
|
27
|
+
* strict mode on without re-declaring it loses `send_message` and `get_messages`
|
|
28
|
+
* and cannot report that it has: a mute agent looks exactly like a quiet one.
|
|
29
|
+
*
|
|
30
|
+
* Matched on the command line rather than the server's NAME, because the name is
|
|
31
|
+
* the operator's to choose and would make this trivially satisfiable by writing
|
|
32
|
+
* `ours:` above the wrong command.
|
|
33
|
+
*/
|
|
34
|
+
const declaresOursConnector = (servers) => Object.values(servers).some(s => [s.command ?? '', ...(s.args ?? [])].some(part => /(^|[/\\])ours-mcp($|\s)|@ours\.network[/\\]mcp/.test(part)));
|
|
35
|
+
/** Shape-check `harness_options.mcp_servers` against `.mcp.json`'s own rules. */
|
|
36
|
+
function validateMcpServers(servers) {
|
|
37
|
+
if (servers == null)
|
|
38
|
+
return [];
|
|
39
|
+
const at = (k = '') => ({ path: `harness_options.mcp_servers${k}` });
|
|
40
|
+
if (typeof servers !== 'object' || Array.isArray(servers))
|
|
41
|
+
return [{ ...at(), message: 'must be a map of server name to server definition' }];
|
|
42
|
+
const entries = Object.entries(servers);
|
|
43
|
+
if (!entries.length)
|
|
44
|
+
return [{ ...at(), message: 'must declare at least one server, or be omitted' }];
|
|
45
|
+
const errors = [];
|
|
46
|
+
for (const [name, raw] of entries) {
|
|
47
|
+
const p = `.${name}`;
|
|
48
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
49
|
+
errors.push({ ...at(p), message: 'server name must be [A-Za-z0-9_-]' });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
53
|
+
errors.push({ ...at(p), message: 'must be a map' });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const s = raw;
|
|
57
|
+
if (s.type != null && !MCP_SERVER_TYPES.includes(s.type))
|
|
58
|
+
errors.push({ ...at(`${p}.type`), message: `must be one of: ${MCP_SERVER_TYPES.join(', ')}` });
|
|
59
|
+
const remote = s.type === 'http' || s.type === 'sse';
|
|
60
|
+
if (remote) {
|
|
61
|
+
if (typeof s.url !== 'string' || !s.url.trim())
|
|
62
|
+
errors.push({ ...at(`${p}.url`), message: `must be a non-empty URL for a ${s.type} server` });
|
|
63
|
+
if (s.command != null)
|
|
64
|
+
errors.push({ ...at(`${p}.command`), message: `must not be set for a ${s.type} server` });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (typeof s.command !== 'string' || !s.command.trim())
|
|
68
|
+
errors.push({ ...at(`${p}.command`), message: 'must be a non-empty command for a stdio server' });
|
|
69
|
+
if (s.args != null && (!Array.isArray(s.args) || s.args.some(a => typeof a !== 'string')))
|
|
70
|
+
errors.push({ ...at(`${p}.args`), message: 'must be an array of strings' });
|
|
71
|
+
if (s.url != null)
|
|
72
|
+
errors.push({ ...at(`${p}.url`), message: 'must not be set for a stdio server' });
|
|
73
|
+
}
|
|
74
|
+
for (const key of ['env', 'headers']) {
|
|
75
|
+
const v = s[key];
|
|
76
|
+
if (v == null)
|
|
77
|
+
continue;
|
|
78
|
+
if (typeof v !== 'object' || Array.isArray(v)
|
|
79
|
+
|| Object.values(v).some(x => typeof x !== 'string'))
|
|
80
|
+
errors.push({ ...at(`${p}.${key}`), message: 'must be a map of string to string' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return errors;
|
|
84
|
+
}
|
|
85
|
+
/** `harness_options.mcp_servers` in ACP's `session/new` array shape. */
|
|
86
|
+
function acpMcpServersFor(servers) {
|
|
87
|
+
if (!servers)
|
|
88
|
+
return [];
|
|
89
|
+
// `env` and `headers` are REQUIRED arrays in the protocol, so they are always
|
|
90
|
+
// sent — empty when the role declared none.
|
|
91
|
+
const pairs = (r) => Object.entries(r ?? {}).map(([name, value]) => ({ name, value }));
|
|
92
|
+
return Object.entries(servers).map(([name, s]) => {
|
|
93
|
+
if (s.type === 'http' || s.type === 'sse')
|
|
94
|
+
return { name, type: s.type, url: s.url, headers: pairs(s.headers) };
|
|
95
|
+
// Stdio carries NO `type` field: ACP's stdio variant is the one without it,
|
|
96
|
+
// and the bundled agent keys on exactly that (claude-agent-acp
|
|
97
|
+
// acp-agent.js:4058, `!("type" in server)`), so sending `type: 'stdio'`
|
|
98
|
+
// would drop the server on the floor.
|
|
99
|
+
return { name, command: s.command, args: s.args ?? [], env: pairs(s.env) };
|
|
100
|
+
});
|
|
101
|
+
}
|
|
11
102
|
/** Claude Code's accepted --permission-mode values. */
|
|
12
103
|
const PERMISSION_MODES = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'];
|
|
13
104
|
/**
|
|
@@ -127,11 +218,8 @@ export async function pretrust(dir, deps = {}) {
|
|
|
127
218
|
* it must be the Monitor TOOL, not a background Bash task (which never wakes the
|
|
128
219
|
* agent on output → an armed-looking but deaf monitor).
|
|
129
220
|
*/
|
|
130
|
-
const shellQuote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
131
|
-
const watchCommand = (id) => 'ours api watch-notifications --input '
|
|
132
|
-
+ shellQuote(JSON.stringify({ identity: id, since: 'tip' })) + ' --json';
|
|
133
221
|
const armMonitor = (id) => 'arm a **persistent Monitor** (the Monitor TOOL — NOT a background Bash command; a ' +
|
|
134
|
-
`background Bash task never wakes you on output) running
|
|
222
|
+
`background Bash task never wakes you on output) running \`ours-mcp watch "${id}"\` ` +
|
|
135
223
|
'so inbound ours mail wakes you';
|
|
136
224
|
export function makeClaudeCodeAdapter(exec = realExec) {
|
|
137
225
|
return {
|
|
@@ -149,7 +237,7 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
149
237
|
}],
|
|
150
238
|
};
|
|
151
239
|
},
|
|
152
|
-
validateOptions(opts) {
|
|
240
|
+
validateOptions(opts, role) {
|
|
153
241
|
if (opts == null)
|
|
154
242
|
return [];
|
|
155
243
|
if (typeof opts !== 'object' || Array.isArray(opts))
|
|
@@ -157,12 +245,50 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
157
245
|
const errors = Object.keys(opts)
|
|
158
246
|
.filter(k => !OPTION_KEYS.includes(k))
|
|
159
247
|
.map(k => ({ path: `harness_options.${k}`, message: `unknown option; allowed: ${OPTION_KEYS.join(', ')}` }));
|
|
160
|
-
const
|
|
248
|
+
const o = opts;
|
|
249
|
+
const effort = o.effort;
|
|
161
250
|
if (effort != null && !EFFORT_LEVELS.includes(effort))
|
|
162
251
|
errors.push({ path: 'harness_options.effort', message: `must be one of: ${EFFORT_LEVELS.join(', ')}` });
|
|
252
|
+
if (o.mcp_servers_only != null && typeof o.mcp_servers_only !== 'boolean')
|
|
253
|
+
errors.push({ path: 'harness_options.mcp_servers_only', message: 'must be a boolean' });
|
|
254
|
+
errors.push(...validateMcpServers(o.mcp_servers));
|
|
255
|
+
if (o.mcp_servers_only === true && !o.mcp_servers)
|
|
256
|
+
errors.push({
|
|
257
|
+
path: 'harness_options.mcp_servers_only',
|
|
258
|
+
message: 'requires harness_options.mcp_servers; on its own it would leave the role with no MCP servers at all',
|
|
259
|
+
});
|
|
260
|
+
// The muteness gate. Only when the declared set is otherwise well-formed —
|
|
261
|
+
// a shape error already told the operator to look here.
|
|
262
|
+
if (o.mcp_servers_only === true && o.mcp_servers && errors.length === 0
|
|
263
|
+
&& !declaresOursConnector(o.mcp_servers))
|
|
264
|
+
errors.push({
|
|
265
|
+
path: 'harness_options.mcp_servers',
|
|
266
|
+
message: 'mcp_servers_only ignores every other MCP configuration, INCLUDING plugins — and the ours '
|
|
267
|
+
+ 'connector is normally a plugin, so this role would have no send_message or get_messages and no way '
|
|
268
|
+
+ 'to report that. Declare it explicitly, e.g. ours: { command: ours-mcp, args: [proxy] }',
|
|
269
|
+
});
|
|
270
|
+
// Session-aware refusals. Both options reach an ACP session through the
|
|
271
|
+
// bundled agent's `_meta` vocabulary, so a role that launches a DIFFERENT
|
|
272
|
+
// ACP agent cannot be promised either one. Refuse rather than send it and
|
|
273
|
+
// hope: silently dropping the config is the defect being fixed here.
|
|
274
|
+
if (role && customAcpCommand(role)) {
|
|
275
|
+
for (const key of ['plugins', 'mcp_servers', 'mcp_servers_only']) {
|
|
276
|
+
if (o[key] == null)
|
|
277
|
+
continue;
|
|
278
|
+
errors.push({
|
|
279
|
+
path: `harness_options.${key}`,
|
|
280
|
+
message: 'cannot be honoured with session_options.acp.command: it is delivered through the bundled '
|
|
281
|
+
+ 'Claude ACP agent\'s _meta vocabulary, which another agent has no reason to read. Drop the '
|
|
282
|
+
+ 'custom ACP command, or drop this option',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
163
286
|
return errors;
|
|
164
287
|
},
|
|
165
288
|
async prepareSession(role, dirs) {
|
|
289
|
+
// Re-materialize only from the persisted exact lock. Ordinary launches
|
|
290
|
+
// never resolve npm tags or run an installer.
|
|
291
|
+
restoreLockedHarnessMarketplace('claude-code', role.harnessPluginChannel);
|
|
166
292
|
// Pre-trust stays a HOST-side step: inside the sandbox ~/.claude.json is
|
|
167
293
|
// read-only, and it is the fleet's job to trust the role's dirs, not the
|
|
168
294
|
// agent's (5.1, 6.1).
|
|
@@ -178,6 +304,15 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
178
304
|
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: String(autocompactPct(role)),
|
|
179
305
|
MEMPALACE_HOOKS_AUTO_SAVE: 'false',
|
|
180
306
|
MEMPALACE_MIDSESSION_AUTOSAVE: o.mem_palace_midsession_autosave ? 'true' : 'false',
|
|
307
|
+
// The role's identity, for the ours connector to bind at startup instead of
|
|
308
|
+
// the briefing telling the MODEL to call choose_identity. Both launches
|
|
309
|
+
// return `prep.env`, so this one line covers tmux and ACP alike.
|
|
310
|
+
//
|
|
311
|
+
// The bind the connector performs is PLAIN and fail-closed: it can never
|
|
312
|
+
// evict a live session, and a role whose identity does not exist yet simply
|
|
313
|
+
// boots unbound and falls through to the briefing's create-if-missing step.
|
|
314
|
+
// Nothing here may ever grow a force flag.
|
|
315
|
+
OURS_BIND_IDENTITY: role.identity,
|
|
181
316
|
};
|
|
182
317
|
if (!memPalace)
|
|
183
318
|
env.MEMPALACE_DISABLED = 'true';
|
|
@@ -188,12 +323,29 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
188
323
|
if (role.isolation)
|
|
189
324
|
mkdirSync(harnessRuntimeDir(dirs.stateDir, 'claude'), { recursive: true });
|
|
190
325
|
const argv = [];
|
|
326
|
+
let settingsOverlay;
|
|
191
327
|
if (Object.keys(enabledPlugins).length) {
|
|
192
|
-
|
|
193
|
-
writeFileSync(
|
|
194
|
-
argv.push('--settings',
|
|
328
|
+
settingsOverlay = join(dirs.stateDir, '.settings-overlay.json');
|
|
329
|
+
writeFileSync(settingsOverlay, JSON.stringify({ enabledPlugins }, null, 2));
|
|
330
|
+
argv.push('--settings', settingsOverlay);
|
|
195
331
|
}
|
|
196
|
-
|
|
332
|
+
// `harness_options.mcp_servers` — the tmux delivery. `--mcp-config` ADDS the
|
|
333
|
+
// file's servers; `--strict-mcp-config` is what makes the set exclusive, and
|
|
334
|
+
// it is opt-in per role because it drops everything else the user has,
|
|
335
|
+
// plugins included (see `declaresOursConnector`).
|
|
336
|
+
let mcpConfigFile;
|
|
337
|
+
if (o.mcp_servers) {
|
|
338
|
+
mcpConfigFile = join(dirs.stateDir, '.mcp-config.json');
|
|
339
|
+
writeFileSync(mcpConfigFile, JSON.stringify({ mcpServers: o.mcp_servers }, null, 2), { mode: 0o600 });
|
|
340
|
+
argv.push('--mcp-config', mcpConfigFile);
|
|
341
|
+
if (o.mcp_servers_only === true)
|
|
342
|
+
argv.push('--strict-mcp-config');
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
argv, env,
|
|
346
|
+
...(settingsOverlay ? { settingsOverlay } : {}),
|
|
347
|
+
...(mcpConfigFile ? { mcpConfigFile } : {}),
|
|
348
|
+
};
|
|
197
349
|
},
|
|
198
350
|
buildLaunch(role, mode, s, prep) {
|
|
199
351
|
const stateDir = roleStateDir(role);
|
|
@@ -223,6 +375,46 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
223
375
|
acpPermissionModeId(role) {
|
|
224
376
|
return permissionMode(role);
|
|
225
377
|
},
|
|
378
|
+
/**
|
|
379
|
+
* Deliver, over ACP, the two things the tmux launch delivers as flags.
|
|
380
|
+
*
|
|
381
|
+
* `buildAcpLaunch` builds its own argv and cannot carry `prep.argv`: the
|
|
382
|
+
* process it launches is the ACP agent, not `claude`, and it takes none of
|
|
383
|
+
* claude's flags. That is why `harness_options.plugins` did nothing at all on
|
|
384
|
+
* an ACP role — the overlay was written and then dropped, and the mem-palace
|
|
385
|
+
* toggle rode `prep.env` and survived, so the failure was silent AND
|
|
386
|
+
* selective.
|
|
387
|
+
*
|
|
388
|
+
* `_meta.claudeCode.options` is the bundled agent's own passthrough into the
|
|
389
|
+
* Claude Agent SDK (@agentclientprotocol/claude-agent-acp, acp-agent.js:4092
|
|
390
|
+
* → the `options` object at :4144). `settings` takes the same overlay path
|
|
391
|
+
* `--settings` takes; `strictMcpConfig` is the SDK's spelling of
|
|
392
|
+
* `--strict-mcp-config`. Both are spread BEFORE the fields the agent forces,
|
|
393
|
+
* so neither is overwritten.
|
|
394
|
+
*
|
|
395
|
+
* ⚠ RETURNS NOTHING FOR A ROLE THAT NAMES ITS OWN ACP COMMAND. That process
|
|
396
|
+
* is not the bundled agent and has no reason to read this vocabulary; sending
|
|
397
|
+
* it anyway would be the silent drop again, one level down. `validateOptions`
|
|
398
|
+
* refuses those roles instead.
|
|
399
|
+
*/
|
|
400
|
+
acpSessionMeta(role, prep) {
|
|
401
|
+
if (customAcpCommand(role))
|
|
402
|
+
return undefined;
|
|
403
|
+
const options = {};
|
|
404
|
+
if (prep.settingsOverlay)
|
|
405
|
+
options.settings = prep.settingsOverlay;
|
|
406
|
+
if (role.harness_options?.mcp_servers_only === true)
|
|
407
|
+
options.strictMcpConfig = true;
|
|
408
|
+
return Object.keys(options).length ? { claudeCode: { options } } : undefined;
|
|
409
|
+
},
|
|
410
|
+
/**
|
|
411
|
+
* The declared servers, in ACP's array shape. Sent on `session/new` and on
|
|
412
|
+
* resume/load, because the SDK builds its server set once per session and a
|
|
413
|
+
* resumed session that dropped them would quietly lose its tools.
|
|
414
|
+
*/
|
|
415
|
+
acpMcpServers(role) {
|
|
416
|
+
return acpMcpServersFor(role.harness_options?.mcp_servers);
|
|
417
|
+
},
|
|
226
418
|
isolationPaths(_role, _dirs) {
|
|
227
419
|
const claudeHome = join(home(), '.claude');
|
|
228
420
|
return {
|
|
@@ -285,6 +477,7 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
285
477
|
currentIdentityTool: 'current_identity',
|
|
286
478
|
sendTool: 'send_message',
|
|
287
479
|
getMessagesTool: 'get_messages',
|
|
480
|
+
watchCommand: id => `ours-mcp watch "${id}"`,
|
|
288
481
|
monitorInstruction: id => {
|
|
289
482
|
const m = armMonitor(id);
|
|
290
483
|
return `${m.charAt(0).toUpperCase()}${m.slice(1)}.`;
|
package/dist/harness/codex.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type Exec } from '../exec.js';
|
|
2
|
-
import type { HarnessAdapter, UnattendedCapability } from './types.js';
|
|
2
|
+
import type { AcpLaunch, HarnessAdapter, UnattendedCapability } from './types.js';
|
|
3
|
+
import { type AcpAgentResolution } from './acp-agent.js';
|
|
3
4
|
/**
|
|
4
5
|
* What an unattended role can actually do under Codex's native settings.
|
|
5
6
|
* `on-request` and `untrusted` stop to ask, and with no console attached that
|
|
6
7
|
* request is refused rather than answered — so the role can only read.
|
|
7
8
|
*/
|
|
8
9
|
export declare function codexCapabilities(approval: string, sandbox: string): UnattendedCapability[];
|
|
10
|
+
/** Bind launch argv and metadata provenance to one already-completed resolution. */
|
|
11
|
+
export declare function codexAcpLaunchForResolution(resolution: AcpAgentResolution): Pick<AcpLaunch, 'argv' | 'permissionMetadataSource'>;
|
|
9
12
|
export declare function makeCodexAdapter(exec?: Exec): HarnessAdapter;
|
|
10
13
|
export declare const codexAdapter: HarnessAdapter;
|