ultra-builder-pro-cli 3.0.2 → 5.0.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.
Files changed (41) hide show
  1. package/.ultra-template/contexts/TEMPLATE.md +6 -0
  2. package/CHANGELOG.md +73 -1
  3. package/README.md +126 -36
  4. package/README.zh-CN.md +440 -0
  5. package/adapters/_shared/host-profile.cjs +3 -151
  6. package/adapters/_shared/plugin-core.cjs +57 -0
  7. package/adapters/_shared/runtime-assets.cjs +64 -12
  8. package/bin/delegate-worker.cjs +2 -345
  9. package/bin/delegate.cjs +3 -530
  10. package/docs/ARCHITECTURE.md +59 -18
  11. package/docs/ARTIFACT-AUTHORITY.md +21 -5
  12. package/docs/DECISIONS.md +31 -10
  13. package/docs/PLUGIN-ISOLATION-CONTRACT.md +28 -12
  14. package/docs/RUNTIME-COMPAT-MATRIX.md +101 -22
  15. package/docs/SKILL-AUTHORING.md +15 -3
  16. package/docs/WORKFLOW-LIFECYCLE.md +83 -29
  17. package/docs/evals/ultra-5-0-journey-results.md +62 -0
  18. package/docs/evals/ultra-5-0-live-acp-conformance.md +93 -0
  19. package/docs/evals/ultra-5-0-route-journeys.md +145 -0
  20. package/package.json +2 -1
  21. package/skills/ultra-change/SKILL.md +30 -4
  22. package/skills/ultra-delegate/SKILL.md +80 -7
  23. package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +184 -0
  24. package/skills/ultra-delegate/references/delegation-contract.md +60 -1
  25. package/skills/ultra-delegate/scripts/acp-conformance.cjs +287 -0
  26. package/skills/ultra-delegate/scripts/acp-driver.cjs +762 -0
  27. package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +320 -0
  28. package/skills/ultra-delegate/scripts/cli-driver.cjs +61 -0
  29. package/skills/ultra-delegate/scripts/delegate-worker.cjs +328 -0
  30. package/skills/ultra-delegate/scripts/delegate.cjs +690 -0
  31. package/skills/ultra-delegate/scripts/host-profile.cjs +267 -0
  32. package/skills/ultra-deliver/SKILL.md +13 -0
  33. package/skills/ultra-dev/SKILL.md +18 -2
  34. package/skills/ultra-init/SKILL.md +12 -0
  35. package/skills/ultra-plan/SKILL.md +21 -3
  36. package/skills/ultra-research/SKILL.md +33 -5
  37. package/skills/ultra-status/SKILL.md +37 -7
  38. package/skills/ultra-status/references/route-contract.md +100 -0
  39. package/skills/ultra-test/SKILL.md +16 -1
  40. /package/{adapters/_shared → skills/ultra-delegate/scripts}/delegate-profiles/kimi-read-only.md +0 -0
  41. /package/{adapters/_shared → skills/ultra-delegate/scripts}/delegate-profiles/kimi-write.md +0 -0
@@ -0,0 +1,184 @@
1
+ diff --git a/Cargo.lock b/Cargo.lock
2
+ index bfadfbb..4b1d665 100644
3
+ --- a/Cargo.lock
4
+ +++ b/Cargo.lock
5
+ @@ -2492,7 +2492,7 @@ dependencies = [
6
+
7
+ [[package]]
8
+ name = "zcode-acp"
9
+ -version = "0.1.0"
10
+ +version = "0.1.0+ultra.zcode.0.16.5"
11
+ dependencies = [
12
+ "aes-gcm",
13
+ "agent-client-protocol",
14
+ diff --git a/Cargo.toml b/Cargo.toml
15
+ index f096b91..4b4d985 100644
16
+ --- a/Cargo.toml
17
+ +++ b/Cargo.toml
18
+ @@ -1,6 +1,6 @@
19
+ [package]
20
+ name = "zcode-acp"
21
+ -version = "0.1.0"
22
+ +version = "0.1.0+ultra.zcode.0.16.5"
23
+ edition = "2024"
24
+ description = "ACP adapter that bridges Buzz relay events to the ZCode CLI via its stdio app-server"
25
+ license = "Apache-2.0"
26
+ diff --git a/src/zcode/mod.rs b/src/zcode/mod.rs
27
+ index 7963959..14725bb 100644
28
+ --- a/src/zcode/mod.rs
29
+ +++ b/src/zcode/mod.rs
30
+ @@ -266,6 +266,13 @@ impl DriverState {
31
+ }
32
+ }
33
+ Some(Incoming::ServerRequest { id, method, params }) => {
34
+ + if method == methods::SESSION_REQUEST_RUNTIME_PREFERENCES {
35
+ + let _ = self.write_tx.send(json!({
36
+ + "id": id,
37
+ + "result": default_runtime_preferences(),
38
+ + }));
39
+ + return;
40
+ + }
41
+ // The ACP layer answers via `reply_tx`; a side task writes the
42
+ // reply back through `write_tx` echoing the wire id.
43
+ let (reply_tx, reply_rx) = oneshot::channel();
44
+ @@ -373,6 +380,32 @@ impl DriverState {
45
+ }
46
+ }
47
+
48
+ +fn default_runtime_preferences() -> Value {
49
+ + json!({
50
+ + "nativeSearchEnhancementsEnabled": true,
51
+ + "memoryEnabled": false,
52
+ + "askUserQuestionAutoResolutionEnabled": true,
53
+ + "modelContextBudgetStrategy": "preflight-v1",
54
+ + })
55
+ +}
56
+ +
57
+ +fn bootstrap_server_request_response(id: RequestId, method: &str) -> Value {
58
+ + if method == methods::SESSION_REQUEST_RUNTIME_PREFERENCES {
59
+ + json!({
60
+ + "id": id,
61
+ + "result": default_runtime_preferences(),
62
+ + })
63
+ + } else {
64
+ + json!({
65
+ + "id": id,
66
+ + "error": {
67
+ + "code": -32601,
68
+ + "message": format!("Unsupported app-server request: {method}"),
69
+ + },
70
+ + })
71
+ + }
72
+ +}
73
+ +
74
+ /// Run the dispatcher: read framed messages, dispatch each into shared state.
75
+ async fn run_dispatcher(
76
+ state: Arc<Mutex<DriverState>>,
77
+ @@ -647,6 +680,15 @@ pub async fn build_session(
78
+ protocol::ResponseResult::Ok(v) => break v,
79
+ protocol::ResponseResult::Err(e) => return Err(SessionError::CreateFailed(e)),
80
+ },
81
+ + Some(Incoming::ServerRequest {
82
+ + id: request_id,
83
+ + method,
84
+ + ..
85
+ + }) => {
86
+ + write_tx
87
+ + .send(bootstrap_server_request_response(request_id, &method))
88
+ + .map_err(|e| SessionError::Send(e.to_string()))?;
89
+ + }
90
+ _ => continue,
91
+ },
92
+ None => return Err(SessionError::TransportClosed),
93
+ diff --git a/src/zcode/protocol.rs b/src/zcode/protocol.rs
94
+ index 71fa3e9..6e3e595 100644
95
+ --- a/src/zcode/protocol.rs
96
+ +++ b/src/zcode/protocol.rs
97
+ @@ -138,6 +138,7 @@ impl IncomingMessage {
98
+
99
+ pub mod methods {
100
+ pub const SESSION_CREATE: &str = "session/create";
101
+ + pub const SESSION_REQUEST_RUNTIME_PREFERENCES: &str = "session/requestRuntimePreferences";
102
+ pub const SESSION_RESUME: &str = "session/resume";
103
+ pub const SESSION_LIST: &str = "session/list";
104
+ pub const SESSION_SUBSCRIBE: &str = "session/subscribe";
105
+ diff --git a/tests/acp_client.py b/tests/acp_client.py
106
+ index a407220..0e46772 100644
107
+ --- a/tests/acp_client.py
108
+ +++ b/tests/acp_client.py
109
+ @@ -22,6 +22,7 @@ from __future__ import annotations
110
+
111
+ import json
112
+ import os
113
+ +import select
114
+ import subprocess
115
+ import sys
116
+ from pathlib import Path
117
+ @@ -40,6 +41,9 @@ def send(proc: subprocess.Popen, obj: dict) -> None:
118
+
119
+ def read_message(proc: subprocess.Popen) -> dict:
120
+ assert proc.stdout is not None
121
+ + readable, _, _ = select.select([proc.stdout], [], [], 5)
122
+ + if not readable:
123
+ + raise TimeoutError("agent did not emit an ACP message within 5 seconds")
124
+ raw = proc.stdout.readline()
125
+ if not raw:
126
+ raise EOFError("agent closed stdout")
127
+ @@ -74,6 +78,7 @@ def main() -> int:
128
+ stdin=subprocess.PIPE,
129
+ stdout=subprocess.PIPE,
130
+ stderr=sys.stderr, # surface shim logs inline for debugging
131
+ + bufsize=0,
132
+ )
133
+
134
+ try:
135
+ diff --git a/tests/fake-app-server.cjs b/tests/fake-app-server.cjs
136
+ index f6a5991..e690fbe 100644
137
+ --- a/tests/fake-app-server.cjs
138
+ +++ b/tests/fake-app-server.cjs
139
+ @@ -21,6 +21,8 @@ const rl = readline.createInterface({ input: process.stdin, terminal: false });
140
+ let sessionIdCounter = 0;
141
+ let activeSession = null;
142
+ let seqCounter = 0;
143
+ +let pendingCreateId = null;
144
+ +let runtimePreferencesRequestId = null;
145
+
146
+ function send(obj) {
147
+ process.stdout.write(JSON.stringify(obj) + "\n");
148
+ @@ -54,10 +56,35 @@ function handle(line) {
149
+ return;
150
+ }
151
+ const { id, method, params } = msg;
152
+ + if (!method && id === runtimePreferencesRequestId) {
153
+ + const prefs = msg.result;
154
+ + const valid = prefs
155
+ + && prefs.nativeSearchEnhancementsEnabled === true
156
+ + && prefs.memoryEnabled === false
157
+ + && prefs.askUserQuestionAutoResolutionEnabled === true
158
+ + && prefs.modelContextBudgetStrategy === "preflight-v1";
159
+ + if (!valid) {
160
+ + send({
161
+ + id: pendingCreateId,
162
+ + error: { code: -32602, message: "Invalid runtime preferences response" },
163
+ + });
164
+ + } else {
165
+ + respond(pendingCreateId, { sessionId: activeSession });
166
+ + }
167
+ + pendingCreateId = null;
168
+ + runtimePreferencesRequestId = null;
169
+ + return;
170
+ + }
171
+ switch (method) {
172
+ case "session/create": {
173
+ activeSession = `sess_fake_${++sessionIdCounter}`;
174
+ - respond(id, { sessionId: activeSession });
175
+ + pendingCreateId = id;
176
+ + runtimePreferencesRequestId = "server-runtime-preferences-1";
177
+ + send({
178
+ + id: runtimePreferencesRequestId,
179
+ + method: "session/requestRuntimePreferences",
180
+ + params: { sessionId: activeSession, scope: "runtime-materialization" },
181
+ + });
182
+ break;
183
+ }
184
+ case "session/subscribe": {
@@ -1,5 +1,18 @@
1
1
  # Delegation artifact contract
2
2
 
3
+ ## Runtime ownership
4
+
5
+ The loaded `ultra-delegate` Skill owns `scripts/delegate.cjs`,
6
+ `scripts/delegate-worker.cjs`, `scripts/host-profile.cjs`,
7
+ `scripts/agent-execution-driver.cjs`, `scripts/acp-driver.cjs`,
8
+ `scripts/cli-driver.cjs`, `scripts/acp-conformance.cjs`, and its launch-only profiles.
9
+ Invoke the launcher with the loaded Skill's absolute path. It has no package-root or
10
+ global `ubp` dependency. The Claude and Codex profiles separately invoke exact pinned
11
+ ACP adapter packages through `npx`; no `npx` lookup is used to locate Ultra itself.
12
+ The package `ubp delegate` command is a thin
13
+ one-release compatibility import of the same functions; it contains no launcher or
14
+ worker behavior of its own.
15
+
3
16
  ## Permission
4
17
 
5
18
  ```json
@@ -61,7 +74,7 @@ the host prompt, extracts this object from native structured output, and adds
61
74
  failures also add `failure_type`. A nonzero process cannot publish `finished`.
62
75
 
63
76
  Every receipt, worker spec, and terminal result also records the transport truth:
64
- `transport_maturity` (from the shared host profile) and the exact `transport_surface`
77
+ `transport_maturity` (from the Skill-local host profile) and the exact `transport_surface`
65
78
  wording. The ZCode headless transport is `experimental` with no public stability
66
79
  contract and is not the documented ZCode Desktop interactive surface; launching it
67
80
  requires the explicit `--ack-experimental` flag, prints a stderr warning, and stamps
@@ -69,11 +82,57 @@ requires the explicit `--ack-experimental` flag, prints a stderr warning, and st
69
82
  internal CLI run as a documented Desktop session — including failure, cancellation,
70
83
  and timeout recovery results.
71
84
 
85
+ They also record `execution_driver`, `driver_fallback_reason`, and the probed
86
+ `acp_capability`, including candidate-identity validity when the profile declares
87
+ bounded local version probes. ACP results add negotiated `acp_protocol_version`, observed
88
+ `acp_stop_reason`, and `events_file`. These are mechanical observations only. In
89
+ particular, `stopReason: end_turn`, a completed tool call, an ACP plan, or process exit
90
+ cannot synthesize the exact model response above or satisfy its Git-diff validation.
91
+
92
+ ## ACP v1 permission and event boundary
93
+
94
+ The stdio client advertises no client filesystem, terminal, or terminal-auth
95
+ capability. It creates one fresh session with the isolated worktree as absolute `cwd`,
96
+ an empty MCP-server list, and one digest-bound text prompt. Agent-side file tools remain
97
+ inside the Host process and the existing worktree/diff enforcement remains final.
98
+
99
+ ACP `session/request_permission` uses least authority:
100
+
101
+ - read, search, and think may select an offered `allow_once`;
102
+ - edit, delete, and move may select `allow_once` only when every non-empty absolute
103
+ location is already inside a granted real writable root and outside `.ultra/`;
104
+ - execute, fetch, mode switching, unknown operations, read-only writes, symlink escape,
105
+ and every other widening select an offered rejection and make the run terminally
106
+ denied;
107
+ - `allow_always` is never selected; cancellation answers any pending permission request
108
+ with ACP's `cancelled` outcome.
109
+
110
+ An agent may send creation-time `available_commands_update`, `current_mode_update`,
111
+ `config_option_update`, or `session_info_update` before the `session/new` response.
112
+ Ultra holds at most 32 such messages and 256 KiB transiently, persists no raw payload,
113
+ and replays them only after every message session ID equals the returned session ID.
114
+ Any other early update kind, identity mismatch, count overflow, or byte overflow is a
115
+ typed failure; pre-session buffering cannot contribute model result text.
116
+
117
+ `events.jsonl` is bounded disposable telemetry. It stores phase/update type, tool kind
118
+ and status, counts, permission decision/scope, and terminal stop reason. It drops
119
+ `agent_thought_chunk` entirely and never stores message text, titles, raw tool
120
+ input/output, credentials, or protocol wire payloads. Status re-projects the latest
121
+ record through the same fixed safe shape, so a malformed or replaced journal becomes
122
+ `event_journal_malformed` instead of being echoed. Loss of the journal does not affect
123
+ recovery or completion.
124
+
72
125
  ## Recovery
73
126
 
74
127
  - `status` distinguishes running, cancelling, stalled, and terminal artifacts.
128
+ - ACP status may also expose the latest redacted derived event; it remains non-authoritative.
75
129
  - `cancel` writes `cancel.request`; the worker terminates its host process, publishes a
76
130
  failed result with `failure_type: cancelled`, and removes `run.lock`.
77
131
  - `--timeout` does the same with `failure_type: timeout`.
132
+ - `ULTRA_DELEGATE_FORCE_CLI=1` bypasses ACP selection for one operator-controlled run
133
+ and records `acp_forced_cli`; it does not mutate the conformance record.
134
+ - `acp_auth_required` reported by `session/new` before any prompt was sent runs the
135
+ immutable bounded CLI fallback once and records `acp_fallback.prompt_sent: false`.
136
+ No other ACP failure is retried, preventing duplicate provider work.
78
137
  - A stopped worker with no result is recoverable through `cancel`, which finalizes the
79
138
  stale run. Retry under a new delegation id and fresh digests.
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const crypto = require('node:crypto');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { execFileSync } = require('node:child_process');
9
+
10
+ const { run: runDelegate } = require('./delegate.cjs');
11
+ const { hostProfile, ZCODE_ACP_SOURCE } = require('./host-profile.cjs');
12
+
13
+ const HOSTS = new Set(['claude', 'codex', 'opencode', 'kimi', 'grok', 'zcode']);
14
+ const RECEIPT_SCHEMA = 'ultra-acp-conformance-receipt-v1';
15
+
16
+ function parseArgs(argv) {
17
+ const allowed = new Set(['--host', '--output', '--timeout', '--command', '--args-json']);
18
+ const values = {};
19
+ for (let index = 0; index < argv.length; index += 2) {
20
+ const key = argv[index];
21
+ const value = argv[index + 1];
22
+ if (!allowed.has(key) || value === undefined || value === '' || values[key] !== undefined) {
23
+ throw new Error(`invalid ACP conformance argument: ${key || '<missing>'}`);
24
+ }
25
+ values[key] = value;
26
+ }
27
+ if (!HOSTS.has(values['--host'])) throw new Error('ACP conformance requires a supported --host');
28
+ if (!values['--output']) throw new Error('ACP conformance requires --output');
29
+ const timeoutSeconds = values['--timeout'] === undefined ? 240 : Number(values['--timeout']);
30
+ if (!Number.isFinite(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 600) {
31
+ throw new Error('ACP conformance --timeout must be between 1 and 600 seconds');
32
+ }
33
+ const hasCommand = values['--command'] !== undefined;
34
+ const hasArgs = values['--args-json'] !== undefined;
35
+ if (hasCommand !== hasArgs) throw new Error('--command and --args-json must be supplied together');
36
+ let argsOverride = null;
37
+ if (hasArgs) {
38
+ try { argsOverride = JSON.parse(values['--args-json']); } catch {
39
+ throw new Error('--args-json must be a JSON string array');
40
+ }
41
+ if (!Array.isArray(argsOverride) || !argsOverride.every((item) => typeof item === 'string')) {
42
+ throw new Error('--args-json must be a JSON string array');
43
+ }
44
+ }
45
+ return {
46
+ host: values['--host'],
47
+ output: path.resolve(values['--output']),
48
+ timeoutSeconds,
49
+ commandOverride: values['--command'] || null,
50
+ argsOverride,
51
+ };
52
+ }
53
+
54
+ function git(cwd, args) {
55
+ return execFileSync('git', args, {
56
+ cwd,
57
+ encoding: 'utf8',
58
+ maxBuffer: 8 * 1024 * 1024,
59
+ stdio: ['ignore', 'pipe', 'pipe'],
60
+ }).trim();
61
+ }
62
+
63
+ function sha256(file) {
64
+ return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
65
+ }
66
+
67
+ function artifactDigest(file) {
68
+ const stat = fs.statSync(file);
69
+ return { sha256: sha256(file), bytes: stat.size };
70
+ }
71
+
72
+ function processAlive(pid) {
73
+ if (!Number.isInteger(pid) || pid <= 0) return false;
74
+ try { process.kill(pid, 0); return true; } catch { return false; }
75
+ }
76
+
77
+ async function waitForFile(file, timeoutMs) {
78
+ const deadline = Date.now() + timeoutMs;
79
+ while (Date.now() < deadline) {
80
+ if (fs.existsSync(file) && fs.statSync(file).size > 0) return;
81
+ await new Promise((resolve) => setTimeout(resolve, 50));
82
+ }
83
+ throw new Error('ACP conformance worker did not publish a terminal result before timeout');
84
+ }
85
+
86
+ async function waitForExit(pid, timeoutMs = 5000) {
87
+ const deadline = Date.now() + timeoutMs;
88
+ while (Date.now() < deadline) {
89
+ if (!processAlive(pid)) return true;
90
+ await new Promise((resolve) => setTimeout(resolve, 50));
91
+ }
92
+ return !processAlive(pid);
93
+ }
94
+
95
+ function createFixture(host) {
96
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `ubp-${host}-acp-conformance-`));
97
+ git(root, ['init', '-b', 'main']);
98
+ git(root, ['config', 'user.email', 'acp-conformance@example.invalid']);
99
+ git(root, ['config', 'user.name', 'Ultra ACP Conformance']);
100
+ fs.writeFileSync(path.join(root, 'README.md'), '# Ultra ACP conformance fixture\n');
101
+ fs.writeFileSync(path.join(root, '.gitignore'), '.ultra/.runtime/\n');
102
+ git(root, ['add', 'README.md', '.gitignore']);
103
+ git(root, ['commit', '-m', 'test: create ACP conformance fixture']);
104
+
105
+ const id = `${host}-${crypto.randomUUID()}`;
106
+ const delegation = path.join(root, '.ultra', '.runtime', 'delegations', id);
107
+ const worktree = path.join(root, '.ultra', '.runtime', 'worktrees', id);
108
+ fs.mkdirSync(path.dirname(worktree), { recursive: true });
109
+ git(root, ['worktree', 'add', '--detach', worktree, 'HEAD']);
110
+ fs.mkdirSync(delegation, { recursive: true });
111
+ const expectedResult = {
112
+ $schema: 'ultra-delegation-result-v1',
113
+ status: 'finished',
114
+ summary: 'ACP conformance completed.',
115
+ changed_files: [],
116
+ checks: [],
117
+ evidence: [],
118
+ questions: [],
119
+ residual_risks: [],
120
+ };
121
+ fs.writeFileSync(path.join(delegation, 'instruction.md'), [
122
+ '# ACP conformance',
123
+ '',
124
+ 'Do not use tools or modify files.',
125
+ 'Return exactly this one-line JSON object with no prose, fences, or field changes:',
126
+ JSON.stringify(expectedResult),
127
+ '',
128
+ ].join('\n'));
129
+ fs.writeFileSync(path.join(delegation, 'permission.json'), `${JSON.stringify({
130
+ $schema: 'ultra-delegation-permission-v1',
131
+ writable_roots: [],
132
+ external_effects: [],
133
+ }, null, 2)}\n`);
134
+ return { root, delegation, worktree };
135
+ }
136
+
137
+ function writeReceipt(output, receipt) {
138
+ fs.mkdirSync(path.dirname(output), { recursive: true });
139
+ fs.writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 });
140
+ }
141
+
142
+ async function runConformance(options) {
143
+ const profile = hostProfile(options.host);
144
+ if (!profile.acp || profile.acp.transport !== 'stdio'
145
+ || !profile.acp.protocolVersions.includes(1)) {
146
+ throw new Error(`host ${options.host} has no stable ACP v1 stdio candidate`);
147
+ }
148
+ const command = options.commandOverride || profile.acp.command({
149
+ host: options.host,
150
+ cliCommand: profile.binary,
151
+ env: process.env,
152
+ });
153
+ const args = options.argsOverride || profile.acp.argv({
154
+ host: options.host,
155
+ cliCommand: profile.binary,
156
+ env: process.env,
157
+ });
158
+ const fixture = createFixture(options.host);
159
+ let receipt;
160
+ try {
161
+ const runArgs = [
162
+ '--to', options.host,
163
+ '--instruction', path.join(fixture.delegation, 'instruction.md'),
164
+ '--permission', path.join(fixture.delegation, 'permission.json'),
165
+ '--worktree', fixture.worktree,
166
+ '--timeout', String(options.timeoutSeconds),
167
+ ];
168
+ if (profile.transportMaturity === 'experimental') runArgs.push('--ack-experimental');
169
+ const started = runDelegate(runArgs, {
170
+ projectRoot: fixture.root,
171
+ chooseExecutionDriver: () => ({
172
+ driver: 'acp',
173
+ command,
174
+ args,
175
+ protocolVersion: 1,
176
+ fallbackReason: null,
177
+ acpCapability: {
178
+ candidate: true,
179
+ surface: profile.acp.surface,
180
+ transport: 'stdio',
181
+ protocol_versions: [1],
182
+ conformance_status: 'probe',
183
+ conformance_evidence: null,
184
+ conformance_reason: 'Live conformance probe in progress.',
185
+ executable_available: true,
186
+ candidate_valid: true,
187
+ },
188
+ }),
189
+ });
190
+ const resultFile = path.join(fixture.delegation, 'result.json');
191
+ await waitForFile(resultFile, (options.timeoutSeconds * 1000) + 10000);
192
+ const terminal = JSON.parse(fs.readFileSync(resultFile, 'utf8'));
193
+ const workerExitObserved = await waitForExit(started.worker_pid);
194
+ const changedFiles = Array.isArray(terminal.changed_files) ? terminal.changed_files : [];
195
+ const gitClean = git(fixture.worktree, ['status', '--porcelain=v1', '--untracked-files=all']) === '';
196
+ const artifactFiles = {
197
+ result: resultFile,
198
+ stdout: path.join(fixture.delegation, 'stdout.log'),
199
+ stderr: path.join(fixture.delegation, 'stderr.log'),
200
+ events: path.join(fixture.delegation, 'events.jsonl'),
201
+ host_output: path.join(fixture.delegation, 'host-result.json'),
202
+ };
203
+ const artifactDigests = {};
204
+ for (const [name, file] of Object.entries(artifactFiles)) {
205
+ if (fs.existsSync(file) && fs.statSync(file).isFile()) artifactDigests[name] = artifactDigest(file);
206
+ }
207
+ const events = fs.existsSync(artifactFiles.events)
208
+ ? fs.readFileSync(artifactFiles.events, 'utf8').trim().split('\n').filter(Boolean)
209
+ : [];
210
+ const passed = terminal.status === 'finished'
211
+ && terminal.$schema === 'ultra-delegation-result-v1'
212
+ && terminal.execution_driver === 'acp'
213
+ && terminal.acp_protocol_version === 1
214
+ && terminal.acp_stop_reason === 'end_turn'
215
+ && changedFiles.length === 0
216
+ && gitClean
217
+ && workerExitObserved;
218
+ receipt = {
219
+ $schema: RECEIPT_SCHEMA,
220
+ host: options.host,
221
+ candidate: {
222
+ surface: profile.acp.surface,
223
+ command,
224
+ args,
225
+ protocol_versions: [1],
226
+ override_used: options.commandOverride !== null,
227
+ ...(options.host === 'zcode' ? {
228
+ bridge_source: ZCODE_ACP_SOURCE,
229
+ installed_runtime: profile.binary,
230
+ } : {}),
231
+ },
232
+ result: {
233
+ status: passed ? 'passed' : 'failed',
234
+ execution_driver: terminal.execution_driver || null,
235
+ protocol_version: terminal.acp_protocol_version ?? null,
236
+ stop_reason: terminal.acp_stop_reason ?? null,
237
+ adapter_exit_code: terminal.acp_adapter_exit_code ?? null,
238
+ adapter_exit_signal: terminal.acp_adapter_exit_signal ?? null,
239
+ git_clean: gitClean,
240
+ changed_files_count: changedFiles.length,
241
+ event_count: events.length,
242
+ worker_exit_observed: workerExitObserved,
243
+ failure_type: terminal.failure_type || null,
244
+ validation_diagnostic: terminal.failure_type === 'invalid_result'
245
+ ? terminal.summary
246
+ : null,
247
+ transport_diagnostic: typeof terminal.failure_type === 'string'
248
+ && terminal.failure_type.startsWith('acp_')
249
+ ? terminal.summary
250
+ : null,
251
+ },
252
+ artifact_digests: artifactDigests,
253
+ redaction: {
254
+ provider_content_retained: false,
255
+ prompt_retained: false,
256
+ hidden_reasoning_retained: false,
257
+ credentials_retained: false,
258
+ temporary_artifacts_removed: true,
259
+ },
260
+ observed_at: new Date().toISOString(),
261
+ };
262
+ writeReceipt(options.output, receipt);
263
+ if (!passed) throw new Error(`ACP conformance failed for ${options.host}: ${terminal.failure_type || 'terminal_mismatch'}`);
264
+ return receipt;
265
+ } finally {
266
+ fs.rmSync(fixture.root, { recursive: true, force: true });
267
+ }
268
+ }
269
+
270
+ async function main(argv = process.argv.slice(2)) {
271
+ const options = parseArgs(argv);
272
+ const receipt = await runConformance(options);
273
+ process.stdout.write(`${JSON.stringify({
274
+ host: receipt.host,
275
+ status: receipt.result.status,
276
+ receipt: options.output,
277
+ })}\n`);
278
+ }
279
+
280
+ if (require.main === module) {
281
+ main().catch((error) => {
282
+ process.stderr.write(`${error.message}\n`);
283
+ process.exitCode = 1;
284
+ });
285
+ }
286
+
287
+ module.exports = { parseArgs, runConformance };