@ai-outfitter/outfitter 1.3.1 → 1.5.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/dist/agents/AgentLaunch.d.ts +17 -0
- package/dist/agents/AgentLaunch.js +53 -2
- package/dist/agents/AgentLaunch.js.map +1 -1
- package/dist/extensions/PiExtensionCache.d.ts +11 -1
- package/dist/extensions/PiExtensionCache.js +129 -13
- package/dist/extensions/PiExtensionCache.js.map +1 -1
- package/dist/projection/Materialize.js +3 -4
- package/dist/projection/Materialize.js.map +1 -1
- package/dist/projection/ProjectHarness.js +11 -5
- package/dist/projection/ProjectHarness.js.map +1 -1
- package/dist/projection/Tools.d.ts +91 -0
- package/dist/projection/Tools.js +129 -0
- package/dist/projection/Tools.js.map +1 -0
- package/dist/resolver/AgentDefinition.d.ts +11 -0
- package/dist/resolver/AgentDefinition.js +91 -12
- package/dist/resolver/AgentDefinition.js.map +1 -1
- package/dist/schemas/agent.schema.json +8 -2
- package/docs/documentation/README.md +1 -0
- package/docs/documentation/containers.md +187 -0
- package/docs/documentation/support-matrix.md +3 -1
- package/package.json +1 -1
- package/src/schemas/agent.schema.json +8 -2
|
@@ -4,6 +4,23 @@ export interface AgentProcessLauncher {
|
|
|
4
4
|
}
|
|
5
5
|
export declare const launchAgentProcess: (launcher: AgentProcessLauncher, launchPlan: AgentLaunchPlan, agentId: string) => Promise<number>;
|
|
6
6
|
export declare const resolveAgentLaunchExecutable: (launchPlan: AgentLaunchPlan) => AgentLaunchPlan;
|
|
7
|
+
/**
|
|
8
|
+
* Forwards termination signals from this process to a spawned harness, resolving once the harness
|
|
9
|
+
* actually exits.
|
|
10
|
+
*
|
|
11
|
+
* Without this, a resident agent cannot shut down. Node installs no default forwarding, so the
|
|
12
|
+
* harness never learns the session is ending: under Kubernetes it is SIGKILLed when the grace period
|
|
13
|
+
* expires, skipping credential persistence and projection cleanup. The same gap shows up outside
|
|
14
|
+
* containers — Ctrl-C in a terminal, or a cancelled CI job — which is why this belongs here rather
|
|
15
|
+
* than in a container init.
|
|
16
|
+
*
|
|
17
|
+
* Installing a handler suppresses Node's default termination, so every path must resolve, and the
|
|
18
|
+
* listeners must come off afterwards or repeated launches in one process leak them.
|
|
19
|
+
*/
|
|
20
|
+
export declare const attachSignalForwarding: (child: {
|
|
21
|
+
kill(signal?: NodeJS.Signals): boolean;
|
|
22
|
+
killed: boolean;
|
|
23
|
+
}, emitter?: NodeJS.EventEmitter, graceMs?: number) => (() => void);
|
|
7
24
|
export declare const spawnLauncher: AgentProcessLauncher;
|
|
8
25
|
/**
|
|
9
26
|
* Launches a resolved plan through the given spawn boundary. The install-hint agentId is derived
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Turns a logical agent launch plan into an actual launched process: resolves the bundled pi
|
|
2
2
|
// binary, runs the launcher, and translates a missing agent CLI into actionable install guidance.
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
4
5
|
import { dirname, join } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
export const launchAgentProcess = async (launcher, launchPlan, agentId) => {
|
|
@@ -39,14 +40,64 @@ export const resolveAgentLaunchExecutable = (launchPlan) => {
|
|
|
39
40
|
env: { PI_SKIP_VERSION_CHECK: '1', ...launchPlan.env },
|
|
40
41
|
};
|
|
41
42
|
};
|
|
43
|
+
// Signals we forward to the harness. SIGKILL is deliberately absent: it cannot be caught, and the
|
|
44
|
+
// kernel delivers it to us directly.
|
|
45
|
+
const FORWARDED_SIGNALS = ['SIGTERM', 'SIGINT', 'SIGHUP'];
|
|
46
|
+
// How long the harness gets to exit after a forwarded signal before we stop being polite. Kubernetes
|
|
47
|
+
// defaults to a 30s grace period and SIGKILLs the pod afterwards, so this has to be comfortably
|
|
48
|
+
// shorter or the escalation never runs.
|
|
49
|
+
const TERMINATION_GRACE_MS = 10_000;
|
|
50
|
+
/**
|
|
51
|
+
* Forwards termination signals from this process to a spawned harness, resolving once the harness
|
|
52
|
+
* actually exits.
|
|
53
|
+
*
|
|
54
|
+
* Without this, a resident agent cannot shut down. Node installs no default forwarding, so the
|
|
55
|
+
* harness never learns the session is ending: under Kubernetes it is SIGKILLed when the grace period
|
|
56
|
+
* expires, skipping credential persistence and projection cleanup. The same gap shows up outside
|
|
57
|
+
* containers — Ctrl-C in a terminal, or a cancelled CI job — which is why this belongs here rather
|
|
58
|
+
* than in a container init.
|
|
59
|
+
*
|
|
60
|
+
* Installing a handler suppresses Node's default termination, so every path must resolve, and the
|
|
61
|
+
* listeners must come off afterwards or repeated launches in one process leak them.
|
|
62
|
+
*/
|
|
63
|
+
export const attachSignalForwarding = (child, emitter = process, graceMs = TERMINATION_GRACE_MS) => {
|
|
64
|
+
let escalation;
|
|
65
|
+
const forward = (signal) => () => {
|
|
66
|
+
if (child.killed)
|
|
67
|
+
return;
|
|
68
|
+
child.kill(signal);
|
|
69
|
+
// A harness that ignores or hangs on the signal would otherwise keep us alive until the
|
|
70
|
+
// orchestrator's own SIGKILL, losing the chance to exit cleanly first.
|
|
71
|
+
escalation ??= setTimeout(() => child.kill('SIGKILL'), graceMs);
|
|
72
|
+
escalation.unref?.();
|
|
73
|
+
};
|
|
74
|
+
const handlers = FORWARDED_SIGNALS.map((signal) => [signal, forward(signal)]);
|
|
75
|
+
for (const [signal, handler] of handlers)
|
|
76
|
+
emitter.on(signal, handler);
|
|
77
|
+
return () => {
|
|
78
|
+
for (const [signal, handler] of handlers)
|
|
79
|
+
emitter.removeListener(signal, handler);
|
|
80
|
+
if (escalation)
|
|
81
|
+
clearTimeout(escalation);
|
|
82
|
+
};
|
|
83
|
+
};
|
|
42
84
|
/* v8 ignore start -- real process spawn is covered by end-to-end smoke usage, not unit tests. */
|
|
43
85
|
export const spawnLauncher = {
|
|
44
86
|
async launch(plan) {
|
|
45
87
|
const { default: spawn } = await import('cross-spawn');
|
|
46
88
|
return await new Promise((resolve, reject) => {
|
|
47
89
|
const child = spawn(plan.command, [...plan.args], { stdio: 'inherit', env: { ...process.env, ...plan.env } });
|
|
48
|
-
child
|
|
49
|
-
child.on('
|
|
90
|
+
const detach = attachSignalForwarding(child);
|
|
91
|
+
child.on('error', (error) => {
|
|
92
|
+
detach();
|
|
93
|
+
reject(error); // ENOENT surfaces as an actionable install message
|
|
94
|
+
});
|
|
95
|
+
child.on('close', (code, signal) => {
|
|
96
|
+
detach();
|
|
97
|
+
// 128+n is the shell convention for "died on signal n", and it is what a caller inspecting
|
|
98
|
+
// our exit status expects to see when the harness was terminated rather than returning.
|
|
99
|
+
resolve(code ?? (signal ? 128 + (os.constants.signals[signal] ?? 0) : 0));
|
|
100
|
+
});
|
|
50
101
|
});
|
|
51
102
|
},
|
|
52
103
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentLaunch.js","sourceRoot":"","sources":["../../src/agents/AgentLaunch.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,kGAAkG;AAClG,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAQzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,QAA8B,EAC9B,UAA2B,EAC3B,OAAe,EACE,EAAE;IACnB,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,sBAAsB,CAAC,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/F,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,+FAA+F;AAC/F,kGAAkG;AAClG,mGAAmG;AACnG,+FAA+F;AAC/F,yBAAyB;AACzB,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,UAA2B,EAAmB,EAAE;IAC3F,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAChC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,eAAe,GAAG,sBAAsB,EAAE,CAAC;IAEjD,oGAAoG;IACpG,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO;QACL,GAAG,UAAU;QACb,OAAO,EAAE,eAAe,CAAC,OAAO;QAChC,IAAI,EAAE,CAAC,GAAG,eAAe,CAAC,UAAU,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC;QACzD,8FAA8F;QAC9F,gGAAgG;QAChG,gGAAgG;QAChG,4FAA4F;QAC5F,GAAG,EAAE,EAAE,qBAAqB,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE;KACvD,CAAC;AACJ,CAAC,CAAC;AAEF,iGAAiG;AACjG,MAAM,CAAC,MAAM,aAAa,GAAyB;IACjD,KAAK,CAAC,MAAM,CAAC,IAAqB;QAChC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACvD,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC9G,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,mDAAmD;
|
|
1
|
+
{"version":3,"file":"AgentLaunch.js","sourceRoot":"","sources":["../../src/agents/AgentLaunch.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,13 +18,23 @@ export interface EnsurePiExtensionsResult {
|
|
|
18
18
|
interface PiExtensionSource {
|
|
19
19
|
readonly source: string;
|
|
20
20
|
readonly installSegments: readonly string[];
|
|
21
|
-
/** Exact semver to verify against a cached install; undefined for ranges
|
|
21
|
+
/** Exact semver to verify against a cached npm install; undefined for ranges and tags. */
|
|
22
22
|
readonly pinnedVersion?: string;
|
|
23
|
+
/** The `@ref` of a git specifier, to verify against a cached checkout; undefined when unpinned. */
|
|
24
|
+
readonly pinnedGitRef?: string;
|
|
23
25
|
}
|
|
24
26
|
/** Translates an Outfitter extension specifier to a pi `install` source + its cache install path. */
|
|
25
27
|
export declare const mapSpecifierToPiSource: (specifier: string) => PiExtensionSource | {
|
|
26
28
|
readonly unsupported: string;
|
|
27
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Defense in depth behind segment validation: refuses an install dir that resolves outside the
|
|
32
|
+
* cache root before any filesystem access (the stale-git path runs `rm -rf` on the install dir).
|
|
33
|
+
* Strict containment also keeps the `<installDir>.outfitter-ref.json` marker sibling inside the
|
|
34
|
+
* cache, since a contained install dir is at least one level below the root. Exported for tests;
|
|
35
|
+
* segment validation in mapSpecifierToPiSource should make this unreachable.
|
|
36
|
+
*/
|
|
37
|
+
export declare const assertInstallDirInsideCache: (installDir: string, cacheAgentDir: string, specifier: string) => void;
|
|
28
38
|
/** Ensures each pi extension is cached (installing when online) and returns its load directory. */
|
|
29
39
|
export declare const ensurePiExtensions: (specifiers: readonly string[], input: EnsurePiExtensionsInput) => Promise<EnsurePiExtensionsResult>;
|
|
30
40
|
export {};
|
|
@@ -5,10 +5,43 @@
|
|
|
5
5
|
// npm: <cacheAgentDir>/npm/node_modules/<pkg>
|
|
6
6
|
// Outfitter's `git:`/`npm:` specifiers are already pi's `install` source grammar, so the source is
|
|
7
7
|
// passed through unchanged; only the install directory is reconstructed to check the cache.
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
//
|
|
9
|
+
// A cached git checkout is only trusted when it still matches the specifier's `@ref` pin:
|
|
10
|
+
// - full-SHA ref: `git -C <installDir> rev-parse HEAD` is compared against the pin (offline-safe,
|
|
11
|
+
// no network); a mismatch or an unreadable HEAD reinstalls when online.
|
|
12
|
+
// - branch/tag ref: the ref string is recorded in a `<installDir>.outfitter-ref.json` marker at
|
|
13
|
+
// install time and only a *changed* ref string triggers a reinstall — a moved remote branch tip
|
|
14
|
+
// is deliberately not chased, so pinned launches never hit the network.
|
|
15
|
+
// - branch/tag ref with no marker (a pre-marker cache): served as-is when offline (no evidence it
|
|
16
|
+
// is wrong), refreshed once when online, which writes the marker.
|
|
17
|
+
// Offline, a checkout that provably mismatches its pin is dropped with a warning — the same
|
|
18
|
+
// severity the offline path already applies to a missing extension (fatal only under `--strict`).
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { join, resolve, sep } from 'node:path';
|
|
10
21
|
import { launchThroughSpawn, spawnLauncher } from '../agents/AgentLaunch.js';
|
|
22
|
+
import { runGit } from '../sources/GitRepository.js';
|
|
11
23
|
const exactSemverPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
24
|
+
const fullShaPattern = /^[0-9a-f]{40}$/iu;
|
|
25
|
+
// Specifier text becomes filesystem path segments (and the stale-reinstall path runs `rm -rf` on
|
|
26
|
+
// the joined result), so a segment that traverses (`..`, `.`) or smuggles a separator (`\`, which
|
|
27
|
+
// `join` does not split but Windows resolves) must be rejected before any path is built.
|
|
28
|
+
const unsafePathSegment = (segment) => segment === '.' || segment === '..' || segment.includes('\\');
|
|
29
|
+
const mapGitSpecifier = (specifier) => {
|
|
30
|
+
const rest = specifier.slice('git:'.length);
|
|
31
|
+
const pathPart = rest.includes('@') ? rest.slice(0, rest.lastIndexOf('@')) : rest;
|
|
32
|
+
const ref = rest.includes('@') ? rest.slice(rest.lastIndexOf('@') + 1) : '';
|
|
33
|
+
const segments = pathPart.split('/').filter((part) => part !== '');
|
|
34
|
+
if (segments.length < 2)
|
|
35
|
+
return { unsupported: `extension '${specifier}' is not a valid git source` };
|
|
36
|
+
if (segments.some(unsafePathSegment)) {
|
|
37
|
+
return { unsupported: `extension '${specifier}' contains an unsafe path segment` };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
source: specifier,
|
|
41
|
+
installSegments: ['git', ...segments],
|
|
42
|
+
pinnedGitRef: ref === '' ? undefined : ref,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
12
45
|
/** Translates an Outfitter extension specifier to a pi `install` source + its cache install path. */
|
|
13
46
|
export const mapSpecifierToPiSource = (specifier) => {
|
|
14
47
|
if (specifier.startsWith('npm:')) {
|
|
@@ -16,6 +49,9 @@ export const mapSpecifierToPiSource = (specifier) => {
|
|
|
16
49
|
const name = rest.replace(/@[^@/]+$/u, ''); // strip a trailing @version, keep a scope's leading @
|
|
17
50
|
if (name === '')
|
|
18
51
|
return { unsupported: `extension '${specifier}' has no package name` };
|
|
52
|
+
if (name.split('/').some(unsafePathSegment)) {
|
|
53
|
+
return { unsupported: `extension '${specifier}' contains an unsafe path segment` };
|
|
54
|
+
}
|
|
19
55
|
const version = rest.length > name.length ? rest.slice(name.length + 1) : undefined;
|
|
20
56
|
return {
|
|
21
57
|
source: specifier,
|
|
@@ -23,14 +59,8 @@ export const mapSpecifierToPiSource = (specifier) => {
|
|
|
23
59
|
pinnedVersion: version !== undefined && exactSemverPattern.test(version) ? version : undefined,
|
|
24
60
|
};
|
|
25
61
|
}
|
|
26
|
-
if (specifier.startsWith('git:'))
|
|
27
|
-
|
|
28
|
-
const pathPart = rest.includes('@') ? rest.slice(0, rest.lastIndexOf('@')) : rest;
|
|
29
|
-
const segments = pathPart.split('/').filter((part) => part !== '');
|
|
30
|
-
if (segments.length < 2)
|
|
31
|
-
return { unsupported: `extension '${specifier}' is not a valid git source` };
|
|
32
|
-
return { source: specifier, installSegments: ['git', ...segments] };
|
|
33
|
-
}
|
|
62
|
+
if (specifier.startsWith('git:'))
|
|
63
|
+
return mapGitSpecifier(specifier);
|
|
34
64
|
return { unsupported: `extension '${specifier}' uses an unsupported source (only git: and npm: project to pi)` };
|
|
35
65
|
};
|
|
36
66
|
const installedVersion = (installDir) => {
|
|
@@ -42,6 +72,88 @@ const installedVersion = (installDir) => {
|
|
|
42
72
|
return undefined;
|
|
43
73
|
}
|
|
44
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Defense in depth behind segment validation: refuses an install dir that resolves outside the
|
|
77
|
+
* cache root before any filesystem access (the stale-git path runs `rm -rf` on the install dir).
|
|
78
|
+
* Strict containment also keeps the `<installDir>.outfitter-ref.json` marker sibling inside the
|
|
79
|
+
* cache, since a contained install dir is at least one level below the root. Exported for tests;
|
|
80
|
+
* segment validation in mapSpecifierToPiSource should make this unreachable.
|
|
81
|
+
*/
|
|
82
|
+
export const assertInstallDirInsideCache = (installDir, cacheAgentDir, specifier) => {
|
|
83
|
+
const root = resolve(cacheAgentDir);
|
|
84
|
+
if (!resolve(installDir).startsWith(root + sep)) {
|
|
85
|
+
throw new Error(`extension '${specifier}' resolves outside the extension cache; refusing to touch it.`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/** Marker recording which git ref an install satisfied, kept beside (never inside) the checkout. */
|
|
89
|
+
const refMarkerPath = (installDir) => `${installDir}.outfitter-ref.json`;
|
|
90
|
+
const cachedGitHead = (installDir) => {
|
|
91
|
+
try {
|
|
92
|
+
return runGit(['-C', installDir, 'rev-parse', 'HEAD']);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const recordedInstallRef = (installDir) => {
|
|
99
|
+
try {
|
|
100
|
+
const marker = JSON.parse(readFileSync(refMarkerPath(installDir), 'utf8'));
|
|
101
|
+
return marker.ref;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/** After installing a branch/tag-pinned git extension, records the ref (and resolved SHA). */
|
|
108
|
+
const recordInstalledGitRef = (installDir, mapped) => {
|
|
109
|
+
if (mapped.pinnedGitRef === undefined || fullShaPattern.test(mapped.pinnedGitRef))
|
|
110
|
+
return;
|
|
111
|
+
const marker = { ref: mapped.pinnedGitRef, headSha: cachedGitHead(installDir) };
|
|
112
|
+
writeFileSync(refMarkerPath(installDir), JSON.stringify(marker));
|
|
113
|
+
};
|
|
114
|
+
// Remove-then-install for a stale git pin: `pi install` owns the directory layout, so
|
|
115
|
+
// GitRepository's atomic fetch-and-swap cannot apply here. A failure between the remove and the
|
|
116
|
+
// install leaves neither a directory nor a marker, which the next run detects as plain missing.
|
|
117
|
+
// The npm path keeps its original behavior: `pi install` is spawned over the existing dir.
|
|
118
|
+
const removeStaleGitInstall = (installDir, mapped) => {
|
|
119
|
+
if (mapped.pinnedGitRef === undefined)
|
|
120
|
+
return;
|
|
121
|
+
rmSync(installDir, { recursive: true, force: true });
|
|
122
|
+
rmSync(refMarkerPath(installDir), { force: true });
|
|
123
|
+
};
|
|
124
|
+
/** Compares a cached checkout against its `@ref` pin per the policy in the file header. */
|
|
125
|
+
const gitCacheStatus = (installDir, ref) => {
|
|
126
|
+
if (fullShaPattern.test(ref)) {
|
|
127
|
+
const head = cachedGitHead(installDir);
|
|
128
|
+
return {
|
|
129
|
+
state: head?.toLowerCase() === ref.toLowerCase() ? 'fresh' : 'stale',
|
|
130
|
+
found: head ?? 'no readable git HEAD',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const recorded = recordedInstallRef(installDir);
|
|
134
|
+
if (recorded === undefined)
|
|
135
|
+
return { state: 'unverified', found: 'no recorded install ref' };
|
|
136
|
+
return { state: recorded === ref ? 'fresh' : 'stale', found: recorded };
|
|
137
|
+
};
|
|
138
|
+
/** Decides whether an existing install satisfies the specifier's pin (git ref or exact semver). */
|
|
139
|
+
const evaluateCachedInstall = (installDir, mapped, offline) => {
|
|
140
|
+
if (!existsSync(installDir))
|
|
141
|
+
return { serve: false };
|
|
142
|
+
if (mapped.pinnedGitRef === undefined) {
|
|
143
|
+
return { serve: mapped.pinnedVersion === undefined || installedVersion(installDir) === mapped.pinnedVersion };
|
|
144
|
+
}
|
|
145
|
+
const status = gitCacheStatus(installDir, mapped.pinnedGitRef);
|
|
146
|
+
if (status.state === 'fresh' || (status.state === 'unverified' && offline))
|
|
147
|
+
return { serve: true };
|
|
148
|
+
if (offline) {
|
|
149
|
+
return {
|
|
150
|
+
serve: false,
|
|
151
|
+
staleWarning: `extension '${mapped.source}' is cached at the wrong revision ` +
|
|
152
|
+
`(pinned ${mapped.pinnedGitRef}, found ${status.found}) and cannot be reinstalled offline.`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return { serve: false };
|
|
156
|
+
};
|
|
45
157
|
/* v8 ignore start -- real `pi install` subprocess; ensurePiExtensions is unit-tested with a fake spawner. */
|
|
46
158
|
const defaultSpawner = ({ source, cacheAgentDir }) => launchThroughSpawn(spawnLauncher, {
|
|
47
159
|
command: 'pi',
|
|
@@ -54,12 +166,15 @@ const ensureOneExtension = async (specifier, input, spawn) => {
|
|
|
54
166
|
if ('unsupported' in mapped)
|
|
55
167
|
return { warning: mapped.unsupported };
|
|
56
168
|
const installDir = join(input.cacheAgentDir, ...mapped.installSegments);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
169
|
+
assertInstallDirInsideCache(installDir, input.cacheAgentDir, specifier);
|
|
170
|
+
const decision = evaluateCachedInstall(installDir, mapped, input.offline);
|
|
171
|
+
if (decision.serve)
|
|
60
172
|
return { loadDir: installDir };
|
|
173
|
+
if (decision.staleWarning !== undefined)
|
|
174
|
+
return { warning: decision.staleWarning };
|
|
61
175
|
if (input.offline)
|
|
62
176
|
return { warning: `extension '${specifier}' is not cached and cannot be installed offline.` };
|
|
177
|
+
removeStaleGitInstall(installDir, mapped);
|
|
63
178
|
mkdirSync(input.cacheAgentDir, { recursive: true });
|
|
64
179
|
try {
|
|
65
180
|
const exitCode = await spawn({ source: mapped.source, cacheAgentDir: input.cacheAgentDir });
|
|
@@ -70,6 +185,7 @@ const ensureOneExtension = async (specifier, input, spawn) => {
|
|
|
70
185
|
catch (error) {
|
|
71
186
|
return { warning: `extension '${specifier}' failed to install (${String(error)}).` };
|
|
72
187
|
}
|
|
188
|
+
recordInstalledGitRef(installDir, mapped);
|
|
73
189
|
return { loadDir: installDir };
|
|
74
190
|
};
|
|
75
191
|
/** Ensures each pi extension is cached (installing when online) and returns its load directory. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PiExtensionCache.js","sourceRoot":"","sources":["../../src/extensions/PiExtensionCache.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,qGAAqG;AACrG,oFAAoF;AACpF,oDAAoD;AACpD,iDAAiD;AACjD,mGAAmG;AACnG,4FAA4F;AAC5F,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"PiExtensionCache.js","sourceRoot":"","sources":["../../src/extensions/PiExtensionCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { escapesRoots } from '../dump/Containment.js';
|
|
5
5
|
import { removeTargetTypeConflict } from '../fs/TypeConflict.js';
|
|
6
6
|
import { isAgentDefinitionIssue, readAgentDefinition } from '../resolver/AgentDefinition.js';
|
|
7
|
+
import { effectiveToolAllowlist } from './Tools.js';
|
|
7
8
|
/** Recursively copies a directory, skipping symlinked entries so no path escapes the tree. */
|
|
8
9
|
const copyDirectory = (sourceDir, targetDir) => {
|
|
9
10
|
removeTargetTypeConflict(targetDir, 'directory');
|
|
@@ -73,8 +74,7 @@ const serializeSubagent = (subagent) => {
|
|
|
73
74
|
const definition = readValidSubagentDefinition(subagent);
|
|
74
75
|
if (definition === undefined)
|
|
75
76
|
return undefined;
|
|
76
|
-
const
|
|
77
|
-
const tools = definition.loadout.tools?.allow?.filter((tool) => !denied.has(tool));
|
|
77
|
+
const tools = effectiveToolAllowlist(definition.loadout.tools);
|
|
78
78
|
const frontmatter = [
|
|
79
79
|
`name: ${JSON.stringify(subagent.slug)}`,
|
|
80
80
|
`description: ${JSON.stringify(definition.description ?? definition.label ?? `Delegated ${subagent.slug} agent.`)}`,
|
|
@@ -95,8 +95,7 @@ const composedSubagentBody = (subagent) => [
|
|
|
95
95
|
.filter((fragment) => fragment !== undefined && fragment.length > 0)
|
|
96
96
|
.join('\n\n');
|
|
97
97
|
const serializeComposedSubagent = (subagent) => {
|
|
98
|
-
const
|
|
99
|
-
const tools = subagent.tools?.allow?.filter((tool) => !denied.has(tool));
|
|
98
|
+
const tools = effectiveToolAllowlist(subagent.tools);
|
|
100
99
|
const description = subagent.identity.description ?? subagent.identity.label;
|
|
101
100
|
const frontmatter = [
|
|
102
101
|
`name: ${JSON.stringify(subagent.resource.slug)}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Materialize.js","sourceRoot":"","sources":["../../src/projection/Materialize.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"Materialize.js","sourceRoot":"","sources":["../../src/projection/Materialize.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,14 +3,16 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { PI_SESSION_DIRECTORY_ENV } from '../agents/PiSessionDirectory.js';
|
|
5
5
|
import { materializeComposition, materializeConfigurationOverlays } from './Materialize.js';
|
|
6
|
+
import { toolArgs } from './Tools.js';
|
|
6
7
|
// Loadout elements a projection actually maps to native config. Anything else is reported
|
|
7
8
|
// unsupported so `--strict` catches silently-dropped selections. Baseline for both harnesses is
|
|
8
|
-
// identity + skills + model + thinking.
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// identity + skills + model + thinking + tools. Both harnesses express an allowlist and a denylist
|
|
10
|
+
// natively, so `tools` is unconditionally supported; a name projection cannot carry is a hard error
|
|
11
|
+
// from `toolArgs`, not an unsupported element. Pi also projects selected subagents and MCP servers
|
|
12
|
+
// into its runtime config directory, and projects `extensions` once the run path has resolved their
|
|
13
|
+
// install dirs (`extensionLoadDirs`). plugins remain unsupported pending incremental parity (#183).
|
|
12
14
|
const supportedElements = (input) => {
|
|
13
|
-
const baseline = ['skills', 'model', 'thinking'];
|
|
15
|
+
const baseline = ['skills', 'model', 'thinking', 'tools'];
|
|
14
16
|
if (input.harness !== 'pi')
|
|
15
17
|
return baseline;
|
|
16
18
|
const pi = [...baseline, 'subagents', 'mcp', 'prompt_template'];
|
|
@@ -96,6 +98,10 @@ const buildLaunchPlan = (composition, input, systemPromptPath, appendPromptPaths
|
|
|
96
98
|
return {
|
|
97
99
|
command: isPi ? 'pi' : 'claude',
|
|
98
100
|
args: [
|
|
101
|
+
// Tool args lead the list on purpose: Claude's `--allowedTools`/`--disallowedTools` are
|
|
102
|
+
// variadic and stop only at the next `--flag`, and the prompt args that follow always start
|
|
103
|
+
// with one. Placed later they could swallow a pass-through positional. See Tools.ts.
|
|
104
|
+
...(composition.loadout.tools === undefined ? [] : toolArgs(input.harness, composition.loadout.tools)),
|
|
99
105
|
...promptArgs(composition, input, systemPromptPath, appendPromptPaths),
|
|
100
106
|
...skillArgs,
|
|
101
107
|
...extensionArgs,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectHarness.js","sourceRoot":"","sources":["../../src/projection/ProjectHarness.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,OAAO,EAAE,sBAAsB,EAAE,gCAAgC,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"ProjectHarness.js","sourceRoot":"","sources":["../../src/projection/ProjectHarness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Loadout } from '../resolver/Resource.js';
|
|
2
|
+
import type { Harness } from '../settings/Settings.js';
|
|
3
|
+
/** The `{allow?, deny?}` tool selection carried by a loadout. */
|
|
4
|
+
export type ToolSelection = NonNullable<Loadout['tools']>;
|
|
5
|
+
/**
|
|
6
|
+
* A projectable tool name. Kept in lockstep with `$defs/toolName` in `agent.schema.json`, which
|
|
7
|
+
* rejects the same shapes at the YAML read boundary; a unit test asserts the two stay equal.
|
|
8
|
+
*
|
|
9
|
+
* Three characters are excluded, because projection puts these names into harness argv:
|
|
10
|
+
*
|
|
11
|
+
* - A leading `-` makes the name an independent flag on Claude, where each entry becomes its own
|
|
12
|
+
* argv element. `allow: [Read, --dangerously-skip-permissions]` would not name a tool. It would
|
|
13
|
+
* turn off the permission system this element exists to configure. Profiles compose from remote
|
|
14
|
+
* catalogs and inheritance chains, so a tool name is not always written by the person who runs
|
|
15
|
+
* the agent, which makes this a supply-chain path to arbitrary launch flags.
|
|
16
|
+
* - A comma splits one name into several entries inside pi's `--tools a,b,c` value.
|
|
17
|
+
* - Whitespace is not a separator on either path today, because no shell is involved and pi gets a
|
|
18
|
+
* single argv element. It is rejected as conservatism, and it keeps names portable between the
|
|
19
|
+
* two harnesses. Note that this also excludes Claude's scoped permission rules, such as
|
|
20
|
+
* `Bash(npm run test:*)`. That is deliberate for a harness-neutral field: the same string is
|
|
21
|
+
* meaningless to pi. Relaxing the pattern later is backward compatible.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TOOL_NAME_PATTERN: RegExp;
|
|
24
|
+
/** The rule the pattern encodes, phrased for an error that a profile author reads. */
|
|
25
|
+
export declare const TOOL_NAME_RULE = "a tool name must not start with '-' or contain a comma or whitespace, because projection would make it a separate harness flag or split it into several names.";
|
|
26
|
+
/**
|
|
27
|
+
* The first tool name in the selection that projection cannot carry, or `undefined` when every name
|
|
28
|
+
* is projectable. Shared with the agent read boundary, so `config.json` overlays, which bypass the
|
|
29
|
+
* JSON Schema, are held to the same invariant.
|
|
30
|
+
*/
|
|
31
|
+
export declare const invalidToolName: (tools: Loadout["tools"]) => string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The effective tool allowlist: `allow` minus `deny`, per OFTR-003.10.4 ("denied tools MUST win
|
|
34
|
+
* when projected"). `undefined` and `[]` mean different things and callers must keep them apart:
|
|
35
|
+
* `undefined` is "no allowlist declared", `[]` is "an allowlist that `deny` emptied".
|
|
36
|
+
*/
|
|
37
|
+
export declare const effectiveToolAllowlist: (tools: Loadout["tools"]) => readonly string[] | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* pi and Claude Code BOTH have an availability control; Claude additionally has a permission layer
|
|
40
|
+
* on top of its own. The pi rows were measured against pi 0.83.0 with a probe extension that
|
|
41
|
+
* registers a uniquely-named tool and prints `pi.getActiveTools()` at `session_start`; the exact
|
|
42
|
+
* runs are recorded in the PR for #255. The Claude rows come from `claude --help` and
|
|
43
|
+
* https://code.claude.com/docs/en/cli-reference — documented behaviour, not locally measured.
|
|
44
|
+
*
|
|
45
|
+
* | | pi | claude |
|
|
46
|
+
* | availability ceiling | `--no-tools --tools a,b,c` (comma-joined) | `--tools a b c` (variadic; built-in set only) |
|
|
47
|
+
* | pre-approval within it | n/a — pi has no permission layer | `--allowedTools a b c` (variadic) |
|
|
48
|
+
* | denylist | `--exclude-tools a,b,c` (comma-joined) | `--disallowedTools a b c` (variadic; a bare name removes the tool from context, per docs) |
|
|
49
|
+
* | no tools at all | `--no-tools` | `--tools ""` (empties the built-in set only) |
|
|
50
|
+
*
|
|
51
|
+
* An allowlist therefore projects to TWO flags on Claude: `--tools` is the ceiling (the tool is not
|
|
52
|
+
* in the session), and `--allowedTools` pre-approves the same names within that ceiling so a
|
|
53
|
+
* headless session is not stopped by a permission prompt for the very tools the profile granted.
|
|
54
|
+
* Either flag alone would be wrong: `--allowedTools` alone leaves every unlisted builtin available
|
|
55
|
+
* (merely unapproved), and `--tools` alone leaves the granted tools prompting for approval.
|
|
56
|
+
*
|
|
57
|
+
* Scope caveat, from the CLI reference: `--tools` governs the BUILT-IN set only. MCP tools
|
|
58
|
+
* (`mcp__server__*`) are untouched by it — they are governed by which servers load (the `mcp`
|
|
59
|
+
* loadout element) and by `--disallowedTools` patterns such as `mcp__*`. So `--tools ""` is not
|
|
60
|
+
* pi's zero-tool session when MCP servers are selected: the builtins are gone, the MCP tools
|
|
61
|
+
* remain. (`mcp__*` does pass TOOL_NAME_PATTERN, but only Claude reads `*` as a wildcard; on pi it
|
|
62
|
+
* would be a literal name, so a harness-neutral profile cannot lean on it.)
|
|
63
|
+
*
|
|
64
|
+
* Four rules follow from the flags themselves:
|
|
65
|
+
*
|
|
66
|
+
* - On pi, `--tools` is a hard allowlist across all three tool categories (built-in, extension, and
|
|
67
|
+
* custom), not an addition to the built-in set. Measured: `--tools read` alone yields exactly
|
|
68
|
+
* `["read"]`, with the probe extension's own tool gone. No base flag is needed to make an
|
|
69
|
+
* allowlist restrict. `--no-tools` is emitted with it anyway, because it costs nothing, states
|
|
70
|
+
* the intent in the argv, and keeps the allowlist a ceiling if a future pi made `--tools`
|
|
71
|
+
* additive.
|
|
72
|
+
* - `--no-tools` is pi's empty-session flag, NOT `--no-builtin-tools`. `--no-builtin-tools` keeps
|
|
73
|
+
* extension and custom tools by design: measured alone it yields `["probe_marker_tool"]`, the
|
|
74
|
+
* extension tool, while `--no-tools` yields `[]`. An allowlist that `deny` empties must therefore
|
|
75
|
+
* project to `--no-tools` on pi and `--tools ""` on Claude (help: 'Use "" to disable all tools'),
|
|
76
|
+
* or the "zero tools" request would fail open and leave the whole builtin set available. The
|
|
77
|
+
* empty value is one empty-string argv element — no shell is involved, so it survives as such.
|
|
78
|
+
* - `deny` is ALWAYS emitted on both harnesses, including when `allow` is also set and has already
|
|
79
|
+
* removed those names. On Claude a bare name in `--disallowedTools` removes the tool from context
|
|
80
|
+
* per the docs, which makes the explicit denial an availability statement in its own right rather
|
|
81
|
+
* than a bet on the approval path. On pi it is belt and braces, since a tool left out of
|
|
82
|
+
* `--tools` does not exist. Either way it satisfies OFTR-003.10.4 ("denied tools MUST win when
|
|
83
|
+
* projected") without depending on the filter alone.
|
|
84
|
+
* - Claude's three flags are all variadic. Each consumes every following token until the next
|
|
85
|
+
* `--flag`, so they are emitted in a fixed order — `--tools`, `--allowedTools`,
|
|
86
|
+
* `--disallowedTools` — where each present flag is terminated by the next one, and the launch
|
|
87
|
+
* plan places the whole group first, ahead of the prompt flags, which terminate the last. They
|
|
88
|
+
* must never sit immediately before pass-through positionals, which would be eaten as tool names.
|
|
89
|
+
* pi's flags take one comma-joined value each, so they are not exposed to this.
|
|
90
|
+
*/
|
|
91
|
+
export declare const toolArgs: (harness: Harness, tools: ToolSelection) => readonly string[];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A projectable tool name. Kept in lockstep with `$defs/toolName` in `agent.schema.json`, which
|
|
3
|
+
* rejects the same shapes at the YAML read boundary; a unit test asserts the two stay equal.
|
|
4
|
+
*
|
|
5
|
+
* Three characters are excluded, because projection puts these names into harness argv:
|
|
6
|
+
*
|
|
7
|
+
* - A leading `-` makes the name an independent flag on Claude, where each entry becomes its own
|
|
8
|
+
* argv element. `allow: [Read, --dangerously-skip-permissions]` would not name a tool. It would
|
|
9
|
+
* turn off the permission system this element exists to configure. Profiles compose from remote
|
|
10
|
+
* catalogs and inheritance chains, so a tool name is not always written by the person who runs
|
|
11
|
+
* the agent, which makes this a supply-chain path to arbitrary launch flags.
|
|
12
|
+
* - A comma splits one name into several entries inside pi's `--tools a,b,c` value.
|
|
13
|
+
* - Whitespace is not a separator on either path today, because no shell is involved and pi gets a
|
|
14
|
+
* single argv element. It is rejected as conservatism, and it keeps names portable between the
|
|
15
|
+
* two harnesses. Note that this also excludes Claude's scoped permission rules, such as
|
|
16
|
+
* `Bash(npm run test:*)`. That is deliberate for a harness-neutral field: the same string is
|
|
17
|
+
* meaningless to pi. Relaxing the pattern later is backward compatible.
|
|
18
|
+
*/
|
|
19
|
+
export const TOOL_NAME_PATTERN = /^[^\s,-][^\s,]*$/;
|
|
20
|
+
/**
|
|
21
|
+
* Rejects a tool name that projection cannot carry as a name. This throws instead of filtering,
|
|
22
|
+
* because dropping an entry from a security control without saying so is the failure mode this
|
|
23
|
+
* element must not have. A selection that cannot be projected exactly as written must not launch.
|
|
24
|
+
*/
|
|
25
|
+
const assertProjectableToolNames = (tools) => {
|
|
26
|
+
const offending = invalidToolName(tools);
|
|
27
|
+
if (offending !== undefined) {
|
|
28
|
+
throw new Error(`tool name ${JSON.stringify(offending)} cannot be projected: ${TOOL_NAME_RULE}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/** The rule the pattern encodes, phrased for an error that a profile author reads. */
|
|
32
|
+
export const TOOL_NAME_RULE = "a tool name must not start with '-' or contain a comma or whitespace, because projection would make it a separate harness flag or split it into several names.";
|
|
33
|
+
/**
|
|
34
|
+
* The first tool name in the selection that projection cannot carry, or `undefined` when every name
|
|
35
|
+
* is projectable. Shared with the agent read boundary, so `config.json` overlays, which bypass the
|
|
36
|
+
* JSON Schema, are held to the same invariant.
|
|
37
|
+
*/
|
|
38
|
+
export const invalidToolName = (tools) => [...(tools?.allow ?? []), ...(tools?.deny ?? [])].find((name) => !TOOL_NAME_PATTERN.test(name));
|
|
39
|
+
/**
|
|
40
|
+
* The effective tool allowlist: `allow` minus `deny`, per OFTR-003.10.4 ("denied tools MUST win
|
|
41
|
+
* when projected"). `undefined` and `[]` mean different things and callers must keep them apart:
|
|
42
|
+
* `undefined` is "no allowlist declared", `[]` is "an allowlist that `deny` emptied".
|
|
43
|
+
*/
|
|
44
|
+
export const effectiveToolAllowlist = (tools) => {
|
|
45
|
+
const denied = new Set(tools?.deny ?? []);
|
|
46
|
+
return tools?.allow?.filter((tool) => !denied.has(tool));
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* pi and Claude Code BOTH have an availability control; Claude additionally has a permission layer
|
|
50
|
+
* on top of its own. The pi rows were measured against pi 0.83.0 with a probe extension that
|
|
51
|
+
* registers a uniquely-named tool and prints `pi.getActiveTools()` at `session_start`; the exact
|
|
52
|
+
* runs are recorded in the PR for #255. The Claude rows come from `claude --help` and
|
|
53
|
+
* https://code.claude.com/docs/en/cli-reference — documented behaviour, not locally measured.
|
|
54
|
+
*
|
|
55
|
+
* | | pi | claude |
|
|
56
|
+
* | availability ceiling | `--no-tools --tools a,b,c` (comma-joined) | `--tools a b c` (variadic; built-in set only) |
|
|
57
|
+
* | pre-approval within it | n/a — pi has no permission layer | `--allowedTools a b c` (variadic) |
|
|
58
|
+
* | denylist | `--exclude-tools a,b,c` (comma-joined) | `--disallowedTools a b c` (variadic; a bare name removes the tool from context, per docs) |
|
|
59
|
+
* | no tools at all | `--no-tools` | `--tools ""` (empties the built-in set only) |
|
|
60
|
+
*
|
|
61
|
+
* An allowlist therefore projects to TWO flags on Claude: `--tools` is the ceiling (the tool is not
|
|
62
|
+
* in the session), and `--allowedTools` pre-approves the same names within that ceiling so a
|
|
63
|
+
* headless session is not stopped by a permission prompt for the very tools the profile granted.
|
|
64
|
+
* Either flag alone would be wrong: `--allowedTools` alone leaves every unlisted builtin available
|
|
65
|
+
* (merely unapproved), and `--tools` alone leaves the granted tools prompting for approval.
|
|
66
|
+
*
|
|
67
|
+
* Scope caveat, from the CLI reference: `--tools` governs the BUILT-IN set only. MCP tools
|
|
68
|
+
* (`mcp__server__*`) are untouched by it — they are governed by which servers load (the `mcp`
|
|
69
|
+
* loadout element) and by `--disallowedTools` patterns such as `mcp__*`. So `--tools ""` is not
|
|
70
|
+
* pi's zero-tool session when MCP servers are selected: the builtins are gone, the MCP tools
|
|
71
|
+
* remain. (`mcp__*` does pass TOOL_NAME_PATTERN, but only Claude reads `*` as a wildcard; on pi it
|
|
72
|
+
* would be a literal name, so a harness-neutral profile cannot lean on it.)
|
|
73
|
+
*
|
|
74
|
+
* Four rules follow from the flags themselves:
|
|
75
|
+
*
|
|
76
|
+
* - On pi, `--tools` is a hard allowlist across all three tool categories (built-in, extension, and
|
|
77
|
+
* custom), not an addition to the built-in set. Measured: `--tools read` alone yields exactly
|
|
78
|
+
* `["read"]`, with the probe extension's own tool gone. No base flag is needed to make an
|
|
79
|
+
* allowlist restrict. `--no-tools` is emitted with it anyway, because it costs nothing, states
|
|
80
|
+
* the intent in the argv, and keeps the allowlist a ceiling if a future pi made `--tools`
|
|
81
|
+
* additive.
|
|
82
|
+
* - `--no-tools` is pi's empty-session flag, NOT `--no-builtin-tools`. `--no-builtin-tools` keeps
|
|
83
|
+
* extension and custom tools by design: measured alone it yields `["probe_marker_tool"]`, the
|
|
84
|
+
* extension tool, while `--no-tools` yields `[]`. An allowlist that `deny` empties must therefore
|
|
85
|
+
* project to `--no-tools` on pi and `--tools ""` on Claude (help: 'Use "" to disable all tools'),
|
|
86
|
+
* or the "zero tools" request would fail open and leave the whole builtin set available. The
|
|
87
|
+
* empty value is one empty-string argv element — no shell is involved, so it survives as such.
|
|
88
|
+
* - `deny` is ALWAYS emitted on both harnesses, including when `allow` is also set and has already
|
|
89
|
+
* removed those names. On Claude a bare name in `--disallowedTools` removes the tool from context
|
|
90
|
+
* per the docs, which makes the explicit denial an availability statement in its own right rather
|
|
91
|
+
* than a bet on the approval path. On pi it is belt and braces, since a tool left out of
|
|
92
|
+
* `--tools` does not exist. Either way it satisfies OFTR-003.10.4 ("denied tools MUST win when
|
|
93
|
+
* projected") without depending on the filter alone.
|
|
94
|
+
* - Claude's three flags are all variadic. Each consumes every following token until the next
|
|
95
|
+
* `--flag`, so they are emitted in a fixed order — `--tools`, `--allowedTools`,
|
|
96
|
+
* `--disallowedTools` — where each present flag is terminated by the next one, and the launch
|
|
97
|
+
* plan places the whole group first, ahead of the prompt flags, which terminate the last. They
|
|
98
|
+
* must never sit immediately before pass-through positionals, which would be eaten as tool names.
|
|
99
|
+
* pi's flags take one comma-joined value each, so they are not exposed to this.
|
|
100
|
+
*/
|
|
101
|
+
export const toolArgs = (harness, tools) => {
|
|
102
|
+
// Before filtering, so a poisoned name is rejected even when `deny` would have removed it, and on
|
|
103
|
+
// both harnesses: `projectComposition` is exported and reachable without schema validation, so
|
|
104
|
+
// this cannot assume the read boundary already ran.
|
|
105
|
+
assertProjectableToolNames(tools);
|
|
106
|
+
const allow = effectiveToolAllowlist(tools);
|
|
107
|
+
const deny = tools.deny ?? [];
|
|
108
|
+
if (harness === 'pi') {
|
|
109
|
+
return [
|
|
110
|
+
// No allowlist declared means no ceiling requested; `deny` below still applies.
|
|
111
|
+
...(allow === undefined ? [] : allow.length === 0 ? ['--no-tools'] : ['--no-tools', '--tools', allow.join(',')]),
|
|
112
|
+
...(deny.length === 0 ? [] : ['--exclude-tools', deny.join(',')]),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
return [
|
|
116
|
+
// The ceiling and the pre-approval within it. An allowlist emptied by `deny` projects to
|
|
117
|
+
// `--tools ""` — one empty-string argv element, Claude's documented "disable all tools" form —
|
|
118
|
+
// with no `--allowedTools`, because there is nothing left to pre-approve. Only the builtin set
|
|
119
|
+
// is emptied: MCP tools from selected servers survive `--tools ""` (see the scope caveat
|
|
120
|
+
// above), so this is close to, but not exactly, pi's zero-tool session.
|
|
121
|
+
...(allow === undefined
|
|
122
|
+
? []
|
|
123
|
+
: allow.length === 0
|
|
124
|
+
? ['--tools', '']
|
|
125
|
+
: ['--tools', ...allow, '--allowedTools', ...allow]),
|
|
126
|
+
...(deny.length === 0 ? [] : ['--disallowedTools', ...deny]),
|
|
127
|
+
];
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=Tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tools.js","sourceRoot":"","sources":["../../src/projection/Tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,17 @@ interface FrontmatterSplit {
|
|
|
32
32
|
}
|
|
33
33
|
/** Splits leading `---` YAML frontmatter from the markdown body. */
|
|
34
34
|
export declare const splitFrontmatter: (content: string) => FrontmatterSplit | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The defect in a RAW `tools` value, before any normalization, or `undefined` when the shape is
|
|
37
|
+
* exactly `{allow?: string[], deny?: string[]}` with non-empty entries. This runs at the read
|
|
38
|
+
* boundary because silent normalization fails open: `asStringArray` turns an unknown key, a string
|
|
39
|
+
* where an array was meant, or a non-string entry into an empty or partial selection, and the
|
|
40
|
+
* agent then launches WITHOUT the restriction the author wrote — under `--strict` included, since
|
|
41
|
+
* nothing is left to warn about. A malformed selection must be a hard error naming the defect.
|
|
42
|
+
* `agent.md` frontmatter is shape-checked by `agent.schema.json`; this is the same invariant for
|
|
43
|
+
* the `config.json` overlay path that bypasses the schema.
|
|
44
|
+
*/
|
|
45
|
+
export declare const toolsShapeDefect: (tools: unknown) => string | undefined;
|
|
35
46
|
/**
|
|
36
47
|
* Parses an agent definition. `configPaths` are highest-precedence first; each is a loadout-only
|
|
37
48
|
* override that JSON-merges by key across layers over the frontmatter loadout.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Parses `agents/<id>/agent.md` frontmatter and merges per-layer config.json loadout overrides.
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { TOOL_NAME_RULE, invalidToolName } from '../projection/Tools.js';
|
|
3
4
|
import { validateSchema } from '../validation/SchemaValidator.js';
|
|
4
5
|
import { parseYamlDocument } from '../validation/YamlDocument.js';
|
|
5
6
|
import { isPromptSourceReference } from '../composer/PromptSource.js';
|
|
@@ -53,20 +54,82 @@ const promptControlsFromRecord = (record) => ({
|
|
|
53
54
|
promptTemplate: isPromptSourceReference(record.prompt_template) ? record.prompt_template : undefined,
|
|
54
55
|
});
|
|
55
56
|
const readMarkdownHeading = (body) => /^#\s+(.+)$/mu.exec(body)?.[1]?.trim();
|
|
56
|
-
const loadoutFromRecord = (record) => {
|
|
57
|
-
|
|
57
|
+
const loadoutFromRecord = (record) => ({
|
|
58
|
+
...emptyLoadout(),
|
|
59
|
+
skills: asStringArray(record.skills),
|
|
60
|
+
subagents: asStringArray(record.subagents),
|
|
61
|
+
mcp: asStringArray(record.mcp),
|
|
62
|
+
extensions: asStringArray(record.extensions),
|
|
63
|
+
plugins: asStringArray(record.plugins),
|
|
64
|
+
model: asString(record.model),
|
|
65
|
+
thinking: asString(record.thinking),
|
|
66
|
+
tools: toolSelectionFromRecord(record.tools),
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Narrows a raw `tools` value that `toolsShapeDefect` has already accepted. Key presence is
|
|
70
|
+
* preserved: an absent `allow` stays absent, because "no allowlist declared" and "an empty
|
|
71
|
+
* allowlist" project differently and normalizing one into the other would fabricate a ceiling.
|
|
72
|
+
*/
|
|
73
|
+
const toolSelectionFromRecord = (value) => {
|
|
74
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
75
|
+
return undefined;
|
|
76
|
+
const record = value;
|
|
58
77
|
return {
|
|
59
|
-
...
|
|
60
|
-
|
|
61
|
-
subagents: asStringArray(record.subagents),
|
|
62
|
-
mcp: asStringArray(record.mcp),
|
|
63
|
-
extensions: asStringArray(record.extensions),
|
|
64
|
-
plugins: asStringArray(record.plugins),
|
|
65
|
-
model: asString(record.model),
|
|
66
|
-
thinking: asString(record.thinking),
|
|
67
|
-
tools: tools === undefined ? undefined : { allow: asStringArray(tools.allow), deny: asStringArray(tools.deny) },
|
|
78
|
+
...(record.allow === undefined ? {} : { allow: asStringArray(record.allow) }),
|
|
79
|
+
...(record.deny === undefined ? {} : { deny: asStringArray(record.deny) }),
|
|
68
80
|
};
|
|
69
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* The defect in a RAW `tools` value, before any normalization, or `undefined` when the shape is
|
|
84
|
+
* exactly `{allow?: string[], deny?: string[]}` with non-empty entries. This runs at the read
|
|
85
|
+
* boundary because silent normalization fails open: `asStringArray` turns an unknown key, a string
|
|
86
|
+
* where an array was meant, or a non-string entry into an empty or partial selection, and the
|
|
87
|
+
* agent then launches WITHOUT the restriction the author wrote — under `--strict` included, since
|
|
88
|
+
* nothing is left to warn about. A malformed selection must be a hard error naming the defect.
|
|
89
|
+
* `agent.md` frontmatter is shape-checked by `agent.schema.json`; this is the same invariant for
|
|
90
|
+
* the `config.json` overlay path that bypasses the schema.
|
|
91
|
+
*/
|
|
92
|
+
export const toolsShapeDefect = (tools) => {
|
|
93
|
+
if (tools === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (tools === null || typeof tools !== 'object' || Array.isArray(tools)) {
|
|
96
|
+
return `\`tools\` must be an object of the form {allow?: [...], deny?: [...]}, not ${describeValue(tools)}.`;
|
|
97
|
+
}
|
|
98
|
+
const record = tools;
|
|
99
|
+
const unknownKey = Object.keys(record).find((key) => key !== 'allow' && key !== 'deny');
|
|
100
|
+
if (unknownKey !== undefined) {
|
|
101
|
+
return `\`tools\` has unknown key ${JSON.stringify(unknownKey)}; only "allow" and "deny" are accepted.`;
|
|
102
|
+
}
|
|
103
|
+
return toolListDefect('allow', record.allow) ?? toolListDefect('deny', record.deny);
|
|
104
|
+
};
|
|
105
|
+
/** Names the value's kind for a shape-defect message. */
|
|
106
|
+
const describeValue = (value) => value === null ? 'null' : Array.isArray(value) ? 'an array' : `a ${typeof value}`;
|
|
107
|
+
/** The defect in one raw `tools.allow`/`tools.deny` list, or `undefined` when it is well formed. */
|
|
108
|
+
const toolListDefect = (key, value) => {
|
|
109
|
+
if (value === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (!Array.isArray(value)) {
|
|
112
|
+
return `\`tools.${key}\` must be an array of tool names, not ${describeValue(value)}.`;
|
|
113
|
+
}
|
|
114
|
+
const badIndex = value.findIndex((entry) => typeof entry !== 'string' || entry === '');
|
|
115
|
+
return badIndex === -1
|
|
116
|
+
? undefined
|
|
117
|
+
: `\`tools.${key}[${badIndex}]\` must be a non-empty string, not ${JSON.stringify(value[badIndex])}.`;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Reports a tool name in the merged loadout that projection cannot carry. The shape is already
|
|
121
|
+
* guaranteed here — frontmatter by `agent.schema.json`, each `config.json` layer by
|
|
122
|
+
* `toolsShapeDefect` in `readConfigLoadout` — but the *names* must be re-checked on the merged
|
|
123
|
+
* record, because a `config.json` overlay can replace `tools` wholesale after the schema check.
|
|
124
|
+
*/
|
|
125
|
+
const toolsIssue = (record, path) => {
|
|
126
|
+
if (record.tools === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
const offending = invalidToolName(toolSelectionFromRecord(record.tools));
|
|
129
|
+
return offending === undefined
|
|
130
|
+
? undefined
|
|
131
|
+
: { path, message: `declares an unusable tool name ${JSON.stringify(offending)}: ${TOOL_NAME_RULE}` };
|
|
132
|
+
};
|
|
70
133
|
/** Reads one config.json, restricting it to loadout keys; parse/read/non-object failures are issues. */
|
|
71
134
|
const readConfigLoadout = (configPath) => {
|
|
72
135
|
let parsed;
|
|
@@ -79,7 +142,16 @@ const readConfigLoadout = (configPath) => {
|
|
|
79
142
|
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
80
143
|
return { path: configPath, message: 'config.json must be a JSON object of loadout overrides.' };
|
|
81
144
|
}
|
|
82
|
-
|
|
145
|
+
const picked = pickLoadoutKeys(parsed);
|
|
146
|
+
// Validate the RAW `tools` shape per layer, before any normalization and before the merge can
|
|
147
|
+
// hide which file is malformed. See `toolsShapeDefect` for why silent normalization fails open.
|
|
148
|
+
if ('tools' in picked && picked.tools !== undefined) {
|
|
149
|
+
const defect = toolsShapeDefect(picked.tools);
|
|
150
|
+
if (defect !== undefined) {
|
|
151
|
+
return { path: configPath, message: `config.json declares a malformed tool selection: ${defect}` };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return picked;
|
|
83
155
|
};
|
|
84
156
|
const parseFrontmatterRecord = (content, agentPath) => {
|
|
85
157
|
const split = splitFrontmatter(content);
|
|
@@ -119,6 +191,13 @@ export const parseAgentDefinition = (content, configPaths, agentPath) => {
|
|
|
119
191
|
}
|
|
120
192
|
merged = { ...merged, ...config };
|
|
121
193
|
}
|
|
194
|
+
// The JSON Schema only sees the frontmatter, and a config.json overlay can replace `tools`
|
|
195
|
+
// wholesale after that check. Validate the merged loadout so an unprojectable tool name is an
|
|
196
|
+
// error from `outfitter validate`, not a surprise at launch.
|
|
197
|
+
const toolIssue = toolsIssue(merged, configPaths[0] ?? agentPath);
|
|
198
|
+
if (toolIssue !== undefined) {
|
|
199
|
+
return toolIssue;
|
|
200
|
+
}
|
|
122
201
|
return {
|
|
123
202
|
name: frontmatter.record.name,
|
|
124
203
|
label: asString(frontmatter.record.label)?.trim() || readMarkdownHeading(frontmatter.body),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentDefinition.js","sourceRoot":"","sources":["../../src/resolver/AgentDefinition.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"AgentDefinition.js","sourceRoot":"","sources":["../../src/resolver/AgentDefinition.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,14 +56,20 @@
|
|
|
56
56
|
"tools": {
|
|
57
57
|
"type": "object",
|
|
58
58
|
"properties": {
|
|
59
|
-
"allow": { "type": "array", "items": { "
|
|
60
|
-
"deny": { "type": "array", "items": { "
|
|
59
|
+
"allow": { "type": "array", "items": { "$ref": "#/$defs/toolName" } },
|
|
60
|
+
"deny": { "type": "array", "items": { "$ref": "#/$defs/toolName" } }
|
|
61
61
|
},
|
|
62
62
|
"additionalProperties": false
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"additionalProperties": true,
|
|
66
66
|
"$defs": {
|
|
67
|
+
"toolName": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"minLength": 1,
|
|
70
|
+
"pattern": "^[^\\s,-][^\\s,]*$",
|
|
71
|
+
"description": "A tool name. A leading '-' would become an independent harness flag when projected, and a comma or whitespace would split the name inside a projected flag value, so all three are rejected."
|
|
72
|
+
},
|
|
67
73
|
"slugList": {
|
|
68
74
|
"type": "array",
|
|
69
75
|
"items": { "type": "string", "minLength": 1 }
|
|
@@ -33,6 +33,7 @@ Outfitter lays out conventions for iterating on and sharing agent configuration
|
|
|
33
33
|
The same composition runs on every surface; only the trigger changes.
|
|
34
34
|
|
|
35
35
|
- [Running an agent in GitHub Actions](./actions.md) — headless runs on any workflow trigger.
|
|
36
|
+
- [Container images](./containers.md) — run the published Debian-based image persistently, extend it with apt, or use the `-nix` variant.
|
|
36
37
|
- [Recurring runs](./recurring-runs.md) — loops three ways: the local loop extension, Actions cron, cluster schedules.
|
|
37
38
|
- [In-cluster agents](./in-cluster.md) — resident agents, CronJobs, and subagent Jobs via Link Operator.
|
|
38
39
|
- [Hooks](./hooks.md) — harness hook wiring and the protocol gap.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Container images
|
|
2
|
+
|
|
3
|
+
The published image is a generic Outfitter runtime:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
ghcr.io/ai-outfitter/outfitter:<version>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
It is Debian-based (`node:24-slim`), uses `outfitter` as its entrypoint, and
|
|
10
|
+
includes Node.js, npm, Git, SSH, and CA certificates at their conventional
|
|
11
|
+
Debian paths. It does not include agent profiles, credentials, channels, MCP
|
|
12
|
+
servers, or other use-case behavior. The default runtime user and group are
|
|
13
|
+
both `1000` (named `outfitter`), with `/tmp` as the home directory and
|
|
14
|
+
`/workspace` as the working directory.
|
|
15
|
+
|
|
16
|
+
A Nix closure variant of the image is also published under the `-nix` suffix;
|
|
17
|
+
see [the `-nix` variant](#the--nix-variant) below.
|
|
18
|
+
|
|
19
|
+
## Run a resident agent
|
|
20
|
+
|
|
21
|
+
A resident container is an ordinary `outfitter run` whose harness stays in RPC
|
|
22
|
+
mode. Keep standard input open so the harness remains available while its
|
|
23
|
+
extensions wait for work:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
apiVersion: apps/v1
|
|
27
|
+
kind: Deployment
|
|
28
|
+
metadata:
|
|
29
|
+
name: example-agent
|
|
30
|
+
spec:
|
|
31
|
+
replicas: 1
|
|
32
|
+
selector:
|
|
33
|
+
matchLabels:
|
|
34
|
+
app: example-agent
|
|
35
|
+
template:
|
|
36
|
+
metadata:
|
|
37
|
+
labels:
|
|
38
|
+
app: example-agent
|
|
39
|
+
spec:
|
|
40
|
+
securityContext:
|
|
41
|
+
# Pod-level, and not optional. HOME points at the mounted volume, and a
|
|
42
|
+
# freshly provisioned volume arrives owned by root — runAsUser does not
|
|
43
|
+
# change that. Extension installs and credential persistence both write
|
|
44
|
+
# below HOME, so without fsGroup the agent fails with EACCES on first
|
|
45
|
+
# write rather than at startup. A volume plugin that ignores fsGroup
|
|
46
|
+
# must be pre-provisioned with UID/GID 1000 ownership instead.
|
|
47
|
+
fsGroup: 1000
|
|
48
|
+
containers:
|
|
49
|
+
- name: agent
|
|
50
|
+
# The primary Debian-based image; append -nix for the Nix variant.
|
|
51
|
+
image: ghcr.io/ai-outfitter/outfitter:<version>
|
|
52
|
+
stdin: true
|
|
53
|
+
workingDir: /workspace
|
|
54
|
+
securityContext:
|
|
55
|
+
runAsNonRoot: true
|
|
56
|
+
runAsUser: 1000
|
|
57
|
+
runAsGroup: 1000
|
|
58
|
+
env:
|
|
59
|
+
- name: HOME
|
|
60
|
+
value: /workspace
|
|
61
|
+
args:
|
|
62
|
+
- run
|
|
63
|
+
- example-agent
|
|
64
|
+
- --strict
|
|
65
|
+
- --
|
|
66
|
+
- --mode
|
|
67
|
+
- rpc
|
|
68
|
+
- --no-session
|
|
69
|
+
volumeMounts:
|
|
70
|
+
- name: workspace
|
|
71
|
+
mountPath: /workspace
|
|
72
|
+
volumes:
|
|
73
|
+
- name: workspace
|
|
74
|
+
persistentVolumeClaim:
|
|
75
|
+
claimName: example-agent
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Mount `.agents` settings, credentials, and any channel configuration through
|
|
79
|
+
the workload that owns the container. The image does not interpret Kubernetes
|
|
80
|
+
resources or impose image, profile, or extension policy.
|
|
81
|
+
|
|
82
|
+
## Build a derivative image
|
|
83
|
+
|
|
84
|
+
The image is a normal Debian base: extend it with an ordinary Dockerfile.
|
|
85
|
+
`apt-get` works, and so does `COPY`ing binaries. A dynamically linked binary
|
|
86
|
+
runs when it matches the image — same architecture, glibc-linked, and its
|
|
87
|
+
shared-library dependencies present. The standard ELF interpreter is where
|
|
88
|
+
tools expect it (unlike the `-nix` variant), but the slim base ships a small
|
|
89
|
+
library set: `apt-get install` a binary's runtime libraries when it needs more.
|
|
90
|
+
Switch to `root` for the layers that install, then drop back to `1000`:
|
|
91
|
+
|
|
92
|
+
```dockerfile
|
|
93
|
+
FROM ghcr.io/ai-outfitter/outfitter:<version>
|
|
94
|
+
|
|
95
|
+
USER root
|
|
96
|
+
RUN apt-get update \
|
|
97
|
+
&& apt-get install -y --no-install-recommends jq ripgrep \
|
|
98
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
99
|
+
COPY --chmod=0755 my-tool /usr/local/bin/my-tool
|
|
100
|
+
USER 1000
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Build and exercise the exact image:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
docker build -t example-agent .
|
|
107
|
+
docker run --rm example-agent --version
|
|
108
|
+
docker run --rm --entrypoint /bin/sh example-agent \
|
|
109
|
+
-c 'jq --version && rg --version && my-tool --version'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The entrypoint stays `outfitter`; override `ENTRYPOINT` only when the derived
|
|
113
|
+
image wraps the launch itself.
|
|
114
|
+
|
|
115
|
+
## The `-nix` variant
|
|
116
|
+
|
|
117
|
+
The Nix closure image that was previously the primary tag remains published
|
|
118
|
+
for `lib.mkContainer` consumers:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
ghcr.io/ai-outfitter/outfitter:<version>-nix
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It is built by the flake, includes the Nix CLI, Bash, core utilities, Git,
|
|
125
|
+
SSH, and CA certificates, and its entrypoint is an absolute `/nix/store` path.
|
|
126
|
+
It is not conventionally extensible — there is no apt, and foreign dynamic
|
|
127
|
+
binaries do not run — so extend it through Nix instead: the flake exports
|
|
128
|
+
`lib.mkContainer` for reproducible derivative images:
|
|
129
|
+
|
|
130
|
+
```nix
|
|
131
|
+
{
|
|
132
|
+
inputs = {
|
|
133
|
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
134
|
+
outfitter = {
|
|
135
|
+
url = "github:ai-outfitter/outfitter/v1.4.0";
|
|
136
|
+
inputs.nixpkgs.follows = "nixpkgs";
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
outputs =
|
|
141
|
+
{ nixpkgs, outfitter, ... }:
|
|
142
|
+
let
|
|
143
|
+
system = "x86_64-linux";
|
|
144
|
+
pkgs = nixpkgs.legacyPackages.${system};
|
|
145
|
+
in
|
|
146
|
+
{
|
|
147
|
+
packages.${system}.default = outfitter.lib.mkContainer {
|
|
148
|
+
inherit pkgs;
|
|
149
|
+
outfitterPackage = outfitter.packages.${system}.outfitter;
|
|
150
|
+
name = "example-agent";
|
|
151
|
+
extraPackages = [
|
|
152
|
+
pkgs.jq
|
|
153
|
+
pkgs.ripgrep
|
|
154
|
+
];
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Build and exercise the exact image:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
nix build
|
|
164
|
+
docker load < result
|
|
165
|
+
docker run --rm example-agent:latest --version
|
|
166
|
+
docker run --rm --entrypoint /bin/sh example-agent:latest \
|
|
167
|
+
-c 'nix --version && jq --version && rg --version'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Prefer adding known runtime packages through `extraPackages`. The resulting
|
|
171
|
+
image stays reproducible, and it avoids the trap below.
|
|
172
|
+
|
|
173
|
+
**Do not mount an empty volume over `/nix` of the `-nix` variant.** That image
|
|
174
|
+
_is_ its Nix store: the entrypoint is an absolute store path and every binary
|
|
175
|
+
in `/bin` is a symlink into `/nix/store`. Mounting a fresh volume there hides
|
|
176
|
+
all of it, so the container cannot start — it fails before it could initialize
|
|
177
|
+
the very store you mounted the volume to populate. (The primary Debian image
|
|
178
|
+
has no `/nix` and is not affected.)
|
|
179
|
+
|
|
180
|
+
Runtime installation in the `-nix` variant therefore needs one of:
|
|
181
|
+
|
|
182
|
+
- a volume **pre-populated** with the image's closure, seeded from the image
|
|
183
|
+
before the agent starts (an init container copying `/nix` into the volume);
|
|
184
|
+
- an **overlay** whose lower layer is the image's `/nix`, so the closure stays
|
|
185
|
+
visible while writes land in the upper layer; or
|
|
186
|
+
- writable Nix **state** only — `/nix/var` and a per-user profile — leaving the
|
|
187
|
+
store itself as the image shipped it.
|
|
@@ -28,7 +28,7 @@ Tasks and bake are not in this matrix — they are the subject of a [separate up
|
|
|
28
28
|
| Credentials and environment | Supported | Supported |
|
|
29
29
|
| DeepWork job selection | Supported | Roadmap |
|
|
30
30
|
| Hooks | Partial | Partial |
|
|
31
|
-
| Tool availability
|
|
31
|
+
| Tool availability (agent `tools:` loadout) | Supported | Supported |
|
|
32
32
|
| Theme / UI presentation | Roadmap | Roadmap |
|
|
33
33
|
| Working directory | Roadmap | Roadmap |
|
|
34
34
|
| Pass-through arguments | Supported | Supported |
|
|
@@ -41,6 +41,7 @@ Tasks and bake are not in this matrix — they are the subject of a [separate up
|
|
|
41
41
|
- **Skills (Partial)** — selected skills are materialized into the config directory's skills surface; remaining gaps are tracked per release. The bundled Outfitter skill ships through the plugin channel.
|
|
42
42
|
- **Model selection (Partial)** — model maps to `--model` and thinking level to `--effort`; provider selection is not projected for Claude and warns if requested.
|
|
43
43
|
- **Hooks (Partial)** — hook configuration is projected into the generated `settings.json`; there is no portable protocol hooks resource yet. See [Hooks](./hooks.md).
|
|
44
|
+
- **Tool availability** — `tools.allow` (after `tools.deny` removes entries) maps to both `--tools` (_availability_: an unlisted builtin is not in the session) and `--allowedTools` (_permission_: the granted tools are pre-approved, so a headless session is not stopped by a prompt); `tools.deny` always maps to `--disallowedTools`, including when both are declared, and a bare denied name removes the tool from context per Claude's docs. An allowlist that `tools.deny` empties maps to `--tools ""`, Claude's documented "disable all tools" form. Caveat: per the CLI reference, `--tools` governs the built-in set only — MCP tools (`mcp__server__*`) are unaffected and are governed by which MCP servers the loadout selects, so `--tools ""` is not exactly pi's zero-tool session when MCP servers are present. Claude's behavior here comes from `claude --help` and the CLI reference, not local measurement.
|
|
44
45
|
- **DeepWork jobs** — job selection is Pi-only today and warns on Claude.
|
|
45
46
|
- **Bundled Outfitter skill** — every launch also publishes Outfitter's own self-documentation skill as a bundled plugin, so the agent can explain Outfitter and this launch's configuration.
|
|
46
47
|
|
|
@@ -49,6 +50,7 @@ Tasks and bake are not in this matrix — they are the subject of a [separate up
|
|
|
49
50
|
- Pi projects the full resource set: agent identity, subagents (via the subagent extension), skills (`--skill`), commands, model configuration, MCP, extensions (`--extension`) and plugins as first-class loadout elements, environment, pass-through args, session directory, and DeepWork job selection.
|
|
50
51
|
- Selected skills resolve across layers following [layer precedence](./concepts.md#layer-precedence); `references`, `scripts`, and `assets` frontmatter materialize into a generated skill passed via `--skill`. `outfitter validate` checks selections and references before launch.
|
|
51
52
|
- **Hooks (Partial)** — bootstrap behavior uses an explicit Pi extension via `--extension`; recurring per-event hooks are extension territory. See [Hooks](./hooks.md).
|
|
53
|
+
- **Tool availability** — `tools.allow` (after `tools.deny` removes entries) maps to `--no-tools --tools a,b,c`, and `tools.deny` maps to `--exclude-tools a,b,c`. `--tools` is a hard allowlist across built-in, extension, and custom tools, so the session's tool set is exactly that list. An allowlist that `tools.deny` empties maps to `--no-tools` alone, a session with no tools at all. Note that `--no-builtin-tools` is deliberately not used: it keeps extension and custom tools enabled, so it does not express an empty tool set.
|
|
52
54
|
- Every launch also passes Outfitter's own self-documentation skill through `--skill`.
|
|
53
55
|
|
|
54
56
|
For the architecture-level definitions behind each row, see [Controllable elements](../architecture/controllable-elements.md).
|
package/package.json
CHANGED
|
@@ -56,14 +56,20 @@
|
|
|
56
56
|
"tools": {
|
|
57
57
|
"type": "object",
|
|
58
58
|
"properties": {
|
|
59
|
-
"allow": { "type": "array", "items": { "
|
|
60
|
-
"deny": { "type": "array", "items": { "
|
|
59
|
+
"allow": { "type": "array", "items": { "$ref": "#/$defs/toolName" } },
|
|
60
|
+
"deny": { "type": "array", "items": { "$ref": "#/$defs/toolName" } }
|
|
61
61
|
},
|
|
62
62
|
"additionalProperties": false
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"additionalProperties": true,
|
|
66
66
|
"$defs": {
|
|
67
|
+
"toolName": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"minLength": 1,
|
|
70
|
+
"pattern": "^[^\\s,-][^\\s,]*$",
|
|
71
|
+
"description": "A tool name. A leading '-' would become an independent harness flag when projected, and a comma or whitespace would split the name inside a projected flag value, so all three are rejected."
|
|
72
|
+
},
|
|
67
73
|
"slugList": {
|
|
68
74
|
"type": "array",
|
|
69
75
|
"items": { "type": "string", "minLength": 1 }
|