external-subagent 0.4.0 → 0.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/README.md +83 -19
- package/bin/external-subagent-debug-mcp.mjs +2 -1
- package/bin/external-subagent-mcp.mjs +36 -11
- package/cli/commands/config.mjs +6 -1
- package/cli/commands/daemon.mjs +12 -2
- package/cli/commands/tasks.mjs +9 -0
- package/cli/config/write.mjs +21 -3
- package/cli/constants.mjs +35 -1
- package/cli/install/init.mjs +30 -11
- package/cli/install/layout.mjs +11 -4
- package/cli/install/path.mjs +35 -10
- package/cli/install/payload.mjs +86 -6
- package/cli/install/service-activation.mjs +133 -1
- package/cli/install/service-linux.mjs +327 -0
- package/cli/install/service-macos.mjs +59 -18
- package/cli/install/update.mjs +7 -1
- package/cli/main.mjs +25 -11
- package/cli/maintenance.mjs +16 -11
- package/cli/paths.mjs +57 -8
- package/npm/native/darwin-arm64/external-subagent-mcp +0 -0
- package/npm/native/darwin-arm64/external-subagentd +0 -0
- package/npm/native/darwin-arm64/payload.json +4 -4
- package/npm/native/linux-x64/external-subagent-mcp +0 -0
- package/npm/native/linux-x64/external-subagentd +0 -0
- package/npm/native/linux-x64/payload.json +20 -0
- package/package.json +6 -3
- package/plugins/codex/external-subagent/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/external-subagent/scripts/policy-verifier.mjs +16 -2
- package/scripts/release/build-native-payload.mjs +18 -6
- package/scripts/release/check-native-tarball.mjs +99 -31
- package/scripts/release/test-installed-tarball.mjs +6 -1
|
@@ -3,26 +3,47 @@
|
|
|
3
3
|
// check-native-tarball.mjs [package.tgz] verify a packed tarball (entries,
|
|
4
4
|
// payload manifest consistency,
|
|
5
5
|
// forbidden development material)
|
|
6
|
-
// check-native-tarball.mjs --staged verify only the staged
|
|
7
|
-
// (npm/native
|
|
6
|
+
// check-native-tarball.mjs --staged verify only the staged payloads
|
|
7
|
+
// (npm/native/<platform>) against
|
|
8
8
|
// the package version — the publish
|
|
9
9
|
// gate, since `npm publish` packs
|
|
10
10
|
// into a private temp directory no
|
|
11
11
|
// lifecycle script can inspect.
|
|
12
|
+
// A released tarball carries every supported platform payload (darwin-arm64
|
|
13
|
+
// and linux-x64), so both entry sets and both manifests are required in either
|
|
14
|
+
// mode: a tarball missing one platform is rejected. Version agreement with the
|
|
15
|
+
// packed package is enforced for the running host's payload (prepack rebuilds
|
|
16
|
+
// only the host payload from source) and, in --staged mode, for every staged
|
|
17
|
+
// platform — the merged publish tree where the foreign payload comes from CI's
|
|
18
|
+
// other platform job. Image checks (Mach-O arm64 / ELF x86-64) and the 755
|
|
19
|
+
// mode check use the same probes and layout table as cli/install/payload.mjs,
|
|
20
|
+
// so this gate cannot drift from what an installed package verifies.
|
|
12
21
|
// Without --staged the tarball is resolved from: the explicit argument, the
|
|
13
22
|
// npm --pack-destination directory (set during `npm pack` lifecycles), or the
|
|
14
23
|
// newest matching tgz in the working directory. A dry run (npm pack --dry-run)
|
|
15
24
|
// or a publish (no local tarball) skips the tarball checks and still runs the
|
|
16
25
|
// staged payload checks.
|
|
17
26
|
import fs from 'node:fs';
|
|
18
|
-
import os from 'node:os';
|
|
19
27
|
import path from 'node:path';
|
|
20
28
|
import { spawnSync } from 'node:child_process';
|
|
29
|
+
import { elfArch, machoArch } from '../../cli/install/payload.mjs';
|
|
30
|
+
import { nativePlatform } from '../../cli/install/layout.mjs';
|
|
21
31
|
|
|
22
32
|
const stagedOnly = process.argv.includes('--staged');
|
|
23
33
|
const explicit = process.argv[2] && !process.argv[2].startsWith('--') ? process.argv[2] : null;
|
|
24
34
|
const dryRun = process.env.npm_config_dry_run === 'true';
|
|
25
35
|
|
|
36
|
+
// Every platform the single release tarball ships, and the image identity each
|
|
37
|
+
// payload must carry. Keeping both lists here (rather than a fixed
|
|
38
|
+
// darwin-arm64 path) is what makes the gate dual-platform.
|
|
39
|
+
const RELEASE_PLATFORMS = Object.freeze(['darwin-arm64', 'linux-x64']);
|
|
40
|
+
const BINARIES = Object.freeze(['external-subagentd', 'external-subagent-mcp']);
|
|
41
|
+
const IMAGE_CHECKS = Object.freeze({
|
|
42
|
+
'darwin-arm64': { describe: 'Mach-O arm64 executable', probe: machoArch, arch: 'arm64' },
|
|
43
|
+
'linux-x64': { describe: 'ELF x86-64 executable', probe: elfArch, arch: 'x64' },
|
|
44
|
+
});
|
|
45
|
+
const hostPlatform = nativePlatform();
|
|
46
|
+
|
|
26
47
|
function findTarball() {
|
|
27
48
|
if (explicit) return explicit;
|
|
28
49
|
const candidates = [];
|
|
@@ -57,14 +78,17 @@ if (!stagedOnly) {
|
|
|
57
78
|
'package/bin/external-subagent-debug.mjs',
|
|
58
79
|
'package/bin/external-subagent-debug-mcp.mjs',
|
|
59
80
|
'package/cli/main.mjs',
|
|
60
|
-
'package/npm/native/darwin-arm64/external-subagentd',
|
|
61
|
-
'package/npm/native/darwin-arm64/external-subagent-mcp',
|
|
62
|
-
'package/npm/native/darwin-arm64/payload.json',
|
|
63
81
|
'package/plugins/codex/external-subagent/.codex-plugin/plugin.json',
|
|
64
82
|
'package/plugins/codex/external-subagent/.mcp.json',
|
|
65
83
|
'package/launchd/com.external-subagent.daemon.plist.template',
|
|
66
84
|
'package/LICENSE',
|
|
67
85
|
];
|
|
86
|
+
// Both platform payloads are mandatory: a tarball that ships only one is
|
|
87
|
+
// rejected here (and by the --staged publish gate) before it can be
|
|
88
|
+
// published.
|
|
89
|
+
for (const platform of RELEASE_PLATFORMS) {
|
|
90
|
+
for (const name of [...BINARIES, 'payload.json']) required.push(`package/npm/native/${platform}/${name}`);
|
|
91
|
+
}
|
|
68
92
|
|
|
69
93
|
const listing = spawnSync('tar', ['-tzf', tgz], { encoding: 'utf8' });
|
|
70
94
|
if (listing.status !== 0) {
|
|
@@ -85,38 +109,82 @@ if (!stagedOnly) {
|
|
|
85
109
|
}
|
|
86
110
|
|
|
87
111
|
const packedPackage = JSON.parse(spawnSync('tar', ['-xzf', tgz, '-O', 'package/package.json'], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }).stdout);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
for (const platform of RELEASE_PLATFORMS) {
|
|
113
|
+
const manifestText = spawnSync('tar', ['-xzf', tgz, '-O', `package/npm/native/${platform}/payload.json`], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }).stdout;
|
|
114
|
+
let manifest;
|
|
115
|
+
try {
|
|
116
|
+
manifest = JSON.parse(manifestText);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
process.stderr.write(`payload manifest for ${platform} is not valid JSON: ${error.message}\n`);
|
|
119
|
+
failed = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (manifest.product !== 'external-subagent' || manifest.platform !== platform) {
|
|
123
|
+
process.stderr.write(`payload manifest for ${platform} has an inconsistent identity (${manifest.product}/${manifest.platform})\n`);
|
|
124
|
+
failed = true;
|
|
125
|
+
}
|
|
126
|
+
if (platform === hostPlatform && manifest.version !== packedPackage.version) {
|
|
127
|
+
process.stderr.write(`payload version ${manifest.version} (${platform}) does not match package version ${packedPackage.version}\n`);
|
|
128
|
+
failed = true;
|
|
129
|
+
}
|
|
93
130
|
}
|
|
94
131
|
}
|
|
95
132
|
}
|
|
96
133
|
|
|
97
|
-
// Staged payload checks
|
|
98
|
-
//
|
|
99
|
-
//
|
|
134
|
+
// Staged payload checks: the staged native binaries and the package version
|
|
135
|
+
// must agree right where the tarball is built from. The publish gate
|
|
136
|
+
// (--staged) checks every platform because CI merges both artifacts into one
|
|
137
|
+
// tree before packing; a plain `npm pack` checks the running host's platform,
|
|
138
|
+
// whose payload prepack rebuilds from source (the foreign platform is required
|
|
139
|
+
// to be present by the tarball entry checks above).
|
|
100
140
|
const repoPackage = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
function checkStagedPlatform(platform) {
|
|
142
|
+
const dir = path.join(process.cwd(), 'npm', 'native', platform);
|
|
143
|
+
const manifestPath = path.join(dir, 'payload.json');
|
|
144
|
+
let manifest;
|
|
145
|
+
try {
|
|
146
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
process.stderr.write(`staged ${platform} payload manifest is unavailable: ${error.message}\n`);
|
|
149
|
+
failed = true;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (manifest.version !== repoPackage.version) {
|
|
153
|
+
process.stderr.write(`staged ${platform} payload version ${manifest.version} does not match package version ${repoPackage.version}\n`);
|
|
154
|
+
failed = true;
|
|
155
|
+
}
|
|
156
|
+
if (manifest.product !== 'external-subagent') {
|
|
157
|
+
process.stderr.write(`staged ${platform} payload product ${manifest.product} is not the release payload\n`);
|
|
116
158
|
failed = true;
|
|
117
159
|
}
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
if (manifest.platform !== platform) {
|
|
161
|
+
process.stderr.write(`staged ${platform} payload manifest declares ${manifest.platform}\n`);
|
|
162
|
+
failed = true;
|
|
163
|
+
}
|
|
164
|
+
const image = IMAGE_CHECKS[platform];
|
|
165
|
+
for (const name of BINARIES) {
|
|
166
|
+
const local = path.join(dir, name);
|
|
167
|
+
let bytes;
|
|
168
|
+
try {
|
|
169
|
+
bytes = fs.readFileSync(local);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
process.stderr.write(`${platform}/${name} is missing from the staged payload: ${error.code}\n`);
|
|
172
|
+
failed = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (image.probe(bytes) !== image.arch) {
|
|
176
|
+
process.stderr.write(`${platform}/${name} is not a ${image.describe}\n`);
|
|
177
|
+
failed = true;
|
|
178
|
+
}
|
|
179
|
+
const mode = (fs.statSync(local).mode & 0o777).toString(8);
|
|
180
|
+
if (mode !== '755') { process.stderr.write(`${platform}/${name} has release mode ${mode}, expected 755\n`); failed = true; }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const stagedPlatforms = stagedOnly ? RELEASE_PLATFORMS : (hostPlatform ? [hostPlatform] : []);
|
|
185
|
+
for (const platform of stagedPlatforms) checkStagedPlatform(platform);
|
|
186
|
+
if (stagedPlatforms.length === 0) {
|
|
187
|
+
note(`no staged release platform for ${process.platform}-${process.arch}; staged payload checks skipped\n`);
|
|
120
188
|
}
|
|
121
189
|
|
|
122
190
|
if (failed) process.exit(1);
|
|
@@ -10,6 +10,7 @@ import fs from 'node:fs';
|
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { nativePlatform } from '../../cli/install/layout.mjs';
|
|
13
14
|
|
|
14
15
|
function run(command, args, options = {}) {
|
|
15
16
|
const result = spawnSync(command, args, { encoding: 'utf8', ...options });
|
|
@@ -38,7 +39,11 @@ try {
|
|
|
38
39
|
run('npm', ['install', '--global', `--prefix=${prefix}`, '--no-audit', '--no-fund', tgz], { cwd: work, env });
|
|
39
40
|
const cli = path.join(prefix, 'bin', 'external-subagent');
|
|
40
41
|
const packageRoot = path.join(prefix, 'lib', 'node_modules', 'external-subagent');
|
|
41
|
-
|
|
42
|
+
// The packed payload directory is the running host's tuple (S01), so the
|
|
43
|
+
// smoke checks the platform it is actually installing rather than a fixed
|
|
44
|
+
// darwin-arm64 path.
|
|
45
|
+
const payloadPlatform = nativePlatform() ?? `${process.platform}-${process.arch}`;
|
|
46
|
+
for (const target of [cli, path.join(prefix, 'bin', 'external-subagent-mcp'), path.join(packageRoot, 'npm', 'native', payloadPlatform, 'external-subagentd')]) {
|
|
42
47
|
fs.accessSync(target, fs.constants.X_OK);
|
|
43
48
|
}
|
|
44
49
|
run(cli, ['version'], { env });
|