@ours.network/fleet 0.19.0-nightly.9 → 1.0.1
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 +66 -11
- package/dist/application/fleet-query-service.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/briefing.js +103 -10
- package/dist/build-info.json +5 -5
- package/dist/cli.js +41 -11
- package/dist/config.d.ts +21 -7
- package/dist/config.js +32 -18
- package/dist/creation.d.ts +1 -1
- package/dist/docs.d.ts +2 -2
- package/dist/docs.js +117 -24
- package/dist/doctor.js +10 -10
- package/dist/duration.js +1 -1
- package/dist/fleet-proxy.d.ts +5 -0
- package/dist/harness/acp-agent.js +11 -6
- package/dist/harness/claude-code.js +201 -8
- package/dist/harness/codex.d.ts +4 -1
- package/dist/harness/codex.js +72 -13
- package/dist/harness/types.d.ts +57 -6
- package/dist/isolation/bubblewrap.d.ts +4 -4
- package/dist/isolation/bubblewrap.js +4 -4
- package/dist/isolation/policy.d.ts +1 -1
- package/dist/isolation/policy.js +2 -2
- package/dist/isolation/registry.d.ts +3 -3
- package/dist/isolation/registry.js +5 -5
- package/dist/isolation/resources.d.ts +1 -1
- package/dist/isolation/resources.js +1 -1
- package/dist/isolation/types.d.ts +3 -3
- 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.d.ts +2 -2
- package/dist/monitor.js +4 -4
- package/dist/ops.d.ts +2 -2
- package/dist/ops.js +9 -9
- package/dist/owner-channel/channel.d.ts +17 -0
- package/dist/owner-channel/channel.js +104 -23
- package/dist/owner-channel/commands.d.ts +9 -0
- package/dist/owner-channel/commands.js +226 -99
- package/dist/owner-channel/notices.d.ts +7 -0
- package/dist/owner-channel/notices.js +9 -0
- package/dist/rooms-tasks/cli.js +731 -219
- package/dist/rooms-tasks/close.d.ts +35 -0
- package/dist/rooms-tasks/close.js +190 -0
- package/dist/rooms-tasks/cowork-adapter.d.ts +24 -0
- package/dist/rooms-tasks/cowork-adapter.js +120 -0
- package/dist/rooms-tasks/external-worker.d.ts +2 -0
- package/dist/rooms-tasks/external-worker.js +40 -0
- package/dist/rooms-tasks/index.d.ts +2 -0
- package/dist/rooms-tasks/index.js +2 -0
- package/dist/rooms-tasks/markdown.d.ts +49 -0
- package/dist/rooms-tasks/markdown.js +206 -0
- package/dist/rooms-tasks/member-startup.d.ts +51 -0
- package/dist/rooms-tasks/member-startup.js +151 -0
- package/dist/rooms-tasks/provision.d.ts +8 -0
- package/dist/rooms-tasks/provision.js +418 -97
- package/dist/rooms-tasks/room-state.d.ts +13 -1
- package/dist/rooms-tasks/room-state.js +149 -1
- package/dist/rooms-tasks/task-state.d.ts +13 -2
- package/dist/rooms-tasks/task-state.js +131 -3
- package/dist/rooms-tasks/terminal.d.ts +23 -0
- package/dist/rooms-tasks/terminal.js +56 -0
- package/dist/rooms-tasks/types.d.ts +108 -2
- package/dist/runner.d.ts +48 -0
- package/dist/runner.js +252 -100
- package/dist/session/acp.d.ts +106 -2
- package/dist/session/acp.js +217 -14
- package/dist/session/activity.d.ts +31 -0
- package/dist/session/activity.js +48 -0
- package/dist/session/control.d.ts +1 -1
- package/dist/session/conversation-normalizer.d.ts +7 -1
- package/dist/session/conversation-normalizer.js +154 -11
- package/dist/session/conversation-store.js +1 -1
- package/dist/session/conversation-types.d.ts +25 -6
- package/dist/session/types.d.ts +35 -0
- package/dist/spawn.d.ts +4 -2
- package/dist/spawn.js +39 -26
- package/dist/supervisor/launchd.d.ts +2 -2
- package/dist/supervisor/launchd.js +4 -4
- package/dist/supervisor/systemd.js +4 -4
- package/dist/supervisor/types.d.ts +1 -1
- package/dist/temp-lifecycle.d.ts +14 -2
- package/dist/temp-lifecycle.js +90 -0
- package/dist/watchdog/alerts.d.ts +2 -2
- package/dist/watchdog/alerts.js +2 -2
- package/dist/watchdog/briefing.d.ts +5 -5
- package/dist/watchdog/briefing.js +13 -6
- package/dist/watchdog/config.js +1 -1
- package/dist/watchdog/query.d.ts +2 -2
- package/dist/watchdog/query.js +2 -2
- package/dist/watchdog/report.js +2 -2
- package/dist/watchdog/run.d.ts +2 -2
- package/dist/watchdog/run.js +6 -6
- package/dist/watchdog/scheduler.d.ts +7 -7
- package/dist/watchdog/scheduler.js +10 -10
- package/dist/watchdog/service.d.ts +4 -4
- package/dist/watchdog/service.js +5 -6
- package/dist/watchdog/store.d.ts +4 -4
- package/dist/watchdog/store.js +4 -4
- package/dist/web/runtime.js +1 -1
- package/dist/web/server.js +1 -1
- package/dist/web/topology-promote.js +1 -1
- 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 -1
package/dist/duration.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Parse `30s | 10m | 2h` duration strings to milliseconds
|
|
1
|
+
/** Parse `30s | 10m | 2h` duration strings to milliseconds. */
|
|
2
2
|
const UNIT_MS = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
3
3
|
const RE = /^(\d+)([smhd])$/;
|
|
4
4
|
export function parseDuration(text, opts = {}) {
|
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,98 @@ 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
|
-
const OPTION_KEYS = [
|
|
9
|
+
const OPTION_KEYS = [
|
|
10
|
+
'plugins', 'mem_palace', 'mem_palace_midsession_autosave', 'permission_mode', 'effort',
|
|
11
|
+
'mcp_servers', 'mcp_servers_only',
|
|
12
|
+
];
|
|
10
13
|
const EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
14
|
+
/** `.mcp.json` server types. Absent means stdio, as the file format has it. */
|
|
15
|
+
const MCP_SERVER_TYPES = ['stdio', 'http', 'sse'];
|
|
16
|
+
/** A role that names its own ACP command runs a process fleet did not choose. */
|
|
17
|
+
const customAcpCommand = (role) => role.session === 'acp' && role.session_options?.acp?.command != null;
|
|
18
|
+
/**
|
|
19
|
+
* Does this server set include the ours connector?
|
|
20
|
+
*
|
|
21
|
+
* Load-bearing, and the reason it is a check rather than a doc line:
|
|
22
|
+
* `mcp_servers_only` maps to `--strict-mcp-config`, which ignores EVERY other MCP
|
|
23
|
+
* configuration — project `.mcp.json`, user settings, and **plugins**. On a
|
|
24
|
+
* normal install the ours connector arrives as a plugin
|
|
25
|
+
* (`~/.claude/plugins/.../plugin.json` declares `ours`), so a role that turns
|
|
26
|
+
* strict mode on without re-declaring it loses `send_message` and `get_messages`
|
|
27
|
+
* and cannot report that it has: a mute agent looks exactly like a quiet one.
|
|
28
|
+
*
|
|
29
|
+
* Matched on the command line rather than the server's NAME, because the name is
|
|
30
|
+
* the operator's to choose and would make this trivially satisfiable by writing
|
|
31
|
+
* `ours:` above the wrong command.
|
|
32
|
+
*/
|
|
33
|
+
const declaresOursConnector = (servers) => Object.values(servers).some(s => [s.command ?? '', ...(s.args ?? [])].some(part => /(^|[/\\])ours-mcp($|\s)|@ours\.network[/\\]mcp/.test(part)));
|
|
34
|
+
/** Shape-check `harness_options.mcp_servers` against `.mcp.json`'s own rules. */
|
|
35
|
+
function validateMcpServers(servers) {
|
|
36
|
+
if (servers == null)
|
|
37
|
+
return [];
|
|
38
|
+
const at = (k = '') => ({ path: `harness_options.mcp_servers${k}` });
|
|
39
|
+
if (typeof servers !== 'object' || Array.isArray(servers))
|
|
40
|
+
return [{ ...at(), message: 'must be a map of server name to server definition' }];
|
|
41
|
+
const entries = Object.entries(servers);
|
|
42
|
+
if (!entries.length)
|
|
43
|
+
return [{ ...at(), message: 'must declare at least one server, or be omitted' }];
|
|
44
|
+
const errors = [];
|
|
45
|
+
for (const [name, raw] of entries) {
|
|
46
|
+
const p = `.${name}`;
|
|
47
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
48
|
+
errors.push({ ...at(p), message: 'server name must be [A-Za-z0-9_-]' });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
52
|
+
errors.push({ ...at(p), message: 'must be a map' });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const s = raw;
|
|
56
|
+
if (s.type != null && !MCP_SERVER_TYPES.includes(s.type))
|
|
57
|
+
errors.push({ ...at(`${p}.type`), message: `must be one of: ${MCP_SERVER_TYPES.join(', ')}` });
|
|
58
|
+
const remote = s.type === 'http' || s.type === 'sse';
|
|
59
|
+
if (remote) {
|
|
60
|
+
if (typeof s.url !== 'string' || !s.url.trim())
|
|
61
|
+
errors.push({ ...at(`${p}.url`), message: `must be a non-empty URL for a ${s.type} server` });
|
|
62
|
+
if (s.command != null)
|
|
63
|
+
errors.push({ ...at(`${p}.command`), message: `must not be set for a ${s.type} server` });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (typeof s.command !== 'string' || !s.command.trim())
|
|
67
|
+
errors.push({ ...at(`${p}.command`), message: 'must be a non-empty command for a stdio server' });
|
|
68
|
+
if (s.args != null && (!Array.isArray(s.args) || s.args.some(a => typeof a !== 'string')))
|
|
69
|
+
errors.push({ ...at(`${p}.args`), message: 'must be an array of strings' });
|
|
70
|
+
if (s.url != null)
|
|
71
|
+
errors.push({ ...at(`${p}.url`), message: 'must not be set for a stdio server' });
|
|
72
|
+
}
|
|
73
|
+
for (const key of ['env', 'headers']) {
|
|
74
|
+
const v = s[key];
|
|
75
|
+
if (v == null)
|
|
76
|
+
continue;
|
|
77
|
+
if (typeof v !== 'object' || Array.isArray(v)
|
|
78
|
+
|| Object.values(v).some(x => typeof x !== 'string'))
|
|
79
|
+
errors.push({ ...at(`${p}.${key}`), message: 'must be a map of string to string' });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return errors;
|
|
83
|
+
}
|
|
84
|
+
/** `harness_options.mcp_servers` in ACP's `session/new` array shape. */
|
|
85
|
+
function acpMcpServersFor(servers) {
|
|
86
|
+
if (!servers)
|
|
87
|
+
return [];
|
|
88
|
+
// `env` and `headers` are REQUIRED arrays in the protocol, so they are always
|
|
89
|
+
// sent — empty when the role declared none.
|
|
90
|
+
const pairs = (r) => Object.entries(r ?? {}).map(([name, value]) => ({ name, value }));
|
|
91
|
+
return Object.entries(servers).map(([name, s]) => {
|
|
92
|
+
if (s.type === 'http' || s.type === 'sse')
|
|
93
|
+
return { name, type: s.type, url: s.url, headers: pairs(s.headers) };
|
|
94
|
+
// Stdio carries NO `type` field: ACP's stdio variant is the one without it,
|
|
95
|
+
// and the bundled agent keys on exactly that (claude-agent-acp
|
|
96
|
+
// acp-agent.js:4058, `!("type" in server)`), so sending `type: 'stdio'`
|
|
97
|
+
// would drop the server on the floor.
|
|
98
|
+
return { name, command: s.command, args: s.args ?? [], env: pairs(s.env) };
|
|
99
|
+
});
|
|
100
|
+
}
|
|
11
101
|
/** Claude Code's accepted --permission-mode values. */
|
|
12
102
|
const PERMISSION_MODES = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'];
|
|
13
103
|
/**
|
|
@@ -149,7 +239,7 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
149
239
|
}],
|
|
150
240
|
};
|
|
151
241
|
},
|
|
152
|
-
validateOptions(opts) {
|
|
242
|
+
validateOptions(opts, role) {
|
|
153
243
|
if (opts == null)
|
|
154
244
|
return [];
|
|
155
245
|
if (typeof opts !== 'object' || Array.isArray(opts))
|
|
@@ -157,9 +247,44 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
157
247
|
const errors = Object.keys(opts)
|
|
158
248
|
.filter(k => !OPTION_KEYS.includes(k))
|
|
159
249
|
.map(k => ({ path: `harness_options.${k}`, message: `unknown option; allowed: ${OPTION_KEYS.join(', ')}` }));
|
|
160
|
-
const
|
|
250
|
+
const o = opts;
|
|
251
|
+
const effort = o.effort;
|
|
161
252
|
if (effort != null && !EFFORT_LEVELS.includes(effort))
|
|
162
253
|
errors.push({ path: 'harness_options.effort', message: `must be one of: ${EFFORT_LEVELS.join(', ')}` });
|
|
254
|
+
if (o.mcp_servers_only != null && typeof o.mcp_servers_only !== 'boolean')
|
|
255
|
+
errors.push({ path: 'harness_options.mcp_servers_only', message: 'must be a boolean' });
|
|
256
|
+
errors.push(...validateMcpServers(o.mcp_servers));
|
|
257
|
+
if (o.mcp_servers_only === true && !o.mcp_servers)
|
|
258
|
+
errors.push({
|
|
259
|
+
path: 'harness_options.mcp_servers_only',
|
|
260
|
+
message: 'requires harness_options.mcp_servers; on its own it would leave the role with no MCP servers at all',
|
|
261
|
+
});
|
|
262
|
+
// The muteness gate. Only when the declared set is otherwise well-formed —
|
|
263
|
+
// a shape error already told the operator to look here.
|
|
264
|
+
if (o.mcp_servers_only === true && o.mcp_servers && errors.length === 0
|
|
265
|
+
&& !declaresOursConnector(o.mcp_servers))
|
|
266
|
+
errors.push({
|
|
267
|
+
path: 'harness_options.mcp_servers',
|
|
268
|
+
message: 'mcp_servers_only ignores every other MCP configuration, INCLUDING plugins — and the ours '
|
|
269
|
+
+ 'connector is normally a plugin, so this role would have no send_message or get_messages and no way '
|
|
270
|
+
+ 'to report that. Declare it explicitly, e.g. ours: { command: ours-mcp, args: [proxy] }',
|
|
271
|
+
});
|
|
272
|
+
// Session-aware refusals. Both options reach an ACP session through the
|
|
273
|
+
// bundled agent's `_meta` vocabulary, so a role that launches a DIFFERENT
|
|
274
|
+
// ACP agent cannot be promised either one. Refuse rather than send it and
|
|
275
|
+
// hope: silently dropping the config is the defect being fixed here.
|
|
276
|
+
if (role && customAcpCommand(role)) {
|
|
277
|
+
for (const key of ['plugins', 'mcp_servers', 'mcp_servers_only']) {
|
|
278
|
+
if (o[key] == null)
|
|
279
|
+
continue;
|
|
280
|
+
errors.push({
|
|
281
|
+
path: `harness_options.${key}`,
|
|
282
|
+
message: 'cannot be honoured with session_options.acp.command: it is delivered through the bundled '
|
|
283
|
+
+ 'Claude ACP agent\'s _meta vocabulary, which another agent has no reason to read. Drop the '
|
|
284
|
+
+ 'custom ACP command, or drop this option',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
163
288
|
return errors;
|
|
164
289
|
},
|
|
165
290
|
async prepareSession(role, dirs) {
|
|
@@ -178,22 +303,48 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
178
303
|
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: String(autocompactPct(role)),
|
|
179
304
|
MEMPALACE_HOOKS_AUTO_SAVE: 'false',
|
|
180
305
|
MEMPALACE_MIDSESSION_AUTOSAVE: o.mem_palace_midsession_autosave ? 'true' : 'false',
|
|
306
|
+
// The role's identity, for the ours connector to bind at startup instead of
|
|
307
|
+
// the briefing telling the MODEL to call choose_identity. Both launches
|
|
308
|
+
// return `prep.env`, so this one line covers tmux and ACP alike.
|
|
309
|
+
//
|
|
310
|
+
// The bind the connector performs is PLAIN and fail-closed: it can never
|
|
311
|
+
// evict a live session, and a role whose identity does not exist yet simply
|
|
312
|
+
// boots unbound and falls through to the briefing's create-if-missing step.
|
|
313
|
+
// Nothing here may ever grow a force flag.
|
|
314
|
+
OURS_BIND_IDENTITY: role.identity,
|
|
181
315
|
};
|
|
182
316
|
if (!memPalace)
|
|
183
317
|
env.MEMPALACE_DISABLED = 'true';
|
|
184
|
-
// Per-role harness runtime home
|
|
318
|
+
// Per-role harness runtime home. Created before sandbox entry so the
|
|
185
319
|
// bind has something to mount; harmless for un-isolated roles.
|
|
186
320
|
// Only a role that declares `isolation:` gets a sandbox, and only a
|
|
187
321
|
// sandbox needs this directory to exist before entry.
|
|
188
322
|
if (role.isolation)
|
|
189
323
|
mkdirSync(harnessRuntimeDir(dirs.stateDir, 'claude'), { recursive: true });
|
|
190
324
|
const argv = [];
|
|
325
|
+
let settingsOverlay;
|
|
191
326
|
if (Object.keys(enabledPlugins).length) {
|
|
192
|
-
|
|
193
|
-
writeFileSync(
|
|
194
|
-
argv.push('--settings',
|
|
327
|
+
settingsOverlay = join(dirs.stateDir, '.settings-overlay.json');
|
|
328
|
+
writeFileSync(settingsOverlay, JSON.stringify({ enabledPlugins }, null, 2));
|
|
329
|
+
argv.push('--settings', settingsOverlay);
|
|
195
330
|
}
|
|
196
|
-
|
|
331
|
+
// `harness_options.mcp_servers` — the tmux delivery. `--mcp-config` ADDS the
|
|
332
|
+
// file's servers; `--strict-mcp-config` is what makes the set exclusive, and
|
|
333
|
+
// it is opt-in per role because it drops everything else the user has,
|
|
334
|
+
// plugins included (see `declaresOursConnector`).
|
|
335
|
+
let mcpConfigFile;
|
|
336
|
+
if (o.mcp_servers) {
|
|
337
|
+
mcpConfigFile = join(dirs.stateDir, '.mcp-config.json');
|
|
338
|
+
writeFileSync(mcpConfigFile, JSON.stringify({ mcpServers: o.mcp_servers }, null, 2), { mode: 0o600 });
|
|
339
|
+
argv.push('--mcp-config', mcpConfigFile);
|
|
340
|
+
if (o.mcp_servers_only === true)
|
|
341
|
+
argv.push('--strict-mcp-config');
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
argv, env,
|
|
345
|
+
...(settingsOverlay ? { settingsOverlay } : {}),
|
|
346
|
+
...(mcpConfigFile ? { mcpConfigFile } : {}),
|
|
347
|
+
};
|
|
197
348
|
},
|
|
198
349
|
buildLaunch(role, mode, s, prep) {
|
|
199
350
|
const stateDir = roleStateDir(role);
|
|
@@ -223,6 +374,46 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
223
374
|
acpPermissionModeId(role) {
|
|
224
375
|
return permissionMode(role);
|
|
225
376
|
},
|
|
377
|
+
/**
|
|
378
|
+
* Deliver, over ACP, the two things the tmux launch delivers as flags.
|
|
379
|
+
*
|
|
380
|
+
* `buildAcpLaunch` builds its own argv and cannot carry `prep.argv`: the
|
|
381
|
+
* process it launches is the ACP agent, not `claude`, and it takes none of
|
|
382
|
+
* claude's flags. That is why `harness_options.plugins` did nothing at all on
|
|
383
|
+
* an ACP role — the overlay was written and then dropped, and the mem-palace
|
|
384
|
+
* toggle rode `prep.env` and survived, so the failure was silent AND
|
|
385
|
+
* selective.
|
|
386
|
+
*
|
|
387
|
+
* `_meta.claudeCode.options` is the bundled agent's own passthrough into the
|
|
388
|
+
* Claude Agent SDK (@agentclientprotocol/claude-agent-acp, acp-agent.js:4092
|
|
389
|
+
* → the `options` object at :4144). `settings` takes the same overlay path
|
|
390
|
+
* `--settings` takes; `strictMcpConfig` is the SDK's spelling of
|
|
391
|
+
* `--strict-mcp-config`. Both are spread BEFORE the fields the agent forces,
|
|
392
|
+
* so neither is overwritten.
|
|
393
|
+
*
|
|
394
|
+
* ⚠ RETURNS NOTHING FOR A ROLE THAT NAMES ITS OWN ACP COMMAND. That process
|
|
395
|
+
* is not the bundled agent and has no reason to read this vocabulary; sending
|
|
396
|
+
* it anyway would be the silent drop again, one level down. `validateOptions`
|
|
397
|
+
* refuses those roles instead.
|
|
398
|
+
*/
|
|
399
|
+
acpSessionMeta(role, prep) {
|
|
400
|
+
if (customAcpCommand(role))
|
|
401
|
+
return undefined;
|
|
402
|
+
const options = {};
|
|
403
|
+
if (prep.settingsOverlay)
|
|
404
|
+
options.settings = prep.settingsOverlay;
|
|
405
|
+
if (role.harness_options?.mcp_servers_only === true)
|
|
406
|
+
options.strictMcpConfig = true;
|
|
407
|
+
return Object.keys(options).length ? { claudeCode: { options } } : undefined;
|
|
408
|
+
},
|
|
409
|
+
/**
|
|
410
|
+
* The declared servers, in ACP's array shape. Sent on `session/new` and on
|
|
411
|
+
* resume/load, because the SDK builds its server set once per session and a
|
|
412
|
+
* resumed session that dropped them would quietly lose its tools.
|
|
413
|
+
*/
|
|
414
|
+
acpMcpServers(role) {
|
|
415
|
+
return acpMcpServersFor(role.harness_options?.mcp_servers);
|
|
416
|
+
},
|
|
226
417
|
isolationPaths(_role, _dirs) {
|
|
227
418
|
const claudeHome = join(home(), '.claude');
|
|
228
419
|
return {
|
|
@@ -285,6 +476,8 @@ export function makeClaudeCodeAdapter(exec = realExec) {
|
|
|
285
476
|
currentIdentityTool: 'current_identity',
|
|
286
477
|
sendTool: 'send_message',
|
|
287
478
|
getMessagesTool: 'get_messages',
|
|
479
|
+
listHistoryTool: 'list_history',
|
|
480
|
+
getHistoryItemTool: 'get_history_item',
|
|
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;
|
package/dist/harness/codex.js
CHANGED
|
@@ -5,7 +5,7 @@ import { agentDir, home } from '../paths.js';
|
|
|
5
5
|
import { realExec } from '../exec.js';
|
|
6
6
|
import { registerAdapter } from './registry.js';
|
|
7
7
|
import { harnessRuntimeDir } from '../isolation/policy.js';
|
|
8
|
-
import {
|
|
8
|
+
import { resolveBundledAcpAgent, } from './acp-agent.js';
|
|
9
9
|
const OPTION_KEYS = [
|
|
10
10
|
'launcher', 'sandbox', 'approval', 'permission_mode', 'search', 'profile', 'config', 'add_dirs',
|
|
11
11
|
'monitor',
|
|
@@ -51,9 +51,7 @@ function sandboxMode(role) {
|
|
|
51
51
|
throw new Error(`invalid harness_options.sandbox "${s}"; allowed: ${SANDBOX_MODES.join(', ')}`);
|
|
52
52
|
return s;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
function acpAgentMode(role) {
|
|
56
|
-
const sandbox = sandboxMode(role);
|
|
54
|
+
function modeForSandbox(sandbox) {
|
|
57
55
|
if (sandbox === 'read-only')
|
|
58
56
|
return 'read-only';
|
|
59
57
|
if (sandbox === 'workspace-write')
|
|
@@ -62,6 +60,26 @@ function acpAgentMode(role) {
|
|
|
62
60
|
return 'agent-full-access';
|
|
63
61
|
return undefined;
|
|
64
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the coupled Codex ACP mode.
|
|
65
|
+
*
|
|
66
|
+
* The portable approval contract owns the default mode selection: `allow`
|
|
67
|
+
* means the adapter's fully non-interactive yolo preset and `auto` means its
|
|
68
|
+
* ordinary agent preset. This intentionally means that Codex ACP cannot retain
|
|
69
|
+
* an independent neutral filesystem posture for those two modes. An explicit
|
|
70
|
+
* native sandbox remains authoritative and selects its corresponding preset.
|
|
71
|
+
*/
|
|
72
|
+
function acpAgentMode(role) {
|
|
73
|
+
const explicitSandbox = role.harness_options?.sandbox;
|
|
74
|
+
if (explicitSandbox != null)
|
|
75
|
+
return modeForSandbox(sandboxMode(role));
|
|
76
|
+
if (role.permissions?.approval === 'allow')
|
|
77
|
+
return 'agent-full-access';
|
|
78
|
+
if (role.permissions?.approval === 'auto')
|
|
79
|
+
return 'agent';
|
|
80
|
+
const sandbox = sandboxMode(role);
|
|
81
|
+
return modeForSandbox(sandbox);
|
|
82
|
+
}
|
|
65
83
|
function acpModePermissions(mode) {
|
|
66
84
|
if (mode === 'read-only')
|
|
67
85
|
return { approval: 'on-request', sandbox: 'read-only' };
|
|
@@ -69,6 +87,10 @@ function acpModePermissions(mode) {
|
|
|
69
87
|
return { approval: 'never', sandbox: 'danger-full-access' };
|
|
70
88
|
return { approval: 'on-request', sandbox: 'workspace-write' };
|
|
71
89
|
}
|
|
90
|
+
/** The sandbox Codex will actually receive from the selected coupled ACP mode. */
|
|
91
|
+
function acpRuntimeSandbox(role) {
|
|
92
|
+
return acpModePermissions(acpAgentMode(role)).sandbox;
|
|
93
|
+
}
|
|
72
94
|
function fleetModeForApproval(nativeMode) {
|
|
73
95
|
if (nativeMode === 'never')
|
|
74
96
|
return 'allow';
|
|
@@ -102,6 +124,18 @@ function launcherMode(role) {
|
|
|
102
124
|
function bundledCodexAcp() {
|
|
103
125
|
return resolveBundledAcpAgent(CODEX_ACP_PACKAGE, 'codex-acp', 'codex-acp');
|
|
104
126
|
}
|
|
127
|
+
/** Bind launch argv and metadata provenance to one already-completed resolution. */
|
|
128
|
+
export function codexAcpLaunchForResolution(resolution) {
|
|
129
|
+
const permissionMetadataSource = resolution.bundled
|
|
130
|
+
&& resolution.version === BUNDLED_CODEX_ACP_VERSION
|
|
131
|
+
&& resolution.manifestPath !== undefined
|
|
132
|
+
? 'codex-acp'
|
|
133
|
+
: undefined;
|
|
134
|
+
return {
|
|
135
|
+
argv: [...resolution.argv],
|
|
136
|
+
...(permissionMetadataSource ? { permissionMetadataSource } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
105
139
|
function canOverrideBundledAcpApproval() {
|
|
106
140
|
const resolution = bundledCodexAcp();
|
|
107
141
|
return resolution.bundled && resolution.version === BUNDLED_CODEX_ACP_VERSION
|
|
@@ -147,7 +181,7 @@ function codexAcpEnvironment(role, dirs) {
|
|
|
147
181
|
return {
|
|
148
182
|
CODEX_PATH: command,
|
|
149
183
|
[CODEX_PROXY_APPROVAL_ENV]: approvalPolicy(role) ?? 'on-request',
|
|
150
|
-
[CODEX_PROXY_SANDBOX_ENV]:
|
|
184
|
+
[CODEX_PROXY_SANDBOX_ENV]: acpRuntimeSandbox(role),
|
|
151
185
|
[CODEX_PROXY_MANIFEST_ENV]: resolution.manifestPath,
|
|
152
186
|
...(process.env.CODEX_PATH ? { [CODEX_PROXY_REAL_PATH_ENV]: process.env.CODEX_PATH } : {}),
|
|
153
187
|
};
|
|
@@ -275,7 +309,7 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
275
309
|
return errs;
|
|
276
310
|
},
|
|
277
311
|
async prepareSession(role, dirs) {
|
|
278
|
-
// Per-role harness runtime home
|
|
312
|
+
// Per-role harness runtime home; harmless for un-isolated roles.
|
|
279
313
|
// Only a role that declares `isolation:` gets a sandbox, and only a
|
|
280
314
|
// sandbox needs this directory to exist before entry.
|
|
281
315
|
if (role.isolation)
|
|
@@ -285,7 +319,16 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
285
319
|
if (requested === 'ours-codex' && !hasOursCodex)
|
|
286
320
|
throw new Error('harness_options.launcher is ours-codex, but ours-codex is not on PATH; install @ours.network/codex or use launcher: auto');
|
|
287
321
|
const command = requested === 'codex' ? 'codex' : hasOursCodex ? 'ours-codex' : 'codex';
|
|
288
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
argv: [],
|
|
324
|
+
// OURS_BIND_IDENTITY is the connector's startup bind seed — see the note in
|
|
325
|
+
// claude-code.ts's prepareSession. It belongs on EVERY harness that runs a
|
|
326
|
+
// role with an ours identity, not just claude-code: a seed that works on one
|
|
327
|
+
// harness and silently does nothing on the other is the same class of defect
|
|
328
|
+
// as a config key that only works on one session type.
|
|
329
|
+
env: { OURS_BIND_IDENTITY: role.identity, ...codexAcpEnvironment(role, dirs) },
|
|
330
|
+
command,
|
|
331
|
+
};
|
|
289
332
|
},
|
|
290
333
|
buildLaunch(role, mode, _s, prep) {
|
|
291
334
|
const stateDir = roleStateDir(role);
|
|
@@ -299,17 +342,30 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
299
342
|
},
|
|
300
343
|
buildAcpLaunch(role, prep) {
|
|
301
344
|
const configured = role.session_options?.acp?.command;
|
|
345
|
+
// Resolve once: both argv and permission-metadata provenance must describe
|
|
346
|
+
// the same artifact. A bare PATH fallback is launchable for compatibility,
|
|
347
|
+
// but is never authenticated for protected-MCP auto-approval.
|
|
348
|
+
const resolved = configured == null
|
|
349
|
+
? codexAcpLaunchForResolution(bundledCodexAcp())
|
|
350
|
+
: undefined;
|
|
302
351
|
const argv = Array.isArray(configured)
|
|
303
352
|
? [...configured]
|
|
304
353
|
: typeof configured === 'string'
|
|
305
354
|
? ['sh', '-c', configured]
|
|
306
|
-
:
|
|
355
|
+
: resolved.argv;
|
|
307
356
|
const initialMode = acpAgentMode(role);
|
|
308
357
|
return {
|
|
309
358
|
argv,
|
|
310
359
|
env: initialMode ? { ...prep.env, INITIAL_AGENT_MODE: initialMode } : prep.env,
|
|
360
|
+
...(resolved?.permissionMetadataSource
|
|
361
|
+
? { permissionMetadataSource: resolved.permissionMetadataSource } : {}),
|
|
311
362
|
};
|
|
312
363
|
},
|
|
364
|
+
// INITIAL_AGENT_MODE covers session/new in codex-acp; session/set_mode
|
|
365
|
+
// keeps resumed/loaded sessions and live status on the identical mode.
|
|
366
|
+
acpPermissionModeId(role) {
|
|
367
|
+
return acpAgentMode(role);
|
|
368
|
+
},
|
|
313
369
|
isolationPaths(role, _dirs) {
|
|
314
370
|
const codexHome = join(home(), '.codex');
|
|
315
371
|
const profile = role.harness_options?.profile;
|
|
@@ -362,7 +418,9 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
362
418
|
const mode = acpAgentMode(role) ?? 'agent';
|
|
363
419
|
const configured = role.session_options?.acp?.command;
|
|
364
420
|
const overrideAvailable = configured == null && canOverrideBundledAcpApproval();
|
|
365
|
-
const actual = overrideAvailable
|
|
421
|
+
const actual = overrideAvailable
|
|
422
|
+
? { approval, sandbox: acpRuntimeSandbox(role) }
|
|
423
|
+
: acpModePermissions(mode);
|
|
366
424
|
const exact = actual.approval === approval && actual.sandbox === sandbox;
|
|
367
425
|
return {
|
|
368
426
|
...translated,
|
|
@@ -372,10 +430,9 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
372
430
|
? `custom ACP command cannot be verified against approval=${approval} sandbox=${sandbox}; `
|
|
373
431
|
+ `its '${mode}' mode is conservatively treated as approval=${actual.approval} `
|
|
374
432
|
+ `sandbox=${actual.sandbox}`
|
|
375
|
-
: `
|
|
376
|
-
+ `sandbox=${actual.sandbox}
|
|
377
|
-
+ `
|
|
378
|
-
+ `approval=${approval} sandbox=${sandbox}`],
|
|
433
|
+
: `Codex ACP mode '${mode}' couples approval and filesystem as `
|
|
434
|
+
+ `approval=${actual.approval} sandbox=${actual.sandbox}; this does not exactly `
|
|
435
|
+
+ `represent approval=${approval} sandbox=${sandbox}`],
|
|
379
436
|
capabilities: codexCapabilities(actual.approval, actual.sandbox),
|
|
380
437
|
};
|
|
381
438
|
}
|
|
@@ -411,6 +468,8 @@ export function makeCodexAdapter(exec = realExec) {
|
|
|
411
468
|
currentIdentityTool: 'current_identity',
|
|
412
469
|
sendTool: 'send_message',
|
|
413
470
|
getMessagesTool: 'get_messages',
|
|
471
|
+
listHistoryTool: 'list_history',
|
|
472
|
+
getHistoryItemTool: 'get_history_item',
|
|
414
473
|
monitorInstruction: (id, configuredRole) => {
|
|
415
474
|
const consented = configuredRole?.harness_options?.monitor === true;
|
|
416
475
|
const consent = consented
|
package/dist/harness/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@agentclientprotocol/sdk';
|
|
1
2
|
import type { CommonPermissions, FleetPermissionMode, ResolvedRole } from '../config.js';
|
|
2
3
|
export interface PrereqCheck {
|
|
3
4
|
name: string;
|
|
@@ -21,14 +22,39 @@ export interface SessionPrep {
|
|
|
21
22
|
env: Record<string, string>;
|
|
22
23
|
/** Optional launcher selected after runtime prerequisite probing. */
|
|
23
24
|
command?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The settings overlay prepareSession wrote, if it wrote one.
|
|
27
|
+
*
|
|
28
|
+
* The tmux launch delivers this as `--settings <path>` in `argv`; an ACP agent
|
|
29
|
+
* takes no flags, so it needs the PATH rather than the flag. Recorded here so
|
|
30
|
+
* the two deliveries read one value instead of each re-deriving the filename.
|
|
31
|
+
*/
|
|
32
|
+
settingsOverlay?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The MCP config file prepareSession wrote for `harness_options.mcp_servers`,
|
|
35
|
+
* if the role declared any. Same reason as `settingsOverlay`: the tmux launch
|
|
36
|
+
* passes the file, the ACP launch has to send the servers themselves.
|
|
37
|
+
*/
|
|
38
|
+
mcpConfigFile?: string;
|
|
24
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* One MCP server as ACP's `session/new` declares it.
|
|
42
|
+
*
|
|
43
|
+
* ⚠ THE PROTOCOL'S OWN TYPE, DELIBERATELY NOT A LOCAL RESTATEMENT. `mcpServers`
|
|
44
|
+
* goes onto the wire unchanged, so a hand-written near-copy would compile while
|
|
45
|
+
* being subtly wrong — `env` and `headers` are REQUIRED arrays, and the stdio
|
|
46
|
+
* variant is the one with no `type` field at all. Aliasing it also keeps
|
|
47
|
+
* `session/new`'s response type inferable, which a structural stand-in silently
|
|
48
|
+
* broke (every field of the result degraded to `unknown`).
|
|
49
|
+
*/
|
|
50
|
+
export type AcpMcpServer = McpServer;
|
|
25
51
|
export interface Launch {
|
|
26
52
|
argv: string[];
|
|
27
53
|
env: Record<string, string>;
|
|
28
54
|
}
|
|
29
|
-
export interface AcpLaunch {
|
|
30
|
-
argv
|
|
31
|
-
|
|
55
|
+
export interface AcpLaunch extends Launch {
|
|
56
|
+
/** Metadata vocabulary authenticated by the exact ACP artifact in argv. */
|
|
57
|
+
permissionMetadataSource?: 'codex-acp';
|
|
32
58
|
}
|
|
33
59
|
/**
|
|
34
60
|
* The result of expressing neutral `permissions:` in a harness's own terms.
|
|
@@ -67,6 +93,8 @@ export interface BriefingVocab {
|
|
|
67
93
|
currentIdentityTool: string;
|
|
68
94
|
sendTool: string;
|
|
69
95
|
getMessagesTool: string;
|
|
96
|
+
listHistoryTool: string;
|
|
97
|
+
getHistoryItemTool: string;
|
|
70
98
|
monitorInstruction(identity: string, role?: ResolvedRole): string;
|
|
71
99
|
/** Wake-source wording for a role whose monitor is supervisor-owned (monitor.mode=fleet). */
|
|
72
100
|
supervisedWakeNote(identity: string, role?: ResolvedRole): string;
|
|
@@ -74,7 +102,7 @@ export interface BriefingVocab {
|
|
|
74
102
|
restartPrompt(identity: string, worklogPath: string, role?: ResolvedRole): string;
|
|
75
103
|
}
|
|
76
104
|
/**
|
|
77
|
-
* How a harness's host state splits for sandboxing
|
|
105
|
+
* How a harness's host state splits for sandboxing. `home` is the
|
|
78
106
|
* directory the CLI treats as its own and whose RUNTIME state must be per-role;
|
|
79
107
|
* `shared` are the credential, instruction and configuration paths that stay
|
|
80
108
|
* shared and become read-only inside the sandbox.
|
|
@@ -95,7 +123,13 @@ export interface HarnessAdapter {
|
|
|
95
123
|
id: string;
|
|
96
124
|
supportsResume: boolean;
|
|
97
125
|
checkPrereqs(): Promise<PrereqReport>;
|
|
98
|
-
|
|
126
|
+
/**
|
|
127
|
+
* `role` is the SESSION-AWARE half: some harness options can only be honoured
|
|
128
|
+
* on some session types, and an option that is silently dropped is worse than
|
|
129
|
+
* one that is refused. Optional so an adapter that has nothing session-specific
|
|
130
|
+
* to say keeps its one-argument implementation.
|
|
131
|
+
*/
|
|
132
|
+
validateOptions(opts: unknown, role?: ResolvedRole): ValidationError[];
|
|
99
133
|
prepareSession(role: ResolvedRole, dirs: RoleDirs): Promise<SessionPrep>;
|
|
100
134
|
buildLaunch(role: ResolvedRole, mode: 'fresh' | 'resume', s: SessionState, prep: SessionPrep): Launch;
|
|
101
135
|
buildAcpLaunch?(role: ResolvedRole, prep: SessionPrep): AcpLaunch;
|
|
@@ -105,6 +139,23 @@ export interface HarnessAdapter {
|
|
|
105
139
|
* agent's default. Omit for a harness whose ACP agent has no modes.
|
|
106
140
|
*/
|
|
107
141
|
acpPermissionModeId?(role: ResolvedRole): string | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* The MCP servers this role declares, for the `mcpServers` array of ACP's
|
|
144
|
+
* `session/new` / `resume` / `load`. Empty (or omitted) leaves the agent's own
|
|
145
|
+
* configuration alone, which is what fleet has always sent.
|
|
146
|
+
*/
|
|
147
|
+
acpMcpServers?(role: ResolvedRole): AcpMcpServer[];
|
|
148
|
+
/**
|
|
149
|
+
* Agent-specific `_meta` for `session/new` — how a capability the CLI takes as
|
|
150
|
+
* a flag reaches an ACP agent that accepts no flags.
|
|
151
|
+
*
|
|
152
|
+
* ⚠ THIS IS A PER-AGENT VOCABULARY, NOT PROTOCOL. `_meta` is free-form in ACP,
|
|
153
|
+
* so what an adapter puts here is only honoured by the agent it was written
|
|
154
|
+
* for. An adapter must therefore return nothing for an ACP command it did not
|
|
155
|
+
* choose, and the options that depend on it must be refused at validation for
|
|
156
|
+
* such a role rather than sent and silently ignored.
|
|
157
|
+
*/
|
|
158
|
+
acpSessionMeta?(role: ResolvedRole, prep: SessionPrep): Record<string, unknown> | undefined;
|
|
108
159
|
/** Effective portable policy and harness-native approval mode after native overrides win. */
|
|
109
160
|
effectivePermissionMode?(role: ResolvedRole): {
|
|
110
161
|
fleetMode: FleetPermissionMode;
|
|
@@ -127,7 +178,7 @@ export interface HarnessAdapter {
|
|
|
127
178
|
nativePermissionOverrides(options: unknown): Record<string, unknown>;
|
|
128
179
|
/**
|
|
129
180
|
* Host paths this harness needs inside a sandbox, split into a per-role
|
|
130
|
-
* writable home and shared read-only credentials/config
|
|
181
|
+
* writable home and shared read-only credentials/config. Omit for a
|
|
131
182
|
* harness with no host state of its own.
|
|
132
183
|
*/
|
|
133
184
|
isolationPaths?(role: ResolvedRole, dirs: RoleDirs): HarnessIsolationPaths;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type Exec } from '../exec.js';
|
|
2
2
|
import type { IsolationBackend, NetworkMode } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* keeps the host network so ours messaging
|
|
6
|
-
*
|
|
7
|
-
* a loopback forwarder is
|
|
4
|
+
* Only `deny` unshares the network namespace. `broker`
|
|
5
|
+
* keeps the host network so ours messaging through a loopback TCP daemon keeps
|
|
6
|
+
* working; hardening `broker` to `--unshare-net` plus
|
|
7
|
+
* a loopback forwarder is not implemented. `allow`/`allowlist` also keep host net.
|
|
8
8
|
*/
|
|
9
9
|
export declare function unsharesNet(network: NetworkMode): boolean;
|
|
10
10
|
export declare function makeBubblewrapBackend(exec?: Exec): IsolationBackend;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { realExec } from '../exec.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* keeps the host network so ours messaging
|
|
5
|
-
*
|
|
6
|
-
* a loopback forwarder is
|
|
3
|
+
* Only `deny` unshares the network namespace. `broker`
|
|
4
|
+
* keeps the host network so ours messaging through a loopback TCP daemon keeps
|
|
5
|
+
* working; hardening `broker` to `--unshare-net` plus
|
|
6
|
+
* a loopback forwarder is not implemented. `allow`/`allowlist` also keep host net.
|
|
7
7
|
*/
|
|
8
8
|
export function unsharesNet(network) {
|
|
9
9
|
return network === 'deny';
|