ultra-builder-pro-cli 3.0.2 → 5.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/.ultra-template/contexts/TEMPLATE.md +6 -0
- package/CHANGELOG.md +83 -1
- package/README.md +126 -36
- package/README.zh-CN.md +440 -0
- package/adapters/_shared/host-profile.cjs +3 -151
- package/adapters/_shared/plugin-core.cjs +57 -0
- package/adapters/_shared/runtime-assets.cjs +64 -12
- package/adapters/codex.js +3 -6
- package/bin/delegate-worker.cjs +2 -345
- package/bin/delegate.cjs +3 -530
- package/docs/ARCHITECTURE.md +59 -18
- package/docs/ARTIFACT-AUTHORITY.md +21 -5
- package/docs/DECISIONS.md +31 -10
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +28 -12
- package/docs/RUNTIME-COMPAT-MATRIX.md +106 -26
- package/docs/SKILL-AUTHORING.md +15 -3
- package/docs/WORKFLOW-LIFECYCLE.md +83 -29
- package/docs/evals/ultra-5-0-journey-results.md +62 -0
- package/docs/evals/ultra-5-0-live-acp-conformance.md +93 -0
- package/docs/evals/ultra-5-0-route-journeys.md +145 -0
- package/package.json +2 -1
- package/skills/ultra-change/SKILL.md +30 -4
- package/skills/ultra-delegate/SKILL.md +80 -7
- package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +184 -0
- package/skills/ultra-delegate/references/delegation-contract.md +60 -1
- package/skills/ultra-delegate/scripts/acp-conformance.cjs +287 -0
- package/skills/ultra-delegate/scripts/acp-driver.cjs +762 -0
- package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +320 -0
- package/skills/ultra-delegate/scripts/cli-driver.cjs +61 -0
- package/skills/ultra-delegate/scripts/delegate-worker.cjs +328 -0
- package/skills/ultra-delegate/scripts/delegate.cjs +690 -0
- package/skills/ultra-delegate/scripts/host-profile.cjs +267 -0
- package/skills/ultra-deliver/SKILL.md +13 -0
- package/skills/ultra-dev/SKILL.md +18 -2
- package/skills/ultra-init/SKILL.md +12 -0
- package/skills/ultra-plan/SKILL.md +21 -3
- package/skills/ultra-research/SKILL.md +33 -5
- package/skills/ultra-status/SKILL.md +37 -7
- package/skills/ultra-status/references/route-contract.md +100 -0
- package/skills/ultra-test/SKILL.md +16 -1
- /package/{adapters/_shared → skills/ultra-delegate/scripts}/delegate-profiles/kimi-read-only.md +0 -0
- /package/{adapters/_shared → skills/ultra-delegate/scripts}/delegate-profiles/kimi-write.md +0 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { execFileSync, spawn } = require('node:child_process');
|
|
8
|
+
|
|
9
|
+
const { hostProfile, transportSurface } = require('./host-profile.cjs');
|
|
10
|
+
const { selectExecutionDriver } = require('./agent-execution-driver.cjs');
|
|
11
|
+
|
|
12
|
+
const PERMISSION_SCHEMA = 'ultra-delegation-permission-v1';
|
|
13
|
+
const RECEIPT_SCHEMA = 'ultra-delegation-receipt-v1';
|
|
14
|
+
const RESULT_SCHEMA = 'ultra-delegation-result-v1';
|
|
15
|
+
const PERMISSION_FIELDS = new Set(['$schema', 'writable_roots', 'external_effects']);
|
|
16
|
+
const PROTECTED_ROOT = '.ultra';
|
|
17
|
+
const RESULT_JSON_SCHEMA = Object.freeze({
|
|
18
|
+
type: 'object',
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
required: [
|
|
21
|
+
'$schema', 'status', 'summary', 'changed_files', 'checks', 'evidence',
|
|
22
|
+
'questions', 'residual_risks',
|
|
23
|
+
],
|
|
24
|
+
properties: {
|
|
25
|
+
$schema: { type: 'string', const: RESULT_SCHEMA },
|
|
26
|
+
status: { type: 'string', enum: ['finished', 'blocked', 'failed'] },
|
|
27
|
+
summary: { type: 'string' },
|
|
28
|
+
changed_files: {
|
|
29
|
+
type: 'array', items: { type: 'string' },
|
|
30
|
+
},
|
|
31
|
+
checks: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
required: ['command', 'status', 'output_ref'],
|
|
37
|
+
properties: {
|
|
38
|
+
command: { type: 'string' },
|
|
39
|
+
status: { type: 'string', enum: ['passed', 'failed', 'not_run'] },
|
|
40
|
+
output_ref: { type: 'string' },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
evidence: { type: 'array', items: { type: 'string' } },
|
|
45
|
+
questions: { type: 'array', items: { type: 'string' } },
|
|
46
|
+
residual_risks: { type: 'array', items: { type: 'string' } },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function writeAtomic(file, content) {
|
|
51
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
52
|
+
let target = file;
|
|
53
|
+
try {
|
|
54
|
+
const stat = fs.lstatSync(file);
|
|
55
|
+
if (stat.isSymbolicLink()) {
|
|
56
|
+
try {
|
|
57
|
+
target = fs.realpathSync(file);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code === 'ENOENT') throw new Error(`refusing to replace dangling symlink: ${file}`);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code !== 'ENOENT') throw error;
|
|
65
|
+
}
|
|
66
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
67
|
+
let mode = null;
|
|
68
|
+
try {
|
|
69
|
+
mode = fs.statSync(target).mode & 0o7777;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.code !== 'ENOENT') throw error;
|
|
72
|
+
}
|
|
73
|
+
const temporary = `${target}.tmp-${crypto.randomUUID()}`;
|
|
74
|
+
try {
|
|
75
|
+
fs.writeFileSync(temporary, content, mode === null ? undefined : { mode });
|
|
76
|
+
if (mode !== null) fs.chmodSync(temporary, mode);
|
|
77
|
+
fs.renameSync(temporary, target);
|
|
78
|
+
} finally {
|
|
79
|
+
try { fs.unlinkSync(temporary); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runnerCommand(action, delegation = '<delegation-dir>') {
|
|
84
|
+
return [process.execPath, __filename, action, '--delegation', delegation]
|
|
85
|
+
.map((value) => JSON.stringify(String(value)))
|
|
86
|
+
.join(' ');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function inside(root, candidate) {
|
|
90
|
+
const relative = path.relative(root, candidate);
|
|
91
|
+
return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`)
|
|
92
|
+
&& !path.isAbsolute(relative);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function atOrInside(root, candidate) {
|
|
96
|
+
return candidate === root || inside(root, candidate);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sameFileSystemPath(left, right, platform = process.platform) {
|
|
100
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
101
|
+
return pathApi.relative(left, right) === '' && pathApi.relative(right, left) === '';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function sameFileSystemEntry(left, right, {
|
|
105
|
+
platform = process.platform,
|
|
106
|
+
stat = fs.statSync,
|
|
107
|
+
} = {}) {
|
|
108
|
+
try {
|
|
109
|
+
const leftStat = stat(left, { bigint: true });
|
|
110
|
+
const rightStat = stat(right, { bigint: true });
|
|
111
|
+
if (leftStat?.ino !== 0n && rightStat?.ino !== 0n) {
|
|
112
|
+
return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
return sameFileSystemPath(left, right, platform);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function projectPath(projectRoot, value, label, { directory = false } = {}) {
|
|
119
|
+
const lexical = path.resolve(projectRoot, value);
|
|
120
|
+
const resolved = fs.existsSync(lexical) ? fs.realpathSync(lexical) : lexical;
|
|
121
|
+
if (!inside(projectRoot, resolved)) throw new Error(`${label} must be inside the current project`);
|
|
122
|
+
if (directory && (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory())) {
|
|
123
|
+
throw new Error(`${label} must be an existing directory`);
|
|
124
|
+
}
|
|
125
|
+
return resolved;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sha256(file) {
|
|
129
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function git(cwd, args) {
|
|
133
|
+
return execFileSync('git', args, {
|
|
134
|
+
cwd,
|
|
135
|
+
encoding: 'utf8',
|
|
136
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
137
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
138
|
+
}).trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function observedChangedFiles(cwd, baseHead) {
|
|
142
|
+
const run = (args) => execFileSync('git', args, {
|
|
143
|
+
cwd,
|
|
144
|
+
encoding: 'buffer',
|
|
145
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
146
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
147
|
+
}).toString('utf8').split('\0').filter(Boolean);
|
|
148
|
+
return [...new Set([
|
|
149
|
+
...run(['diff', '--name-only', '-z', baseHead, '--']),
|
|
150
|
+
...run(['ls-files', '--others', '--exclude-standard', '-z']),
|
|
151
|
+
].map((file) => file.split(path.sep).join('/')))].sort();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function validateWorktree(worktree) {
|
|
155
|
+
let top;
|
|
156
|
+
try {
|
|
157
|
+
top = fs.realpathSync(git(worktree, ['rev-parse', '--show-toplevel']));
|
|
158
|
+
} catch {
|
|
159
|
+
throw new Error('worktree must be a real Git worktree');
|
|
160
|
+
}
|
|
161
|
+
if (!sameFileSystemEntry(top, worktree)) {
|
|
162
|
+
throw new Error('worktree must be the root of a real Git worktree');
|
|
163
|
+
}
|
|
164
|
+
const registered = git(worktree, ['worktree', 'list', '--porcelain'])
|
|
165
|
+
.split('\n')
|
|
166
|
+
.filter((line) => line.startsWith('worktree '))
|
|
167
|
+
.map((line) => line.slice('worktree '.length))
|
|
168
|
+
.some((entry) => {
|
|
169
|
+
try { return sameFileSystemEntry(fs.realpathSync(entry), worktree); } catch { return false; }
|
|
170
|
+
});
|
|
171
|
+
if (!registered) throw new Error('worktree is not registered as a Git worktree');
|
|
172
|
+
const dirty = git(worktree, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
173
|
+
if (dirty) throw new Error('delegated Git worktree must start clean');
|
|
174
|
+
return git(worktree, ['rev-parse', 'HEAD']);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function readPermission(file, worktree) {
|
|
178
|
+
let payload;
|
|
179
|
+
let source;
|
|
180
|
+
try {
|
|
181
|
+
source = fs.readFileSync(file, 'utf8');
|
|
182
|
+
payload = JSON.parse(source);
|
|
183
|
+
} catch {
|
|
184
|
+
throw new Error('permission must be valid JSON');
|
|
185
|
+
}
|
|
186
|
+
if (!payload || Array.isArray(payload) || typeof payload !== 'object') {
|
|
187
|
+
throw new Error('permission must be a JSON object');
|
|
188
|
+
}
|
|
189
|
+
for (const key of Object.keys(payload)) {
|
|
190
|
+
if (!PERMISSION_FIELDS.has(key)) throw new Error(`unknown permission field: ${key}`);
|
|
191
|
+
}
|
|
192
|
+
if (payload.$schema !== PERMISSION_SCHEMA) {
|
|
193
|
+
throw new Error(`permission $schema must be ${PERMISSION_SCHEMA}`);
|
|
194
|
+
}
|
|
195
|
+
if (!Array.isArray(payload.writable_roots)) {
|
|
196
|
+
throw new Error('permission writable_roots must be an array');
|
|
197
|
+
}
|
|
198
|
+
const writableRoots = [];
|
|
199
|
+
const writableRootPaths = [];
|
|
200
|
+
for (const value of payload.writable_roots) {
|
|
201
|
+
if (typeof value !== 'string' || !value.trim() || path.isAbsolute(value)) {
|
|
202
|
+
throw new Error('permission writable_roots entries must be relative non-empty paths');
|
|
203
|
+
}
|
|
204
|
+
const resolved = path.resolve(worktree, value);
|
|
205
|
+
if (!atOrInside(worktree, resolved)) {
|
|
206
|
+
throw new Error('permission writable_roots must stay inside the delegated worktree');
|
|
207
|
+
}
|
|
208
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
|
|
209
|
+
throw new Error(`permission writable_roots entry must be an existing directory: ${value}`);
|
|
210
|
+
}
|
|
211
|
+
const real = fs.realpathSync(resolved);
|
|
212
|
+
if (!atOrInside(worktree, real)) {
|
|
213
|
+
throw new Error('permission writable_roots must not escape through symlinks');
|
|
214
|
+
}
|
|
215
|
+
const normalized = path.relative(worktree, real).split(path.sep).join('/') || '.';
|
|
216
|
+
// `.ultra` is the project's memory and has exactly one writer: the primary host.
|
|
217
|
+
// Rejected here so the delegation never starts, and again against the actual diff
|
|
218
|
+
// in the worker, which also covers the `.` grant.
|
|
219
|
+
if (normalized === PROTECTED_ROOT || normalized.startsWith(`${PROTECTED_ROOT}/`)) {
|
|
220
|
+
throw new Error(`permission writable_roots must not include ${PROTECTED_ROOT}; the primary host is its only writer`);
|
|
221
|
+
}
|
|
222
|
+
if (!writableRoots.includes(normalized)) {
|
|
223
|
+
writableRoots.push(normalized);
|
|
224
|
+
writableRootPaths.push(real);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!Array.isArray(payload.external_effects)) {
|
|
228
|
+
throw new Error('permission external_effects must be an array');
|
|
229
|
+
}
|
|
230
|
+
if (payload.external_effects.length !== 0) {
|
|
231
|
+
throw new Error('permission external_effects must be empty; the primary host performs separately authorized effects');
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
writableRoots,
|
|
235
|
+
writableRootPaths,
|
|
236
|
+
source,
|
|
237
|
+
digest: crypto.createHash('sha256').update(source).digest('hex'),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function delegatePrompt({
|
|
242
|
+
instructionSource, instructionDigest, permissionSource, permissionDigest,
|
|
243
|
+
worktree, readOnly,
|
|
244
|
+
}) {
|
|
245
|
+
return [
|
|
246
|
+
'Execute one bounded Ultra Builder Pro delegation in the isolated Git worktree.',
|
|
247
|
+
'The launcher embedded the digest-bound packet below. Do not access packet paths outside the worktree.',
|
|
248
|
+
`Instruction SHA-256: ${instructionDigest}`,
|
|
249
|
+
'--- instruction.md ---',
|
|
250
|
+
instructionSource.trimEnd(),
|
|
251
|
+
'--- end instruction.md ---',
|
|
252
|
+
`Permission SHA-256: ${permissionDigest}`,
|
|
253
|
+
'--- permission.json ---',
|
|
254
|
+
permissionSource.trimEnd(),
|
|
255
|
+
'--- end permission.json ---',
|
|
256
|
+
`Use this worktree as the only filesystem boundary: ${worktree}`,
|
|
257
|
+
readOnly
|
|
258
|
+
? 'This is read-only delegation. Do not create, modify, or delete any project file.'
|
|
259
|
+
: 'Modify only paths covered by permission.writable_roots.',
|
|
260
|
+
'Do not perform external effects, edit the primary checkout, or change Ultra task authority.',
|
|
261
|
+
'Batch independent reads in the earliest tool turn. Do not emit a preliminary result, and reserve a final turn for the one terminal JSON response.',
|
|
262
|
+
`Return exactly one JSON object as the final response using $schema ${RESULT_SCHEMA}.`,
|
|
263
|
+
'The exact fields are $schema, status, summary, changed_files, checks, evidence, questions, and residual_risks.',
|
|
264
|
+
'status is finished, blocked, or failed. blocked requires at least one question.',
|
|
265
|
+
'changed_files must exactly list every repository-relative path changed in the worktree.',
|
|
266
|
+
'checks items use command, status (passed, failed, or not_run), and output_ref. Use a short evidence reference when no file exists.',
|
|
267
|
+
'Do not write the receipt yourself. The launcher validates structured output, process exit, permissions, and the actual Git diff before publication.',
|
|
268
|
+
].join('\n');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function selectedBinary(runtime, fallback) {
|
|
272
|
+
return process.env[`UBP_DELEGATE_${runtime.toUpperCase()}_BIN`] || fallback;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function selectedModel(runtime, explicit) {
|
|
276
|
+
return explicit || process.env[`UBP_DELEGATE_${runtime.toUpperCase()}_MODEL`] || null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function parsePairs(argv, allowed) {
|
|
280
|
+
const values = {};
|
|
281
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
282
|
+
const key = argv[index];
|
|
283
|
+
const value = argv[index + 1];
|
|
284
|
+
if (!allowed.has(key) || value === undefined || value === '') {
|
|
285
|
+
throw new Error(`invalid delegate argument: ${key || '<missing>'}`);
|
|
286
|
+
}
|
|
287
|
+
if (values[key]) throw new Error(`duplicate delegate argument: ${key}`);
|
|
288
|
+
values[key] = value;
|
|
289
|
+
}
|
|
290
|
+
return values;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function parseRun(argv) {
|
|
294
|
+
// --ack-experimental is the one valueless flag: launching an experimental
|
|
295
|
+
// transport must be a visible choice, never a silent default.
|
|
296
|
+
const ackExperimental = argv.includes('--ack-experimental');
|
|
297
|
+
const pairs = argv.filter((arg) => arg !== '--ack-experimental');
|
|
298
|
+
const values = parsePairs(pairs, new Set([
|
|
299
|
+
'--to', '--instruction', '--permission', '--worktree', '--timeout', '--model',
|
|
300
|
+
]));
|
|
301
|
+
for (const key of ['--to', '--instruction', '--permission', '--worktree']) {
|
|
302
|
+
if (!values[key]) throw new Error(`delegate run requires ${key}`);
|
|
303
|
+
}
|
|
304
|
+
const timeout = values['--timeout'] === undefined ? 1800 : Number(values['--timeout']);
|
|
305
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 86400) {
|
|
306
|
+
throw new Error('delegate --timeout must be greater than 0 and at most 86400 seconds');
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
to: values['--to'], instruction: values['--instruction'],
|
|
310
|
+
permission: values['--permission'], worktree: values['--worktree'],
|
|
311
|
+
model: values['--model'] || null,
|
|
312
|
+
timeoutMs: Math.ceil(timeout * 1000),
|
|
313
|
+
ackExperimental,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function readJson(file) {
|
|
318
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function processAlive(pid) {
|
|
322
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
323
|
+
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function run(argv, {
|
|
327
|
+
projectRoot = process.cwd(),
|
|
328
|
+
resolveHostProfile = hostProfile,
|
|
329
|
+
chooseExecutionDriver = selectExecutionDriver,
|
|
330
|
+
} = {}) {
|
|
331
|
+
const args = parseRun(argv);
|
|
332
|
+
projectRoot = fs.realpathSync(projectRoot);
|
|
333
|
+
const profile = resolveHostProfile(args.to);
|
|
334
|
+
if (args.model && !profile.supportsModelSelection) {
|
|
335
|
+
throw new Error(`delegate --model is not supported by host ${args.to}`);
|
|
336
|
+
}
|
|
337
|
+
const model = profile.supportsModelSelection ? selectedModel(args.to, args.model) : null;
|
|
338
|
+
const binary = selectedBinary(args.to, profile.binary);
|
|
339
|
+
const transportMaturity = profile.transportMaturity;
|
|
340
|
+
const instruction = projectPath(projectRoot, args.instruction, 'instruction');
|
|
341
|
+
const permission = projectPath(projectRoot, args.permission, 'permission');
|
|
342
|
+
const worktree = projectPath(projectRoot, args.worktree, 'worktree', { directory: true });
|
|
343
|
+
if (!fs.statSync(instruction).isFile()) throw new Error('instruction must be a file');
|
|
344
|
+
if (!fs.statSync(permission).isFile()) throw new Error('permission must be a file');
|
|
345
|
+
if (path.dirname(instruction) !== path.dirname(permission)) {
|
|
346
|
+
throw new Error('instruction and permission must share one delegation directory');
|
|
347
|
+
}
|
|
348
|
+
const baseHead = validateWorktree(worktree);
|
|
349
|
+
const permissionContract = readPermission(permission, worktree);
|
|
350
|
+
const instructionSource = fs.readFileSync(instruction, 'utf8');
|
|
351
|
+
const delegationRoot = path.dirname(instruction);
|
|
352
|
+
const delegationId = path.basename(delegationRoot);
|
|
353
|
+
const result = path.join(delegationRoot, 'result.json');
|
|
354
|
+
const receiptFile = path.join(delegationRoot, 'receipt.json');
|
|
355
|
+
const stdout = path.join(delegationRoot, 'stdout.log');
|
|
356
|
+
const stderr = path.join(delegationRoot, 'stderr.log');
|
|
357
|
+
const spec = path.join(delegationRoot, 'worker-spec.json');
|
|
358
|
+
const outputSchema = path.join(delegationRoot, 'result-schema.json');
|
|
359
|
+
const hostOutput = path.join(delegationRoot, 'host-result.json');
|
|
360
|
+
const events = path.join(delegationRoot, 'events.jsonl');
|
|
361
|
+
const lock = path.join(delegationRoot, 'run.lock');
|
|
362
|
+
const cancel = path.join(delegationRoot, 'cancel.request');
|
|
363
|
+
if (fs.existsSync(result)) throw new Error('delegation result.json already exists; use a new delegation id');
|
|
364
|
+
if (fs.existsSync(lock)) throw new Error('delegation is already running');
|
|
365
|
+
if (fs.existsSync(receiptFile) || fs.existsSync(spec) || fs.existsSync(outputSchema)
|
|
366
|
+
|| fs.existsSync(hostOutput) || fs.existsSync(events)) {
|
|
367
|
+
throw new Error('delegation id already has run artifacts; cancel or use a new delegation id');
|
|
368
|
+
}
|
|
369
|
+
let lockFd;
|
|
370
|
+
try {
|
|
371
|
+
lockFd = fs.openSync(lock, 'wx', 0o600);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (error.code === 'EEXIST') throw new Error('delegation is already running');
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
const startedAt = new Date().toISOString();
|
|
377
|
+
const instructionDigest = crypto.createHash('sha256').update(instructionSource).digest('hex');
|
|
378
|
+
const permissionDigest = permissionContract.digest;
|
|
379
|
+
try {
|
|
380
|
+
fs.writeFileSync(lockFd, `${JSON.stringify({ pid: process.pid, started_at: startedAt })}\n`);
|
|
381
|
+
fs.closeSync(lockFd);
|
|
382
|
+
lockFd = undefined;
|
|
383
|
+
writeAtomic(outputSchema, `${JSON.stringify(RESULT_JSON_SCHEMA, null, 2)}\n`);
|
|
384
|
+
fs.chmodSync(outputSchema, 0o600);
|
|
385
|
+
const outputSchemaDigest = sha256(outputSchema);
|
|
386
|
+
const profileOptions = {
|
|
387
|
+
readOnly: permissionContract.writableRoots.length === 0,
|
|
388
|
+
writableRoots: permissionContract.writableRoots,
|
|
389
|
+
schemaFile: outputSchema,
|
|
390
|
+
schemaJson: JSON.stringify(RESULT_JSON_SCHEMA),
|
|
391
|
+
hostOutput,
|
|
392
|
+
model,
|
|
393
|
+
};
|
|
394
|
+
const prompt = delegatePrompt({
|
|
395
|
+
instructionSource,
|
|
396
|
+
instructionDigest,
|
|
397
|
+
permissionSource: permissionContract.source,
|
|
398
|
+
permissionDigest,
|
|
399
|
+
worktree,
|
|
400
|
+
readOnly: profileOptions.readOnly,
|
|
401
|
+
});
|
|
402
|
+
const driverSelection = chooseExecutionDriver({
|
|
403
|
+
host: args.to,
|
|
404
|
+
profile,
|
|
405
|
+
cliCommand: binary,
|
|
406
|
+
env: process.env,
|
|
407
|
+
});
|
|
408
|
+
const transportSurfaceNote = driverSelection.driver === 'acp'
|
|
409
|
+
? profile.acp.surface
|
|
410
|
+
: transportSurface(args.to, binary);
|
|
411
|
+
const cliFallback = driverSelection.driver === 'acp'
|
|
412
|
+
&& typeof profile.delegateArgv === 'function'
|
|
413
|
+
? {
|
|
414
|
+
command: binary,
|
|
415
|
+
args: profile.delegateArgv(prompt, worktree, profileOptions),
|
|
416
|
+
transport_surface: transportSurface(args.to, binary),
|
|
417
|
+
}
|
|
418
|
+
: null;
|
|
419
|
+
if (transportMaturity === 'experimental' && !args.ackExperimental) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
`delegate --to ${args.to} would launch ${transportSurfaceNote}. `
|
|
422
|
+
+ 'This transport is experimental (verified-local only; the provider publishes no '
|
|
423
|
+
+ 'stability contract), and it is not the documented ZCode Desktop interactive '
|
|
424
|
+
+ 'surface. Acknowledge explicitly with --ack-experimental to proceed, or pick a '
|
|
425
|
+
+ 'documented transport.',
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
if (transportMaturity === 'experimental') {
|
|
429
|
+
process.stderr.write(
|
|
430
|
+
`ultra-delegate: warning: host '${args.to}' runs ${transportSurfaceNote}; `
|
|
431
|
+
+ `recovery: ${runnerCommand('cancel')}\n`,
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
const workerSpec = {
|
|
435
|
+
$schema: 'ultra-delegation-worker-spec-v1',
|
|
436
|
+
delegation_id: delegationId,
|
|
437
|
+
host: args.to,
|
|
438
|
+
model,
|
|
439
|
+
command: driverSelection.command,
|
|
440
|
+
transport_maturity: transportMaturity,
|
|
441
|
+
transport_surface: transportSurfaceNote,
|
|
442
|
+
experimental_ack: transportMaturity === 'experimental' ? args.ackExperimental : undefined,
|
|
443
|
+
execution_driver: driverSelection.driver,
|
|
444
|
+
driver_fallback_reason: driverSelection.fallbackReason,
|
|
445
|
+
acp_capability: driverSelection.acpCapability,
|
|
446
|
+
args: driverSelection.driver === 'acp'
|
|
447
|
+
? driverSelection.args
|
|
448
|
+
: profile.delegateArgv(prompt, worktree, profileOptions),
|
|
449
|
+
...(driverSelection.driver === 'acp' ? {
|
|
450
|
+
prompt,
|
|
451
|
+
...(cliFallback ? { cli_fallback: cliFallback } : {}),
|
|
452
|
+
} : {}),
|
|
453
|
+
cwd: worktree,
|
|
454
|
+
instruction,
|
|
455
|
+
permission,
|
|
456
|
+
instruction_digest: instructionDigest,
|
|
457
|
+
permission_digest: permissionDigest,
|
|
458
|
+
output_schema: outputSchema,
|
|
459
|
+
output_schema_digest: outputSchemaDigest,
|
|
460
|
+
host_output: hostOutput,
|
|
461
|
+
read_only: profileOptions.readOnly,
|
|
462
|
+
writable_roots: permissionContract.writableRoots,
|
|
463
|
+
writable_root_paths: permissionContract.writableRootPaths,
|
|
464
|
+
base_head: baseHead,
|
|
465
|
+
started_at: startedAt,
|
|
466
|
+
timeout_ms: args.timeoutMs,
|
|
467
|
+
result,
|
|
468
|
+
stdout,
|
|
469
|
+
stderr,
|
|
470
|
+
events: driverSelection.driver === 'acp' ? events : null,
|
|
471
|
+
lock,
|
|
472
|
+
cancel,
|
|
473
|
+
env: {
|
|
474
|
+
...(profile.delegateEnv ? profile.delegateEnv(profileOptions) : {}),
|
|
475
|
+
UBP_DELEGATE_INSTRUCTION_FILE: instruction,
|
|
476
|
+
UBP_DELEGATE_PERMISSION_FILE: permission,
|
|
477
|
+
UBP_DELEGATE_WORKTREE: worktree,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
writeAtomic(spec, `${JSON.stringify(workerSpec, null, 2)}\n`);
|
|
481
|
+
fs.chmodSync(spec, 0o600);
|
|
482
|
+
const specDigest = sha256(spec);
|
|
483
|
+
const worker = spawn(process.execPath, [
|
|
484
|
+
path.join(__dirname, 'delegate-worker.cjs'), spec, specDigest,
|
|
485
|
+
], {
|
|
486
|
+
cwd: projectRoot,
|
|
487
|
+
detached: true,
|
|
488
|
+
stdio: 'ignore',
|
|
489
|
+
env: process.env,
|
|
490
|
+
});
|
|
491
|
+
if (!worker.pid) throw new Error('failed to start delegation worker');
|
|
492
|
+
worker.unref();
|
|
493
|
+
const receipt = {
|
|
494
|
+
$schema: RECEIPT_SCHEMA,
|
|
495
|
+
status: 'started',
|
|
496
|
+
delegation_id: delegationId,
|
|
497
|
+
host: args.to,
|
|
498
|
+
model,
|
|
499
|
+
transport_maturity: transportMaturity,
|
|
500
|
+
transport_surface: transportSurfaceNote,
|
|
501
|
+
...(transportMaturity === 'experimental' ? { experimental_ack: args.ackExperimental } : {}),
|
|
502
|
+
execution_driver: driverSelection.driver,
|
|
503
|
+
driver_fallback_reason: driverSelection.fallbackReason,
|
|
504
|
+
acp_capability: driverSelection.acpCapability,
|
|
505
|
+
worker_pid: worker.pid,
|
|
506
|
+
instruction_digest: instructionDigest,
|
|
507
|
+
permission_digest: permissionDigest,
|
|
508
|
+
output_schema_digest: outputSchemaDigest,
|
|
509
|
+
spec_digest: specDigest,
|
|
510
|
+
base_head: baseHead,
|
|
511
|
+
started_at: startedAt,
|
|
512
|
+
timeout_ms: args.timeoutMs,
|
|
513
|
+
result_file: result,
|
|
514
|
+
read_only: profileOptions.readOnly,
|
|
515
|
+
stdout_file: stdout,
|
|
516
|
+
stderr_file: stderr,
|
|
517
|
+
events_file: driverSelection.driver === 'acp' ? events : null,
|
|
518
|
+
cancel_command: runnerCommand('cancel', delegationRoot),
|
|
519
|
+
};
|
|
520
|
+
writeAtomic(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
521
|
+
writeAtomic(lock, `${JSON.stringify({ pid: worker.pid, started_at: startedAt })}\n`);
|
|
522
|
+
fs.chmodSync(lock, 0o600);
|
|
523
|
+
if (fs.existsSync(result)) {
|
|
524
|
+
try { fs.unlinkSync(lock); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
525
|
+
}
|
|
526
|
+
return receipt;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (lockFd !== undefined) fs.closeSync(lockFd);
|
|
529
|
+
try { fs.unlinkSync(lock); } catch (unlinkError) { if (unlinkError.code !== 'ENOENT') throw unlinkError; }
|
|
530
|
+
for (const file of [spec, outputSchema, hostOutput, events]) {
|
|
531
|
+
try { fs.unlinkSync(file); } catch (unlinkError) { if (unlinkError.code !== 'ENOENT') throw unlinkError; }
|
|
532
|
+
}
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function delegationPath(projectRoot, value) {
|
|
538
|
+
return projectPath(projectRoot, value, 'delegation', { directory: true });
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function latestDerivedEvent(file) {
|
|
542
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return null;
|
|
543
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
544
|
+
if (Buffer.byteLength(source) > 512 * 1024) return { status: 'unavailable', reason: 'event_journal_limit' };
|
|
545
|
+
const lines = source.trim().split('\n').filter(Boolean);
|
|
546
|
+
if (lines.length === 0) return null;
|
|
547
|
+
try {
|
|
548
|
+
const { projectedDerivedEvent } = require('./acp-driver.cjs');
|
|
549
|
+
const latest = projectedDerivedEvent(JSON.parse(lines.at(-1)));
|
|
550
|
+
return latest
|
|
551
|
+
? { status: 'observed', event_count: lines.length, latest }
|
|
552
|
+
: { status: 'unavailable', reason: 'event_journal_malformed' };
|
|
553
|
+
} catch {
|
|
554
|
+
return { status: 'unavailable', reason: 'event_journal_malformed' };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function status(argv, { projectRoot = process.cwd() } = {}) {
|
|
559
|
+
const values = parsePairs(argv, new Set(['--delegation']));
|
|
560
|
+
if (!values['--delegation']) throw new Error('delegate status requires --delegation');
|
|
561
|
+
projectRoot = fs.realpathSync(projectRoot);
|
|
562
|
+
const root = delegationPath(projectRoot, values['--delegation']);
|
|
563
|
+
const result = path.join(root, 'result.json');
|
|
564
|
+
if (fs.existsSync(result)) return readJson(result);
|
|
565
|
+
const receiptFile = path.join(root, 'receipt.json');
|
|
566
|
+
const receipt = fs.existsSync(receiptFile) ? readJson(receiptFile) : null;
|
|
567
|
+
const lockFile = path.join(root, 'run.lock');
|
|
568
|
+
const lock = fs.existsSync(lockFile) ? readJson(lockFile) : null;
|
|
569
|
+
const cancelling = fs.existsSync(path.join(root, 'cancel.request'));
|
|
570
|
+
const eventsFile = path.join(root, 'events.jsonl');
|
|
571
|
+
return {
|
|
572
|
+
$schema: 'ultra-delegation-status-v1',
|
|
573
|
+
status: cancelling ? 'cancelling' : lock && processAlive(lock.pid) ? 'running' : receipt ? 'stalled' : 'not_started',
|
|
574
|
+
delegation_id: path.basename(root),
|
|
575
|
+
worker_pid: lock?.pid || receipt?.worker_pid || null,
|
|
576
|
+
result_file: result,
|
|
577
|
+
execution_driver: receipt?.execution_driver || null,
|
|
578
|
+
driver_fallback_reason: receipt?.driver_fallback_reason || null,
|
|
579
|
+
events_file: receipt?.events_file || null,
|
|
580
|
+
execution_observation: latestDerivedEvent(eventsFile),
|
|
581
|
+
repair: receipt ? runnerCommand('cancel', root) : null,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function failureFromSpec(spec, failureType, summary) {
|
|
586
|
+
let finalHead = null;
|
|
587
|
+
let changedFiles = [];
|
|
588
|
+
try { finalHead = git(spec.cwd, ['rev-parse', 'HEAD']); } catch {}
|
|
589
|
+
try { changedFiles = observedChangedFiles(spec.cwd, spec.base_head); } catch {}
|
|
590
|
+
return {
|
|
591
|
+
$schema: RESULT_SCHEMA,
|
|
592
|
+
status: 'failed',
|
|
593
|
+
summary,
|
|
594
|
+
changed_files: changedFiles,
|
|
595
|
+
checks: [],
|
|
596
|
+
evidence: [],
|
|
597
|
+
questions: [],
|
|
598
|
+
residual_risks: [],
|
|
599
|
+
failure_type: failureType,
|
|
600
|
+
delegation_id: spec.delegation_id,
|
|
601
|
+
host: spec.host,
|
|
602
|
+
transport_maturity: spec.transport_maturity ?? null,
|
|
603
|
+
transport_surface: spec.transport_surface ?? null,
|
|
604
|
+
execution_driver: spec.execution_driver ?? 'cli',
|
|
605
|
+
driver_fallback_reason: spec.driver_fallback_reason ?? null,
|
|
606
|
+
acp_capability: spec.acp_capability ?? null,
|
|
607
|
+
acp_protocol_version: null,
|
|
608
|
+
acp_stop_reason: null,
|
|
609
|
+
events_file: spec.events ?? null,
|
|
610
|
+
// Terminal recovery results carry the same transport truth as live ones.
|
|
611
|
+
...(spec.experimental_ack === true ? { experimental_ack: true } : {}),
|
|
612
|
+
instruction_digest: spec.instruction_digest,
|
|
613
|
+
permission_digest: spec.permission_digest,
|
|
614
|
+
output_schema_digest: spec.output_schema_digest,
|
|
615
|
+
base_head: spec.base_head,
|
|
616
|
+
final_head: finalHead,
|
|
617
|
+
started_at: spec.started_at,
|
|
618
|
+
finished_at: new Date().toISOString(),
|
|
619
|
+
exit_code: null,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function cancel(argv, { projectRoot = process.cwd() } = {}) {
|
|
624
|
+
const values = parsePairs(argv, new Set(['--delegation']));
|
|
625
|
+
if (!values['--delegation']) throw new Error('delegate cancel requires --delegation');
|
|
626
|
+
projectRoot = fs.realpathSync(projectRoot);
|
|
627
|
+
const root = delegationPath(projectRoot, values['--delegation']);
|
|
628
|
+
const result = path.join(root, 'result.json');
|
|
629
|
+
if (fs.existsSync(result)) return { status: 'already_terminal', result: readJson(result) };
|
|
630
|
+
const specFile = path.join(root, 'worker-spec.json');
|
|
631
|
+
if (!fs.existsSync(specFile)) throw new Error('delegation has no worker spec to cancel');
|
|
632
|
+
const spec = readJson(specFile);
|
|
633
|
+
const request = path.join(root, 'cancel.request');
|
|
634
|
+
writeAtomic(request, `${JSON.stringify({ requested_at: new Date().toISOString() })}\n`);
|
|
635
|
+
const receiptFile = path.join(root, 'receipt.json');
|
|
636
|
+
const receipt = fs.existsSync(receiptFile) ? readJson(receiptFile) : null;
|
|
637
|
+
if (!processAlive(receipt?.worker_pid)) {
|
|
638
|
+
writeAtomic(result, `${JSON.stringify(failureFromSpec(spec, 'cancelled', 'Delegation cancelled after its worker stopped.'), null, 2)}\n`);
|
|
639
|
+
try { fs.unlinkSync(path.join(root, 'run.lock')); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
$schema: 'ultra-delegation-cancel-v1',
|
|
643
|
+
status: 'cancel_requested',
|
|
644
|
+
delegation_id: path.basename(root),
|
|
645
|
+
result_file: result,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function handle(argv, options) {
|
|
650
|
+
if (argv[0] !== 'delegate') throw new Error('delegate command is required');
|
|
651
|
+
if (argv[1] === 'run') return run(argv.slice(2), options);
|
|
652
|
+
if (argv[1] === 'status') return status(argv.slice(2), options);
|
|
653
|
+
if (argv[1] === 'cancel') return cancel(argv.slice(2), options);
|
|
654
|
+
throw new Error(
|
|
655
|
+
'usage: delegate.cjs <run|status|cancel> ... '
|
|
656
|
+
+ '(compatibility: ubp delegate <run|status|cancel> ...)',
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function printHelp() {
|
|
661
|
+
process.stdout.write([
|
|
662
|
+
'Usage: node <ultra-delegate-skill-dir>/scripts/delegate.cjs <run|status|cancel> ...',
|
|
663
|
+
'',
|
|
664
|
+
'The installed Skill-local path is canonical.',
|
|
665
|
+
'Compatibility for one release cycle: ubp delegate <run|status|cancel> ...',
|
|
666
|
+
'',
|
|
667
|
+
].join('\n'));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function main(argv = process.argv.slice(2)) {
|
|
671
|
+
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
672
|
+
printHelp();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const result = handle(['delegate', ...argv]);
|
|
676
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (require.main === module) {
|
|
680
|
+
try {
|
|
681
|
+
main();
|
|
682
|
+
} catch (error) {
|
|
683
|
+
process.stderr.write(`${error.message}\n`);
|
|
684
|
+
process.exitCode = 1;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
module.exports = {
|
|
689
|
+
cancel, handle, main, run, sameFileSystemEntry, sameFileSystemPath, status,
|
|
690
|
+
};
|