@byok-sdk/client 0.3.0 → 0.4.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 +13 -0
- package/dist/adapters/claude/claude-adapter.d.ts +4 -20
- package/dist/adapters/claude/events.d.ts +3 -0
- package/dist/adapters/claude/process-client.d.ts +9 -1
- package/dist/adapters/codex/codex-adapter.d.ts +4 -16
- package/dist/adapters/codex/process-runner.d.ts +4 -1
- package/dist/adapters/index.d.ts +3 -1
- package/dist/adapters/index.js +923 -258
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/pi-adapter.d.ts +3 -16
- package/dist/adapters/pi/rpc-client.d.ts +9 -1
- package/dist/adapters/process-tree.d.ts +19 -0
- package/dist/bin/audit-log.d.ts +12 -0
- package/dist/bin/byok-agent.js +1293 -484
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/commands/workspaces.d.ts +11 -0
- package/dist/bin/format.d.ts +13 -0
- package/dist/bin/runtime-probe.d.ts +1 -1
- package/dist/bin/tasks-view.d.ts +13 -0
- package/dist/daemon/approvals.d.ts +2 -2
- package/dist/daemon/connection-manager.d.ts +4 -2
- package/dist/daemon/control-server.d.ts +18 -1
- package/dist/daemon/create-daemon.d.ts +2 -2
- package/dist/daemon/daemon-owner.d.ts +4 -2
- package/dist/daemon/environment.d.ts +9 -9
- package/dist/daemon/git-workspace.d.ts +21 -0
- package/dist/daemon/observer.d.ts +13 -0
- package/dist/daemon/presence-publisher.d.ts +29 -0
- package/dist/daemon/runtime-capabilities.d.ts +1 -1
- package/dist/daemon/task-runner.d.ts +27 -34
- package/dist/daemon/ws-transport.d.ts +3 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1254 -432
- package/dist/index.js.map +1 -1
- package/dist/runtime-failure.d.ts +64 -0
- package/dist/types.d.ts +100 -73
- package/package.json +4 -4
package/dist/bin/byok-agent.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readFileSync, promises, linkSync, fstatSync, lstatSync, unlinkSync, con
|
|
|
5
5
|
import path20, { isAbsolute, join } from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import { DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
8
|
-
import { TASK_STATES, ToolsetIdSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, createEnvelope, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, RESULT_DOCUMENT_MAX_BYTES, PROTOCOL_VERSION, decodeEnvelope, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, BYOK_EVENTS_PATH, parseMessage, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
8
|
+
import { TASK_STATES, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, createEnvelope, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, RESULT_DOCUMENT_MAX_BYTES, PROTOCOL_VERSION, decodeEnvelope, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, BYOK_EVENTS_PATH, parseMessage, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import 'readline';
|
|
@@ -15,6 +15,52 @@ import { createRequire } from 'module';
|
|
|
15
15
|
import { createInterface } from 'readline/promises';
|
|
16
16
|
|
|
17
17
|
// src/types.ts
|
|
18
|
+
function frozenStrings(values) {
|
|
19
|
+
return values === void 0 ? void 0 : Object.freeze([...values]);
|
|
20
|
+
}
|
|
21
|
+
function frozenPolicy(policy) {
|
|
22
|
+
const allowTools = policy.allowTools === void 0 ? void 0 : Object.freeze([...policy.allowTools]);
|
|
23
|
+
const denyTools = policy.denyTools === void 0 ? void 0 : Object.freeze([...policy.denyTools]);
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
mode: policy.mode,
|
|
26
|
+
...allowTools === void 0 ? {} : { allowTools },
|
|
27
|
+
...denyTools === void 0 ? {} : { denyTools },
|
|
28
|
+
...policy.workspaceRoot === void 0 ? {} : { workspaceRoot: policy.workspaceRoot },
|
|
29
|
+
...policy.network === void 0 ? {} : { network: policy.network }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function freezeRuntimeAdapterDescriptor(descriptor) {
|
|
33
|
+
const baseNames = frozenStrings(descriptor.environmentRequirements.baseNames);
|
|
34
|
+
const credentialNames = frozenStrings(descriptor.environmentRequirements.credentialNames);
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
id: descriptor.id,
|
|
37
|
+
supportsDispatchSelection: descriptor.supportsDispatchSelection === true,
|
|
38
|
+
capabilities: Object.freeze({
|
|
39
|
+
steer: descriptor.capabilities.steer === true,
|
|
40
|
+
resume: descriptor.capabilities.resume === true,
|
|
41
|
+
approvalInteractive: descriptor.capabilities.approvalInteractive === true,
|
|
42
|
+
...descriptor.capabilities.mcpToolsets === void 0 ? {} : { mcpToolsets: descriptor.capabilities.mcpToolsets === true },
|
|
43
|
+
permissionModes: Object.freeze([...descriptor.capabilities.permissionModes])
|
|
44
|
+
}),
|
|
45
|
+
environmentRequirements: Object.freeze({
|
|
46
|
+
...baseNames === void 0 ? {} : { baseNames },
|
|
47
|
+
...credentialNames === void 0 ? {} : { credentialNames }
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function sealRuntimeOperationManifest(manifest) {
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
taskId: manifest.taskId,
|
|
54
|
+
runtimeId: manifest.runtimeId,
|
|
55
|
+
descriptor: freezeRuntimeAdapterDescriptor(manifest.descriptor),
|
|
56
|
+
policy: frozenPolicy(manifest.policy),
|
|
57
|
+
requiredToolsetIds: Object.freeze([...manifest.requiredToolsetIds]),
|
|
58
|
+
...manifest.dispatchSelection === void 0 ? {} : { dispatchSelection: Object.freeze({ ...manifest.dispatchSelection }) },
|
|
59
|
+
...manifest.sessionRef === void 0 ? {} : { sessionRef: manifest.sessionRef },
|
|
60
|
+
workspace: Object.freeze({ ...manifest.workspace }),
|
|
61
|
+
forwardedEnvironmentNames: Object.freeze([...manifest.forwardedEnvironmentNames])
|
|
62
|
+
});
|
|
63
|
+
}
|
|
18
64
|
var PolicyUnsupportedError = class extends Error {
|
|
19
65
|
constructor(message) {
|
|
20
66
|
super(message);
|
|
@@ -22,7 +68,7 @@ var PolicyUnsupportedError = class extends Error {
|
|
|
22
68
|
}
|
|
23
69
|
};
|
|
24
70
|
var SteerUnsupportedError = class extends Error {
|
|
25
|
-
/** The `RuntimeAdapter.id` that cannot steer (e.g. `claude`, `codex`). */
|
|
71
|
+
/** The `RuntimeAdapter.descriptor.id` that cannot steer (e.g. `claude`, `codex`). */
|
|
26
72
|
runtimeId;
|
|
27
73
|
constructor(runtimeId, message) {
|
|
28
74
|
super(message);
|
|
@@ -30,6 +76,112 @@ var SteerUnsupportedError = class extends Error {
|
|
|
30
76
|
this.runtimeId = runtimeId;
|
|
31
77
|
}
|
|
32
78
|
};
|
|
79
|
+
|
|
80
|
+
// src/runtime-failure.ts
|
|
81
|
+
var RUNTIME_EXECUTION_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeExecutionFailure/v1");
|
|
82
|
+
var RUNTIME_DISPOSAL_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeDisposalFailure/v1");
|
|
83
|
+
var RuntimeDisposalFailure = class extends Error {
|
|
84
|
+
stage;
|
|
85
|
+
constructor(input, options) {
|
|
86
|
+
if (!isRuntimeDisposalStage(input.stage) || typeof input.reason !== "string" || input.reason.length === 0) {
|
|
87
|
+
throw new TypeError("invalid RuntimeDisposalFailure input");
|
|
88
|
+
}
|
|
89
|
+
super(input.reason, options);
|
|
90
|
+
this.name = "RuntimeDisposalFailure";
|
|
91
|
+
this.stage = input.stage;
|
|
92
|
+
Object.defineProperty(this, RUNTIME_DISPOSAL_FAILURE_BRAND, { value: true });
|
|
93
|
+
Object.freeze(this);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
function isRuntimeDisposalStage(value) {
|
|
97
|
+
return value === "signal" || value === "quiescence" || value === "cleanup";
|
|
98
|
+
}
|
|
99
|
+
function isRuntimeDisposalFailure(value) {
|
|
100
|
+
if (typeof value !== "object" || value === null) return false;
|
|
101
|
+
const candidate = value;
|
|
102
|
+
return candidate[RUNTIME_DISPOSAL_FAILURE_BRAND] === true && isRuntimeDisposalStage(candidate.stage) && typeof candidate.message === "string" && candidate.message.length > 0;
|
|
103
|
+
}
|
|
104
|
+
var RuntimeExecutionFailure = class extends Error {
|
|
105
|
+
phase;
|
|
106
|
+
category;
|
|
107
|
+
retry;
|
|
108
|
+
constructor(input, options) {
|
|
109
|
+
if (!isRuntimeFailurePhase(input.phase) || !isRuntimeFailureCategory(input.category) || !isRuntimeRetryDisposition(input.retry) || typeof input.reason !== "string" || input.reason.length === 0) {
|
|
110
|
+
throw new TypeError("invalid RuntimeExecutionFailure input");
|
|
111
|
+
}
|
|
112
|
+
super(input.reason, options);
|
|
113
|
+
this.name = "RuntimeExecutionFailure";
|
|
114
|
+
this.phase = input.phase;
|
|
115
|
+
this.category = input.category;
|
|
116
|
+
this.retry = input.retry;
|
|
117
|
+
Object.defineProperty(this, RUNTIME_EXECUTION_FAILURE_BRAND, { value: true });
|
|
118
|
+
Object.freeze(this);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
function isRuntimeFailurePhase(value) {
|
|
122
|
+
return value === "start" || value === "run";
|
|
123
|
+
}
|
|
124
|
+
function isRuntimeFailureCategory(value) {
|
|
125
|
+
return value === "semantic" || value === "infrastructure" || value === "authority";
|
|
126
|
+
}
|
|
127
|
+
function isRuntimeRetryDisposition(value) {
|
|
128
|
+
return value === "retryable" || value === "non-retryable";
|
|
129
|
+
}
|
|
130
|
+
function isRuntimeExecutionFailure(value) {
|
|
131
|
+
if (typeof value !== "object" || value === null) return false;
|
|
132
|
+
const candidate = value;
|
|
133
|
+
return candidate[RUNTIME_EXECUTION_FAILURE_BRAND] === true && isRuntimeFailurePhase(candidate.phase) && isRuntimeFailureCategory(candidate.category) && isRuntimeRetryDisposition(candidate.retry) && typeof candidate.message === "string" && candidate.message.length > 0;
|
|
134
|
+
}
|
|
135
|
+
function retryableFromDisposition(disposition) {
|
|
136
|
+
switch (disposition) {
|
|
137
|
+
case "retryable":
|
|
138
|
+
return true;
|
|
139
|
+
case "non-retryable":
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function projectRuntimeExecutionFailure(failure) {
|
|
144
|
+
return {
|
|
145
|
+
reason: failure.message,
|
|
146
|
+
retryable: retryableFromDisposition(failure.retry)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
var RUNTIME_ADAPTER_CONTRACT_VIOLATION_REASON = Object.freeze({
|
|
150
|
+
start: "runtime adapter contract violation during start",
|
|
151
|
+
run: "runtime adapter contract violation during run"
|
|
152
|
+
});
|
|
153
|
+
function projectRuntimeBoundaryFailure(value, expectedPhase) {
|
|
154
|
+
if (isRuntimeExecutionFailure(value) && value.phase === expectedPhase) {
|
|
155
|
+
return { ...projectRuntimeExecutionFailure(value), contractViolation: false };
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
reason: RUNTIME_ADAPTER_CONTRACT_VIOLATION_REASON[expectedPhase],
|
|
159
|
+
retryable: false,
|
|
160
|
+
contractViolation: true
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
var GIT_ERROR_CATEGORIES = [
|
|
164
|
+
"git-unavailable",
|
|
165
|
+
"git-timeout",
|
|
166
|
+
"git-output-limit",
|
|
167
|
+
"git-command-failed",
|
|
168
|
+
"workspace-root-invalid",
|
|
169
|
+
"workspace-root-conflict",
|
|
170
|
+
"workspace-not-owned",
|
|
171
|
+
"repository-root-mismatch",
|
|
172
|
+
"repository-invalid",
|
|
173
|
+
"lease-busy",
|
|
174
|
+
"ledger-invalid"
|
|
175
|
+
];
|
|
176
|
+
var GIT_WORKSPACE_PHASES = [
|
|
177
|
+
"preparing",
|
|
178
|
+
"active",
|
|
179
|
+
"completed",
|
|
180
|
+
"failed",
|
|
181
|
+
"cancelled",
|
|
182
|
+
"interrupted",
|
|
183
|
+
"salvage"
|
|
184
|
+
];
|
|
33
185
|
var GitWorkspaceError = class extends Error {
|
|
34
186
|
constructor(category, message = category) {
|
|
35
187
|
super(message);
|
|
@@ -945,6 +1097,155 @@ var AsyncQueue = class {
|
|
|
945
1097
|
};
|
|
946
1098
|
}
|
|
947
1099
|
};
|
|
1100
|
+
var DEFAULT_TERM_GRACE_MS = 750;
|
|
1101
|
+
var DEFAULT_KILL_GRACE_MS = 2e3;
|
|
1102
|
+
var POLL_MS = 20;
|
|
1103
|
+
var terminationRequested = /* @__PURE__ */ new WeakSet();
|
|
1104
|
+
var terminationRequestFailed = /* @__PURE__ */ new WeakSet();
|
|
1105
|
+
function withOwnedProcessTree(options) {
|
|
1106
|
+
return {
|
|
1107
|
+
...options,
|
|
1108
|
+
...process.platform === "win32" ? { windowsHide: true } : { detached: true }
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function positivePid(child, label) {
|
|
1112
|
+
const pid = child.pid;
|
|
1113
|
+
if (pid === void 0) return void 0;
|
|
1114
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || pid === process.pid) {
|
|
1115
|
+
throw new RuntimeDisposalFailure({
|
|
1116
|
+
stage: "signal",
|
|
1117
|
+
reason: `${label} runtime process has an unsafe owned pid`
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
return pid;
|
|
1121
|
+
}
|
|
1122
|
+
function groupExists(pid, label) {
|
|
1123
|
+
try {
|
|
1124
|
+
process.kill(-pid, 0);
|
|
1125
|
+
return true;
|
|
1126
|
+
} catch (cause) {
|
|
1127
|
+
const code = cause.code;
|
|
1128
|
+
if (code === "ESRCH") return false;
|
|
1129
|
+
if (code === "EPERM") return true;
|
|
1130
|
+
throw new RuntimeDisposalFailure({
|
|
1131
|
+
stage: "quiescence",
|
|
1132
|
+
reason: `${label} runtime process-group state could not be verified`
|
|
1133
|
+
}, { cause });
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function signalGroup(pid, signal, label) {
|
|
1137
|
+
try {
|
|
1138
|
+
process.kill(-pid, signal);
|
|
1139
|
+
} catch (cause) {
|
|
1140
|
+
const code = cause.code;
|
|
1141
|
+
if (code === "ESRCH" || code === "EPERM") return;
|
|
1142
|
+
throw new RuntimeDisposalFailure({
|
|
1143
|
+
stage: "signal",
|
|
1144
|
+
reason: `${label} runtime process group ${pid} could not receive ${signal} (${code ?? "unknown"})`
|
|
1145
|
+
}, { cause });
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
async function waitUntil(predicate, timeoutMs) {
|
|
1149
|
+
const deadline = Date.now() + timeoutMs;
|
|
1150
|
+
while (predicate()) {
|
|
1151
|
+
if (Date.now() >= deadline) return false;
|
|
1152
|
+
await new Promise((resolve) => {
|
|
1153
|
+
setTimeout(resolve, POLL_MS);
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1158
|
+
async function waitWithDeadline(promise, timeoutMs) {
|
|
1159
|
+
return new Promise((resolve) => {
|
|
1160
|
+
let settled = false;
|
|
1161
|
+
const timer = setTimeout(() => {
|
|
1162
|
+
if (!settled) {
|
|
1163
|
+
settled = true;
|
|
1164
|
+
resolve(false);
|
|
1165
|
+
}
|
|
1166
|
+
}, timeoutMs);
|
|
1167
|
+
void promise.then(
|
|
1168
|
+
() => {
|
|
1169
|
+
if (!settled) {
|
|
1170
|
+
settled = true;
|
|
1171
|
+
clearTimeout(timer);
|
|
1172
|
+
resolve(true);
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
() => {
|
|
1176
|
+
if (!settled) {
|
|
1177
|
+
settled = true;
|
|
1178
|
+
clearTimeout(timer);
|
|
1179
|
+
resolve(false);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
function requestOwnedProcessTreeTermination(options) {
|
|
1186
|
+
if (options.isClosed()) return;
|
|
1187
|
+
const pid = positivePid(options.child, options.label);
|
|
1188
|
+
if (pid === void 0) return;
|
|
1189
|
+
if (process.platform === "win32") {
|
|
1190
|
+
const result = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true });
|
|
1191
|
+
if (result.error) {
|
|
1192
|
+
throw new RuntimeDisposalFailure({
|
|
1193
|
+
stage: "signal",
|
|
1194
|
+
reason: `${options.label} runtime process tree could not be terminated`
|
|
1195
|
+
}, { cause: result.error });
|
|
1196
|
+
}
|
|
1197
|
+
terminationRequested.add(options.child);
|
|
1198
|
+
if (result.status !== 0) terminationRequestFailed.add(options.child);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
signalGroup(pid, "SIGTERM", options.label);
|
|
1202
|
+
terminationRequested.add(options.child);
|
|
1203
|
+
}
|
|
1204
|
+
async function disposeOwnedProcessTree(options) {
|
|
1205
|
+
const pid = positivePid(options.child, options.label);
|
|
1206
|
+
const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
|
|
1207
|
+
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
1208
|
+
if (pid === void 0) {
|
|
1209
|
+
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
1210
|
+
throw new RuntimeDisposalFailure({
|
|
1211
|
+
stage: "quiescence",
|
|
1212
|
+
reason: `${options.label} runtime process did not settle after spawn failure`
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
if (process.platform === "win32") {
|
|
1216
|
+
if (!options.isClosed() && !terminationRequested.has(options.child)) requestOwnedProcessTreeTermination(options);
|
|
1217
|
+
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
1218
|
+
if (terminationRequestFailed.has(options.child)) {
|
|
1219
|
+
throw new RuntimeDisposalFailure({
|
|
1220
|
+
stage: "signal",
|
|
1221
|
+
reason: `${options.label} runtime process tree could not be terminated`
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
throw new RuntimeDisposalFailure({
|
|
1225
|
+
stage: "quiescence",
|
|
1226
|
+
reason: `${options.label} runtime process tree did not close before the disposal deadline`
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
if (groupExists(pid, options.label) && !terminationRequested.has(options.child)) {
|
|
1230
|
+
signalGroup(pid, "SIGTERM", options.label);
|
|
1231
|
+
terminationRequested.add(options.child);
|
|
1232
|
+
}
|
|
1233
|
+
if (!await waitUntil(() => groupExists(pid, options.label), termGraceMs)) {
|
|
1234
|
+
signalGroup(pid, "SIGKILL", options.label);
|
|
1235
|
+
if (!await waitUntil(() => groupExists(pid, options.label), killGraceMs)) {
|
|
1236
|
+
throw new RuntimeDisposalFailure({
|
|
1237
|
+
stage: "quiescence",
|
|
1238
|
+
reason: `${options.label} runtime process group remained live after SIGKILL`
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
1243
|
+
throw new RuntimeDisposalFailure({
|
|
1244
|
+
stage: "quiescence",
|
|
1245
|
+
reason: `${options.label} runtime root did not emit close after its process group exited`
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
948
1249
|
|
|
949
1250
|
// src/adapters/pi/rpc-client.ts
|
|
950
1251
|
var STDERR_RING_CAPACITY = 20;
|
|
@@ -957,16 +1258,22 @@ var PiRpcClient = class {
|
|
|
957
1258
|
eventQueue = new AsyncQueue();
|
|
958
1259
|
closed = false;
|
|
959
1260
|
exitError;
|
|
1261
|
+
closedPromise;
|
|
1262
|
+
resolveClosed;
|
|
1263
|
+
disposalAttempt;
|
|
960
1264
|
/** Bounded tail of recent stderr lines — pi discarded this entirely before (nothing ever read `child.stderr`), which is exactly why finding #1 (`Error: Unknown option: --session-id`, exit 1) had to be root-caused by hand instead of reading it off a thrown error. See `buildExitError`. */
|
|
961
1265
|
stderrRing = [];
|
|
962
1266
|
/** Count of pi RPC message types `PiSession` (pi-adapter.ts) has told us have no `AgentEvent` mapping and aren't routine bookkeeping — see `recordUnmappedFrame`. */
|
|
963
1267
|
unmappedFrameCounts = /* @__PURE__ */ new Map();
|
|
964
1268
|
constructor(options) {
|
|
965
1269
|
const spawnFn = options.spawnFn ?? spawn;
|
|
966
|
-
this.child = spawnFn(options.command, options.args, {
|
|
1270
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
967
1271
|
cwd: options.cwd,
|
|
968
1272
|
env: options.env,
|
|
969
1273
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1274
|
+
}));
|
|
1275
|
+
this.closedPromise = new Promise((resolve) => {
|
|
1276
|
+
this.resolveClosed = resolve;
|
|
970
1277
|
});
|
|
971
1278
|
this.child.stdout.setEncoding("utf8");
|
|
972
1279
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -1001,6 +1308,10 @@ var PiRpcClient = class {
|
|
|
1001
1308
|
get events() {
|
|
1002
1309
|
return this.eventQueue;
|
|
1003
1310
|
}
|
|
1311
|
+
/** Local transport diagnostic retained when the process closes; consumers must classify it explicitly. */
|
|
1312
|
+
get terminalError() {
|
|
1313
|
+
return this.exitError;
|
|
1314
|
+
}
|
|
1004
1315
|
/**
|
|
1005
1316
|
* Record a pi RPC message `type` that `PiSession` (pi-adapter.ts) decided
|
|
1006
1317
|
* has no `AgentEvent` mapping and isn't routine bookkeeping (see
|
|
@@ -1019,15 +1330,30 @@ var PiRpcClient = class {
|
|
|
1019
1330
|
);
|
|
1020
1331
|
}
|
|
1021
1332
|
}
|
|
1022
|
-
/**
|
|
1333
|
+
/** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
|
|
1023
1334
|
kill() {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1335
|
+
requestOwnedProcessTreeTermination(this.processTreeOptions());
|
|
1336
|
+
}
|
|
1337
|
+
waitClosed() {
|
|
1338
|
+
return this.closedPromise;
|
|
1339
|
+
}
|
|
1340
|
+
dispose() {
|
|
1341
|
+
if (!this.disposalAttempt) {
|
|
1342
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
1343
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
1344
|
+
this.disposalAttempt = void 0;
|
|
1345
|
+
throw error;
|
|
1346
|
+
});
|
|
1030
1347
|
}
|
|
1348
|
+
return this.disposalAttempt;
|
|
1349
|
+
}
|
|
1350
|
+
processTreeOptions() {
|
|
1351
|
+
return {
|
|
1352
|
+
child: this.child,
|
|
1353
|
+
waitClosed: () => this.closedPromise,
|
|
1354
|
+
isClosed: () => this.closed,
|
|
1355
|
+
label: "pi"
|
|
1356
|
+
};
|
|
1031
1357
|
}
|
|
1032
1358
|
onData(chunk) {
|
|
1033
1359
|
this.buffer += chunk;
|
|
@@ -1113,6 +1439,7 @@ var PiRpcClient = class {
|
|
|
1113
1439
|
if (this.closed) return;
|
|
1114
1440
|
this.closed = true;
|
|
1115
1441
|
this.exitError = err;
|
|
1442
|
+
this.resolveClosed();
|
|
1116
1443
|
for (const [, waiter] of this.pending) waiter.reject(err);
|
|
1117
1444
|
this.pending.clear();
|
|
1118
1445
|
this.eventQueue.end();
|
|
@@ -1183,8 +1510,17 @@ var PiAdapter = class {
|
|
|
1183
1510
|
this.options = options;
|
|
1184
1511
|
}
|
|
1185
1512
|
options;
|
|
1186
|
-
|
|
1187
|
-
|
|
1513
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
1514
|
+
id: "pi",
|
|
1515
|
+
supportsDispatchSelection: true,
|
|
1516
|
+
capabilities: {
|
|
1517
|
+
steer: true,
|
|
1518
|
+
resume: true,
|
|
1519
|
+
approvalInteractive: false,
|
|
1520
|
+
permissionModes: ["auto", "readonly"]
|
|
1521
|
+
},
|
|
1522
|
+
environmentRequirements: { credentialNames: PROVIDER_CREDENTIAL_ENV_NAMES }
|
|
1523
|
+
});
|
|
1188
1524
|
async detect() {
|
|
1189
1525
|
try {
|
|
1190
1526
|
const bin = this.resolveBin();
|
|
@@ -1196,49 +1532,26 @@ var PiAdapter = class {
|
|
|
1196
1532
|
return { present: false };
|
|
1197
1533
|
}
|
|
1198
1534
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* M5: pi authenticates to its ~30 supported providers via env-var API
|
|
1204
|
-
* keys — `detect()`'s own `authPresent` probe above checks this identical
|
|
1205
|
-
* list — so these MUST keep flowing into pi's spawned process or pi auth
|
|
1206
|
-
* breaks entirely. `KNOWN_PROVIDER_ENV_VARS` above is the single source
|
|
1207
|
-
* of truth, reused here rather than duplicated. No `baseNames`: nothing
|
|
1208
|
-
* in this adapter or `rpc-client.ts` reads a pi-specific config-discovery
|
|
1209
|
-
* variable beyond the platform baseline (`daemon/environment.ts`).
|
|
1210
|
-
*/
|
|
1211
|
-
environmentRequirements() {
|
|
1212
|
-
return { credentialNames: PROVIDER_CREDENTIAL_ENV_NAMES };
|
|
1213
|
-
}
|
|
1214
|
-
async start(task, ctx) {
|
|
1215
|
-
if (typeof task.instruction !== "string") {
|
|
1216
|
-
throw new PolicyUnsupportedError("pi adapter only supports string instructions in M0 (no blob-ref fetch yet)");
|
|
1217
|
-
}
|
|
1218
|
-
const mapping = mapPermissionPolicyToPiArgs(ctx.policy);
|
|
1535
|
+
async prepare(input) {
|
|
1536
|
+
const mapping = mapPermissionPolicyToPiArgs(input.policy);
|
|
1219
1537
|
if (!mapping.ok) {
|
|
1220
|
-
|
|
1538
|
+
return { kind: "reject", reason: mapping.reason ?? "policy rejected by pi adapter", retryable: false };
|
|
1221
1539
|
}
|
|
1222
1540
|
const bin = this.resolveBin();
|
|
1223
|
-
const
|
|
1224
|
-
const
|
|
1225
|
-
const selection = task.dispatchSelection;
|
|
1541
|
+
const selection = input.offer.dispatchSelection;
|
|
1542
|
+
const pinnedSelection = selection === void 0 ? void 0 : Object.freeze({ ...selection });
|
|
1226
1543
|
let command = bin.command;
|
|
1227
|
-
let
|
|
1228
|
-
if (
|
|
1229
|
-
if (
|
|
1230
|
-
|
|
1231
|
-
`pi adapter cannot execute ${selection.lane} selection for runtime ${selection.runtimeId}`
|
|
1232
|
-
);
|
|
1544
|
+
let launcherArgs;
|
|
1545
|
+
if (pinnedSelection !== void 0) {
|
|
1546
|
+
if (pinnedSelection.lane !== "byok" || pinnedSelection.runtimeId !== "pi") {
|
|
1547
|
+
return { kind: "reject", reason: `pi adapter cannot execute ${pinnedSelection.lane} selection for runtime ${pinnedSelection.runtimeId}`, retryable: false };
|
|
1233
1548
|
}
|
|
1234
1549
|
const launcher = this.options.byokLauncher;
|
|
1235
1550
|
if (launcher === void 0) {
|
|
1236
|
-
|
|
1237
|
-
"pi BYOK selection requires a configured credential-custody launcher"
|
|
1238
|
-
);
|
|
1551
|
+
return { kind: "reject", reason: "pi BYOK selection requires a configured credential-custody launcher", retryable: false };
|
|
1239
1552
|
}
|
|
1240
1553
|
command = launcher.command;
|
|
1241
|
-
|
|
1554
|
+
launcherArgs = [
|
|
1242
1555
|
...launcher.args ?? [],
|
|
1243
1556
|
"--pi-bin",
|
|
1244
1557
|
bin.command,
|
|
@@ -1248,62 +1561,136 @@ var PiAdapter = class {
|
|
|
1248
1561
|
launcher.sessionDir,
|
|
1249
1562
|
...launcher.secretServicePrefix ? ["--secret-service-prefix", launcher.secretServicePrefix] : [],
|
|
1250
1563
|
"--provider",
|
|
1251
|
-
|
|
1564
|
+
pinnedSelection.providerId,
|
|
1252
1565
|
"--model",
|
|
1253
|
-
|
|
1254
|
-
"--",
|
|
1255
|
-
...piArgs
|
|
1566
|
+
pinnedSelection.modelId
|
|
1256
1567
|
];
|
|
1257
1568
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1569
|
+
return {
|
|
1570
|
+
kind: "prepared",
|
|
1571
|
+
operation: {
|
|
1572
|
+
start: async (startInput) => {
|
|
1573
|
+
const manifestSelection = startInput.manifest.dispatchSelection;
|
|
1574
|
+
if (!sameDispatchSelection(manifestSelection, pinnedSelection)) {
|
|
1575
|
+
throw new RuntimeExecutionFailure({
|
|
1576
|
+
phase: "start",
|
|
1577
|
+
category: "authority",
|
|
1578
|
+
retry: "non-retryable",
|
|
1579
|
+
reason: "prepared pi operation received a manifest with different runtime selection"
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
if (typeof startInput.instruction !== "string") {
|
|
1583
|
+
throw new RuntimeExecutionFailure({
|
|
1584
|
+
phase: "start",
|
|
1585
|
+
category: "authority",
|
|
1586
|
+
retry: "non-retryable",
|
|
1587
|
+
reason: "prepared pi operation requires a resolved string instruction"
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
const resumeSessionId = startInput.manifest.sessionRef;
|
|
1591
|
+
const piArgs = ["--mode", "rpc", ...resumeSessionId ? ["--session", resumeSessionId] : [], ...mapping.args];
|
|
1592
|
+
const args = launcherArgs === void 0 ? piArgs : [...launcherArgs, "--", ...piArgs];
|
|
1593
|
+
let rpc;
|
|
1594
|
+
try {
|
|
1595
|
+
rpc = new PiRpcClient({
|
|
1596
|
+
command,
|
|
1597
|
+
args,
|
|
1598
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
1599
|
+
env: manifestSelection === void 0 ? startInput.env : withoutProviderCredentials(startInput.env),
|
|
1600
|
+
spawnFn: this.options.spawnFn
|
|
1601
|
+
});
|
|
1602
|
+
} catch (cause) {
|
|
1603
|
+
throw new RuntimeExecutionFailure({
|
|
1604
|
+
phase: "start",
|
|
1605
|
+
category: "infrastructure",
|
|
1606
|
+
retry: "retryable",
|
|
1607
|
+
reason: "pi runtime process could not be spawned"
|
|
1608
|
+
}, { cause });
|
|
1609
|
+
}
|
|
1610
|
+
let response;
|
|
1611
|
+
try {
|
|
1612
|
+
response = await rpc.send({ type: "prompt", message: startInput.instruction });
|
|
1613
|
+
} catch (cause) {
|
|
1614
|
+
rpc.kill();
|
|
1615
|
+
throw new RuntimeExecutionFailure({
|
|
1616
|
+
phase: "start",
|
|
1617
|
+
category: "infrastructure",
|
|
1618
|
+
retry: "retryable",
|
|
1619
|
+
reason: `pi initial prompt transport failed: ${errorMessage(cause)}`
|
|
1620
|
+
}, { cause });
|
|
1621
|
+
}
|
|
1622
|
+
if (response.success === false) {
|
|
1623
|
+
rpc.kill();
|
|
1624
|
+
throw new RuntimeExecutionFailure({
|
|
1625
|
+
phase: "start",
|
|
1626
|
+
category: "semantic",
|
|
1627
|
+
retry: "non-retryable",
|
|
1628
|
+
reason: typeof response.error === "string" ? response.error : "pi rejected the initial prompt"
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
let sessionRef;
|
|
1632
|
+
try {
|
|
1633
|
+
sessionRef = await resolveAuthoritativeSessionId(rpc);
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
rpc.kill();
|
|
1636
|
+
throw err;
|
|
1637
|
+
}
|
|
1638
|
+
if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
|
|
1639
|
+
rpc.kill();
|
|
1640
|
+
throw new RuntimeExecutionFailure({
|
|
1641
|
+
phase: "start",
|
|
1642
|
+
category: "authority",
|
|
1643
|
+
retry: "non-retryable",
|
|
1644
|
+
reason: "pi resumed a different authoritative session than requested"
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
return new PiSession(sessionRef, rpc, manifestSelection);
|
|
1648
|
+
}
|
|
1279
1649
|
}
|
|
1280
|
-
}
|
|
1281
|
-
return new PiSession(sessionRef, rpc, selection);
|
|
1650
|
+
};
|
|
1282
1651
|
}
|
|
1283
1652
|
resolveBin() {
|
|
1284
1653
|
return (this.options.resolveBin ?? resolvePiBin)();
|
|
1285
1654
|
}
|
|
1286
1655
|
};
|
|
1287
|
-
|
|
1656
|
+
function sameDispatchSelection(left, right) {
|
|
1657
|
+
if (left === void 0 || right === void 0) return left === right;
|
|
1658
|
+
return left.lane === right.lane && left.runtimeId === right.runtimeId && left.providerId === right.providerId && left.modelId === right.modelId;
|
|
1659
|
+
}
|
|
1660
|
+
async function resolveAuthoritativeSessionId(rpc) {
|
|
1288
1661
|
let state;
|
|
1289
1662
|
try {
|
|
1290
1663
|
state = await rpc.send({ type: "get_state" });
|
|
1291
1664
|
} catch (err) {
|
|
1292
|
-
|
|
1665
|
+
if (isRuntimeExecutionFailure(err)) throw err;
|
|
1666
|
+
throw new RuntimeExecutionFailure({
|
|
1667
|
+
phase: "start",
|
|
1668
|
+
category: "infrastructure",
|
|
1669
|
+
retry: "retryable",
|
|
1670
|
+
reason: `pi transport ended before yielding an authoritative session id: ${errorMessage(err)}`
|
|
1671
|
+
}, {
|
|
1293
1672
|
cause: err
|
|
1294
1673
|
});
|
|
1295
1674
|
}
|
|
1296
1675
|
if (state.success === false) {
|
|
1297
1676
|
const reason = typeof state.error === "string" ? state.error : "get_state reported failure";
|
|
1298
|
-
throw new
|
|
1677
|
+
throw new RuntimeExecutionFailure({
|
|
1678
|
+
phase: "start",
|
|
1679
|
+
category: "authority",
|
|
1680
|
+
retry: "non-retryable",
|
|
1681
|
+
reason: `pi did not yield an authoritative session id: ${reason}`
|
|
1682
|
+
});
|
|
1299
1683
|
}
|
|
1300
1684
|
const data = state.data;
|
|
1301
1685
|
if (typeof data?.sessionId === "string" && data.sessionId.length > 0) {
|
|
1302
1686
|
return data.sessionId;
|
|
1303
1687
|
}
|
|
1304
|
-
throw new
|
|
1305
|
-
|
|
1306
|
-
|
|
1688
|
+
throw new RuntimeExecutionFailure({
|
|
1689
|
+
phase: "start",
|
|
1690
|
+
category: "authority",
|
|
1691
|
+
retry: "non-retryable",
|
|
1692
|
+
reason: "pi get_state reported no authoritative session id"
|
|
1693
|
+
});
|
|
1307
1694
|
}
|
|
1308
1695
|
var PiSession = class {
|
|
1309
1696
|
constructor(sessionRef, rpc, selection) {
|
|
@@ -1319,12 +1706,40 @@ var PiSession = class {
|
|
|
1319
1706
|
return {
|
|
1320
1707
|
[Symbol.asyncIterator]() {
|
|
1321
1708
|
const inner = rpc.events[Symbol.asyncIterator]();
|
|
1709
|
+
let terminalFailure;
|
|
1322
1710
|
return {
|
|
1323
1711
|
async next() {
|
|
1324
1712
|
for (; ; ) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1713
|
+
if (terminalFailure) throw terminalFailure;
|
|
1714
|
+
let result;
|
|
1715
|
+
try {
|
|
1716
|
+
result = await inner.next();
|
|
1717
|
+
} catch (cause) {
|
|
1718
|
+
throw new RuntimeExecutionFailure({
|
|
1719
|
+
phase: "run",
|
|
1720
|
+
category: "infrastructure",
|
|
1721
|
+
retry: "retryable",
|
|
1722
|
+
reason: "pi runtime event transport failed"
|
|
1723
|
+
}, { cause });
|
|
1724
|
+
}
|
|
1725
|
+
const { value, done } = result;
|
|
1726
|
+
if (done) {
|
|
1727
|
+
throw new RuntimeExecutionFailure({
|
|
1728
|
+
phase: "run",
|
|
1729
|
+
category: "infrastructure",
|
|
1730
|
+
retry: "retryable",
|
|
1731
|
+
reason: "pi runtime process ended before agent_settled"
|
|
1732
|
+
}, { cause: rpc.terminalError });
|
|
1733
|
+
}
|
|
1327
1734
|
const mapped = mapPiMessageToAgentEvent(value);
|
|
1735
|
+
if (value.type === "auto_retry_end" && value.success === false) {
|
|
1736
|
+
terminalFailure = new RuntimeExecutionFailure({
|
|
1737
|
+
phase: "run",
|
|
1738
|
+
category: "semantic",
|
|
1739
|
+
retry: "non-retryable",
|
|
1740
|
+
reason: "pi exhausted its native retry policy"
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1328
1743
|
if (mapped) return { value: mapped, done: false };
|
|
1329
1744
|
if (!ROUTINE_PI_EVENT_TYPES.has(value.type)) {
|
|
1330
1745
|
rpc.recordUnmappedFrame(value.type);
|
|
@@ -1354,7 +1769,7 @@ var PiSession = class {
|
|
|
1354
1769
|
await this.rpc.send({ type: "abort" });
|
|
1355
1770
|
}
|
|
1356
1771
|
async close() {
|
|
1357
|
-
this.rpc.
|
|
1772
|
+
await this.rpc.dispose();
|
|
1358
1773
|
}
|
|
1359
1774
|
async resolveApproval() {
|
|
1360
1775
|
throw new Error("pi adapter does not support approval resume: pi never emits needs_approval in M0/M1");
|
|
@@ -1566,7 +1981,15 @@ function mapResult(msg) {
|
|
|
1566
1981
|
diagnostic ? `claude result frame had a missing/invalid is_error flag (got ${JSON.stringify(msg.is_error)}) \u2014 treating as failure, fail-closed; diagnostic content on the frame: ${truncateResultDiagnostic(diagnostic)}` : `claude result frame had a missing/invalid is_error flag (got ${JSON.stringify(msg.is_error)}) \u2014 treating as failure, fail-closed`
|
|
1567
1982
|
);
|
|
1568
1983
|
const events = usageEvent ? [usageEvent, { type: "error", message }] : [{ type: "error", message }];
|
|
1569
|
-
return {
|
|
1984
|
+
return {
|
|
1985
|
+
events,
|
|
1986
|
+
terminalFailure: new RuntimeExecutionFailure({
|
|
1987
|
+
phase: "run",
|
|
1988
|
+
category: msg.is_error === true ? "semantic" : "authority",
|
|
1989
|
+
retry: "non-retryable",
|
|
1990
|
+
reason: msg.is_error === true ? "claude reported terminal task failure" : "claude emitted a malformed terminal result frame"
|
|
1991
|
+
})
|
|
1992
|
+
};
|
|
1570
1993
|
}
|
|
1571
1994
|
var RESULT_DIAGNOSTIC_MAX_CHARS = 2e3;
|
|
1572
1995
|
function truncateResultDiagnostic(text) {
|
|
@@ -1618,16 +2041,22 @@ var ClaudeProcessClient = class {
|
|
|
1618
2041
|
eventQueue = new AsyncQueue();
|
|
1619
2042
|
closed = false;
|
|
1620
2043
|
exitError;
|
|
2044
|
+
closedPromise;
|
|
2045
|
+
resolveClosed;
|
|
2046
|
+
disposalAttempt;
|
|
1621
2047
|
stderrRing = [];
|
|
1622
2048
|
unmappedFrameCounts = /* @__PURE__ */ new Map();
|
|
1623
2049
|
sessionId;
|
|
1624
2050
|
initWaiter;
|
|
1625
2051
|
constructor(options) {
|
|
1626
2052
|
const spawnFn = options.spawnFn ?? spawn;
|
|
1627
|
-
this.child = spawnFn(options.command, options.args, {
|
|
2053
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
1628
2054
|
cwd: options.cwd,
|
|
1629
2055
|
env: options.env,
|
|
1630
2056
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2057
|
+
}));
|
|
2058
|
+
this.closedPromise = new Promise((resolve) => {
|
|
2059
|
+
this.resolveClosed = resolve;
|
|
1631
2060
|
});
|
|
1632
2061
|
this.child.stdout.setEncoding("utf8");
|
|
1633
2062
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -1683,6 +2112,10 @@ var ClaudeProcessClient = class {
|
|
|
1683
2112
|
get events() {
|
|
1684
2113
|
return this.eventQueue;
|
|
1685
2114
|
}
|
|
2115
|
+
/** Local transport diagnostic retained when the process closes; consumers classify it at the session boundary. */
|
|
2116
|
+
get terminalError() {
|
|
2117
|
+
return this.exitError;
|
|
2118
|
+
}
|
|
1686
2119
|
/**
|
|
1687
2120
|
* Record a claude stream-json frame/subtype/content-block label that
|
|
1688
2121
|
* `ClaudeSession`'s event iterator (`../claude-adapter.ts`) decided has
|
|
@@ -1702,15 +2135,30 @@ var ClaudeProcessClient = class {
|
|
|
1702
2135
|
);
|
|
1703
2136
|
}
|
|
1704
2137
|
}
|
|
1705
|
-
/**
|
|
2138
|
+
/** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
|
|
1706
2139
|
kill() {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2140
|
+
requestOwnedProcessTreeTermination(this.processTreeOptions());
|
|
2141
|
+
}
|
|
2142
|
+
waitClosed() {
|
|
2143
|
+
return this.closedPromise;
|
|
2144
|
+
}
|
|
2145
|
+
dispose() {
|
|
2146
|
+
if (!this.disposalAttempt) {
|
|
2147
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
2148
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
2149
|
+
this.disposalAttempt = void 0;
|
|
2150
|
+
throw error;
|
|
2151
|
+
});
|
|
1713
2152
|
}
|
|
2153
|
+
return this.disposalAttempt;
|
|
2154
|
+
}
|
|
2155
|
+
processTreeOptions() {
|
|
2156
|
+
return {
|
|
2157
|
+
child: this.child,
|
|
2158
|
+
waitClosed: () => this.closedPromise,
|
|
2159
|
+
isClosed: () => this.closed,
|
|
2160
|
+
label: "claude"
|
|
2161
|
+
};
|
|
1714
2162
|
}
|
|
1715
2163
|
onData(chunk) {
|
|
1716
2164
|
this.buffer += chunk;
|
|
@@ -1761,6 +2209,7 @@ var ClaudeProcessClient = class {
|
|
|
1761
2209
|
if (this.closed) return;
|
|
1762
2210
|
this.closed = true;
|
|
1763
2211
|
this.exitError = err;
|
|
2212
|
+
this.resolveClosed();
|
|
1764
2213
|
this.initWaiter?.reject(err);
|
|
1765
2214
|
this.initWaiter = void 0;
|
|
1766
2215
|
this.eventQueue.end();
|
|
@@ -1772,18 +2221,37 @@ var APPROVAL_TOOL_NAME = "approval_prompt";
|
|
|
1772
2221
|
var APPROVAL_MCP_SERVER_NAME = "byokapproval";
|
|
1773
2222
|
var execFileAsync2 = promisify(execFile);
|
|
1774
2223
|
var DETECT_TIMEOUT_MS2 = 5e3;
|
|
2224
|
+
function errorMessage2(err) {
|
|
2225
|
+
return err instanceof Error ? err.message : String(err);
|
|
2226
|
+
}
|
|
1775
2227
|
async function cleanupMcpConfigDir(dir) {
|
|
1776
2228
|
if (!dir) return;
|
|
1777
|
-
|
|
1778
|
-
|
|
2229
|
+
try {
|
|
2230
|
+
await promises.rm(dir, { recursive: true, force: true });
|
|
2231
|
+
} catch (cause) {
|
|
2232
|
+
throw new RuntimeDisposalFailure({
|
|
2233
|
+
stage: "cleanup",
|
|
2234
|
+
reason: "claude task-scoped MCP configuration could not be removed"
|
|
2235
|
+
}, { cause });
|
|
2236
|
+
}
|
|
1779
2237
|
}
|
|
1780
2238
|
var ClaudeAdapter = class {
|
|
1781
2239
|
constructor(options = {}) {
|
|
1782
2240
|
this.options = options;
|
|
1783
2241
|
}
|
|
1784
2242
|
options;
|
|
1785
|
-
|
|
1786
|
-
|
|
2243
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
2244
|
+
id: "claude",
|
|
2245
|
+
supportsDispatchSelection: true,
|
|
2246
|
+
capabilities: {
|
|
2247
|
+
steer: false,
|
|
2248
|
+
resume: true,
|
|
2249
|
+
approvalInteractive: true,
|
|
2250
|
+
mcpToolsets: true,
|
|
2251
|
+
permissionModes: ["auto", "readonly", "plan", "confirm"]
|
|
2252
|
+
},
|
|
2253
|
+
environmentRequirements: { credentialNames: [] }
|
|
2254
|
+
});
|
|
1787
2255
|
async detect() {
|
|
1788
2256
|
const bin = this.resolveBin();
|
|
1789
2257
|
try {
|
|
@@ -1795,54 +2263,73 @@ var ClaudeAdapter = class {
|
|
|
1795
2263
|
return { present: false };
|
|
1796
2264
|
}
|
|
1797
2265
|
}
|
|
1798
|
-
|
|
2266
|
+
async prepare(input) {
|
|
2267
|
+
const mapping = mapPermissionPolicyToClaudeArgs(input.policy);
|
|
2268
|
+
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by claude adapter", retryable: false };
|
|
2269
|
+
let modelId;
|
|
2270
|
+
try {
|
|
2271
|
+
modelId = subscriptionModel(input.offer.dispatchSelection, "claude");
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: false };
|
|
2274
|
+
}
|
|
2275
|
+
if (mapping.needsApprovalMcp && Object.prototype.hasOwnProperty.call(input.mcpServers ?? {}, APPROVAL_MCP_SERVER_NAME)) {
|
|
2276
|
+
return { kind: "reject", reason: `MCP server name "${APPROVAL_MCP_SERVER_NAME}" is reserved by the claude approval channel`, retryable: false };
|
|
2277
|
+
}
|
|
2278
|
+
let bin;
|
|
2279
|
+
try {
|
|
2280
|
+
bin = this.resolveBin();
|
|
2281
|
+
} catch (error) {
|
|
2282
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
2283
|
+
}
|
|
2284
|
+
let approvalMcpBin;
|
|
2285
|
+
if (mapping.needsApprovalMcp) {
|
|
2286
|
+
try {
|
|
2287
|
+
approvalMcpBin = (this.options.resolveApprovalMcpBin ?? resolveApprovalMcpBin)();
|
|
2288
|
+
} catch (error) {
|
|
2289
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
1799
2292
|
return {
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
permissionModes: ["auto", "readonly", "plan", "confirm"]
|
|
2293
|
+
kind: "prepared",
|
|
2294
|
+
operation: {
|
|
2295
|
+
start: (startInput) => this.startPrepared(startInput, mapping, modelId, bin, approvalMcpBin)
|
|
2296
|
+
}
|
|
1805
2297
|
};
|
|
1806
2298
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
return { credentialNames: [] };
|
|
1822
|
-
}
|
|
1823
|
-
async start(task, ctx) {
|
|
1824
|
-
if (typeof task.instruction !== "string") {
|
|
1825
|
-
throw new PolicyUnsupportedError("claude adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
1826
|
-
}
|
|
1827
|
-
const mapping = mapPermissionPolicyToClaudeArgs(ctx.policy);
|
|
1828
|
-
if (!mapping.ok) {
|
|
1829
|
-
throw new PolicyUnsupportedError(mapping.reason ?? "policy rejected by claude adapter");
|
|
2299
|
+
async startPrepared(startInput, initialMapping, modelId, bin, approvalMcpBin) {
|
|
2300
|
+
if (!initialMapping.ok) throw new RuntimeExecutionFailure({
|
|
2301
|
+
phase: "start",
|
|
2302
|
+
category: "authority",
|
|
2303
|
+
retry: "non-retryable",
|
|
2304
|
+
reason: "prepared claude permission mapping was invalid"
|
|
2305
|
+
});
|
|
2306
|
+
if (typeof startInput.instruction !== "string") {
|
|
2307
|
+
throw new RuntimeExecutionFailure({
|
|
2308
|
+
phase: "start",
|
|
2309
|
+
category: "authority",
|
|
2310
|
+
retry: "non-retryable",
|
|
2311
|
+
reason: "prepared claude operation requires a resolved string instruction"
|
|
2312
|
+
});
|
|
1830
2313
|
}
|
|
1831
|
-
const
|
|
2314
|
+
const mapping = { ...initialMapping, args: [...initialMapping.args] };
|
|
1832
2315
|
let mcpConfigDir;
|
|
1833
|
-
const taskMcpServers =
|
|
2316
|
+
const taskMcpServers = startInput.mcpServers ?? {};
|
|
1834
2317
|
const needsMcpConfig = mapping.needsApprovalMcp || Object.keys(taskMcpServers).length > 0;
|
|
1835
2318
|
if (mapping.needsApprovalMcp) {
|
|
1836
|
-
if (!
|
|
1837
|
-
throw new
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
`MCP server name "${APPROVAL_MCP_SERVER_NAME}" is reserved by the claude approval channel`
|
|
1844
|
-
);
|
|
2319
|
+
if (!startInput.approvalChannel) {
|
|
2320
|
+
throw new RuntimeExecutionFailure({
|
|
2321
|
+
phase: "start",
|
|
2322
|
+
category: "authority",
|
|
2323
|
+
retry: "non-retryable",
|
|
2324
|
+
reason: 'claude adapter requires policy.mode "confirm" to be started with an approval channel'
|
|
2325
|
+
});
|
|
1845
2326
|
}
|
|
2327
|
+
if (!approvalMcpBin) throw new RuntimeExecutionFailure({
|
|
2328
|
+
phase: "start",
|
|
2329
|
+
category: "authority",
|
|
2330
|
+
retry: "non-retryable",
|
|
2331
|
+
reason: "prepared claude approval MCP binary was not resolved"
|
|
2332
|
+
});
|
|
1846
2333
|
}
|
|
1847
2334
|
if (needsMcpConfig) {
|
|
1848
2335
|
mcpConfigDir = await promises.mkdtemp(path20.join(os.tmpdir(), "byok-mcp-"));
|
|
@@ -1851,12 +2338,23 @@ var ClaudeAdapter = class {
|
|
|
1851
2338
|
const mcpConfigPath = path20.join(mcpConfigDir, "mcp-config.json");
|
|
1852
2339
|
const mcpServers = { ...taskMcpServers };
|
|
1853
2340
|
if (mapping.needsApprovalMcp) {
|
|
1854
|
-
const approvalChannel =
|
|
1855
|
-
if (!approvalChannel) throw new
|
|
1856
|
-
|
|
2341
|
+
const approvalChannel = startInput.approvalChannel;
|
|
2342
|
+
if (!approvalChannel) throw new RuntimeExecutionFailure({
|
|
2343
|
+
phase: "start",
|
|
2344
|
+
category: "authority",
|
|
2345
|
+
retry: "non-retryable",
|
|
2346
|
+
reason: "prepared claude approval channel was not available"
|
|
2347
|
+
});
|
|
2348
|
+
const preparedApprovalMcpBin = approvalMcpBin;
|
|
2349
|
+
if (!preparedApprovalMcpBin) throw new RuntimeExecutionFailure({
|
|
2350
|
+
phase: "start",
|
|
2351
|
+
category: "authority",
|
|
2352
|
+
retry: "non-retryable",
|
|
2353
|
+
reason: "prepared claude approval MCP binary was not resolved"
|
|
2354
|
+
});
|
|
1857
2355
|
mcpServers[APPROVAL_MCP_SERVER_NAME] = {
|
|
1858
|
-
command:
|
|
1859
|
-
args:
|
|
2356
|
+
command: preparedApprovalMcpBin.command,
|
|
2357
|
+
args: preparedApprovalMcpBin.args,
|
|
1860
2358
|
env: {
|
|
1861
2359
|
BYOK_STORE_DIR: approvalChannel.storeDir,
|
|
1862
2360
|
BYOK_PRODUCT_ID: approvalChannel.productId,
|
|
@@ -1876,8 +2374,26 @@ var ClaudeAdapter = class {
|
|
|
1876
2374
|
"--strict-mcp-config"
|
|
1877
2375
|
];
|
|
1878
2376
|
}
|
|
1879
|
-
const
|
|
1880
|
-
|
|
2377
|
+
const resumeSessionId = startInput.manifest.sessionRef;
|
|
2378
|
+
let manifestModelId;
|
|
2379
|
+
try {
|
|
2380
|
+
manifestModelId = subscriptionModel(startInput.manifest.dispatchSelection, "claude");
|
|
2381
|
+
} catch (cause) {
|
|
2382
|
+
throw new RuntimeExecutionFailure({
|
|
2383
|
+
phase: "start",
|
|
2384
|
+
category: "authority",
|
|
2385
|
+
retry: "non-retryable",
|
|
2386
|
+
reason: "prepared claude operation received an invalid runtime selection manifest"
|
|
2387
|
+
}, { cause });
|
|
2388
|
+
}
|
|
2389
|
+
if (manifestModelId !== modelId) {
|
|
2390
|
+
throw new RuntimeExecutionFailure({
|
|
2391
|
+
phase: "start",
|
|
2392
|
+
category: "authority",
|
|
2393
|
+
retry: "non-retryable",
|
|
2394
|
+
reason: "prepared claude operation received a manifest with different runtime selection"
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
1881
2397
|
const args = [
|
|
1882
2398
|
"-p",
|
|
1883
2399
|
"--input-format",
|
|
@@ -1889,40 +2405,71 @@ var ClaudeAdapter = class {
|
|
|
1889
2405
|
// "Error: When using --print, --output-format=stream-json requires
|
|
1890
2406
|
// --verbose", before spawning any model call.
|
|
1891
2407
|
"--verbose",
|
|
1892
|
-
...
|
|
2408
|
+
...manifestModelId ? ["--model", manifestModelId] : [],
|
|
1893
2409
|
...resumeSessionId ? ["--resume", resumeSessionId] : [],
|
|
1894
2410
|
...mapping.args
|
|
1895
2411
|
];
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2412
|
+
let client;
|
|
2413
|
+
try {
|
|
2414
|
+
client = new ClaudeProcessClient({
|
|
2415
|
+
command: bin.command,
|
|
2416
|
+
args,
|
|
2417
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
2418
|
+
env: withoutProviderCredentials(startInput.env),
|
|
2419
|
+
spawnFn: this.options.spawnFn
|
|
2420
|
+
});
|
|
2421
|
+
} catch (cause) {
|
|
2422
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
2423
|
+
throw new RuntimeExecutionFailure({
|
|
2424
|
+
phase: "start",
|
|
2425
|
+
category: "infrastructure",
|
|
2426
|
+
retry: "retryable",
|
|
2427
|
+
reason: "claude runtime process could not be spawned"
|
|
2428
|
+
}, { cause });
|
|
2429
|
+
}
|
|
2430
|
+
try {
|
|
2431
|
+
client.writeUserMessage(startInput.instruction);
|
|
2432
|
+
} catch (cause) {
|
|
2433
|
+
client.kill();
|
|
2434
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
2435
|
+
throw new RuntimeExecutionFailure({
|
|
2436
|
+
phase: "start",
|
|
2437
|
+
category: "infrastructure",
|
|
2438
|
+
retry: "retryable",
|
|
2439
|
+
reason: "claude initial instruction transport failed"
|
|
2440
|
+
}, { cause });
|
|
2441
|
+
}
|
|
1904
2442
|
let sessionRef;
|
|
1905
2443
|
try {
|
|
1906
2444
|
sessionRef = await client.waitForInit();
|
|
1907
2445
|
} catch (err) {
|
|
1908
2446
|
client.kill();
|
|
1909
2447
|
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1910
|
-
throw err;
|
|
2448
|
+
if (isRuntimeExecutionFailure(err)) throw err;
|
|
2449
|
+
throw new RuntimeExecutionFailure({
|
|
2450
|
+
phase: "start",
|
|
2451
|
+
category: "infrastructure",
|
|
2452
|
+
retry: "retryable",
|
|
2453
|
+
reason: `claude exited before yielding an authoritative session id: ${errorMessage2(err)}`
|
|
2454
|
+
}, { cause: err });
|
|
1911
2455
|
}
|
|
1912
2456
|
if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
|
|
1913
2457
|
client.kill();
|
|
1914
2458
|
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1915
|
-
throw new
|
|
1916
|
-
|
|
1917
|
-
|
|
2459
|
+
throw new RuntimeExecutionFailure({
|
|
2460
|
+
phase: "start",
|
|
2461
|
+
category: "authority",
|
|
2462
|
+
retry: "non-retryable",
|
|
2463
|
+
reason: `claude --resume echoed a different session id than requested (requested ${resumeSessionId}, got ${sessionRef})`
|
|
2464
|
+
});
|
|
1918
2465
|
}
|
|
1919
2466
|
return new ClaudeSession(
|
|
1920
2467
|
sessionRef,
|
|
1921
2468
|
client,
|
|
1922
|
-
|
|
1923
|
-
|
|
2469
|
+
startInput.manifest.workspace.workspaceDir,
|
|
2470
|
+
startInput.approvalChannel,
|
|
1924
2471
|
mcpConfigDir,
|
|
1925
|
-
|
|
2472
|
+
manifestModelId
|
|
1926
2473
|
);
|
|
1927
2474
|
}
|
|
1928
2475
|
/**
|
|
@@ -1957,8 +2504,7 @@ var ClaudeAdapter = class {
|
|
|
1957
2504
|
return (this.options.resolveBin ?? resolveClaudeBin)();
|
|
1958
2505
|
}
|
|
1959
2506
|
};
|
|
1960
|
-
function subscriptionModel(
|
|
1961
|
-
const selection = task.dispatchSelection;
|
|
2507
|
+
function subscriptionModel(selection, runtimeId) {
|
|
1962
2508
|
if (selection === void 0) return void 0;
|
|
1963
2509
|
if (selection.lane !== "subscription" || selection.runtimeId !== runtimeId) {
|
|
1964
2510
|
throw new PolicyUnsupportedError(
|
|
@@ -1983,6 +2529,7 @@ var ClaudeSession = class {
|
|
|
1983
2529
|
mcpConfigDir;
|
|
1984
2530
|
modelId;
|
|
1985
2531
|
correlation = createToolUseCorrelation();
|
|
2532
|
+
closeAttempt;
|
|
1986
2533
|
get events() {
|
|
1987
2534
|
const client = this.client;
|
|
1988
2535
|
const correlation = this.correlation;
|
|
@@ -1991,17 +2538,40 @@ var ClaudeSession = class {
|
|
|
1991
2538
|
[Symbol.asyncIterator]() {
|
|
1992
2539
|
const inner = client.events[Symbol.asyncIterator]();
|
|
1993
2540
|
let pending = [];
|
|
2541
|
+
let terminalFailure;
|
|
1994
2542
|
let turnSettled = false;
|
|
1995
2543
|
return {
|
|
1996
2544
|
async next() {
|
|
1997
2545
|
for (; ; ) {
|
|
1998
2546
|
const buffered = pending.shift();
|
|
1999
2547
|
if (buffered) return { value: buffered, done: false };
|
|
2000
|
-
if (turnSettled)
|
|
2001
|
-
|
|
2002
|
-
|
|
2548
|
+
if (turnSettled) {
|
|
2549
|
+
if (terminalFailure) throw terminalFailure;
|
|
2550
|
+
return { value: void 0, done: true };
|
|
2551
|
+
}
|
|
2552
|
+
let raw;
|
|
2553
|
+
try {
|
|
2554
|
+
raw = await inner.next();
|
|
2555
|
+
} catch (cause) {
|
|
2556
|
+
throw new RuntimeExecutionFailure({
|
|
2557
|
+
phase: "run",
|
|
2558
|
+
category: "infrastructure",
|
|
2559
|
+
retry: "retryable",
|
|
2560
|
+
reason: "claude runtime event transport failed"
|
|
2561
|
+
}, { cause });
|
|
2562
|
+
}
|
|
2563
|
+
const { value, done } = raw;
|
|
2564
|
+
if (done) {
|
|
2565
|
+
throw new RuntimeExecutionFailure({
|
|
2566
|
+
phase: "run",
|
|
2567
|
+
category: "infrastructure",
|
|
2568
|
+
retry: "retryable",
|
|
2569
|
+
reason: "claude runtime process ended before a terminal result frame"
|
|
2570
|
+
}, { cause: client.terminalError });
|
|
2571
|
+
}
|
|
2003
2572
|
if (value.type === "result") turnSettled = true;
|
|
2004
2573
|
const mapped = mapClaudeMessageToAgentEvents(value, correlation, { workspaceDir });
|
|
2574
|
+
terminalFailure = mapped.terminalFailure ?? terminalFailure;
|
|
2005
2575
|
if (mapped.unmappedLabel) {
|
|
2006
2576
|
client.recordUnmappedFrame(mapped.unmappedLabel);
|
|
2007
2577
|
}
|
|
@@ -2032,7 +2602,7 @@ var ClaudeSession = class {
|
|
|
2032
2602
|
if (typeof task.instruction !== "string") {
|
|
2033
2603
|
throw new PolicyUnsupportedError("claude adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
2034
2604
|
}
|
|
2035
|
-
const requestedModel = subscriptionModel(task, "claude");
|
|
2605
|
+
const requestedModel = subscriptionModel(task.dispatchSelection, "claude");
|
|
2036
2606
|
if (requestedModel !== void 0 && requestedModel !== this.modelId) {
|
|
2037
2607
|
throw new PolicyUnsupportedError(
|
|
2038
2608
|
`claude persistent session cannot change model from ${this.modelId ?? "(legacy default)"} to ${requestedModel}`
|
|
@@ -2056,8 +2626,17 @@ var ClaudeSession = class {
|
|
|
2056
2626
|
this.client.kill();
|
|
2057
2627
|
}
|
|
2058
2628
|
async close() {
|
|
2059
|
-
this.
|
|
2060
|
-
|
|
2629
|
+
if (!this.closeAttempt) {
|
|
2630
|
+
const attempt = (async () => {
|
|
2631
|
+
await this.client.dispose();
|
|
2632
|
+
await cleanupMcpConfigDir(this.mcpConfigDir);
|
|
2633
|
+
})();
|
|
2634
|
+
this.closeAttempt = attempt.catch((error) => {
|
|
2635
|
+
this.closeAttempt = void 0;
|
|
2636
|
+
throw error;
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
await this.closeAttempt;
|
|
2061
2640
|
}
|
|
2062
2641
|
/**
|
|
2063
2642
|
* M4 Phase 3: routes into the out-of-band approval channel `start()`
|
|
@@ -2274,14 +2853,15 @@ var CodexProcessRunner = class {
|
|
|
2274
2853
|
exitSignal = null;
|
|
2275
2854
|
closedPromise;
|
|
2276
2855
|
resolveClosed;
|
|
2856
|
+
disposalAttempt;
|
|
2277
2857
|
constructor(options) {
|
|
2278
2858
|
this.onEvent = options.onEvent;
|
|
2279
2859
|
const spawnFn = options.spawnFn ?? spawn;
|
|
2280
|
-
this.child = spawnFn(options.command, options.args, {
|
|
2860
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
2281
2861
|
cwd: options.cwd,
|
|
2282
2862
|
env: options.env,
|
|
2283
2863
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2284
|
-
});
|
|
2864
|
+
}));
|
|
2285
2865
|
this.closedPromise = new Promise((resolve) => {
|
|
2286
2866
|
this.resolveClosed = resolve;
|
|
2287
2867
|
});
|
|
@@ -2311,7 +2891,7 @@ var CodexProcessRunner = class {
|
|
|
2311
2891
|
return this.closed;
|
|
2312
2892
|
}
|
|
2313
2893
|
/**
|
|
2314
|
-
*
|
|
2894
|
+
* Immediate tree termination request. SIGTERM on POSIX: SIGINT was empirically confirmed
|
|
2315
2895
|
* to be silently ignored by `codex exec` (a real, direct test — a 60s
|
|
2316
2896
|
* shell `sleep` ran to full, unaffected completion despite SIGINT sent at
|
|
2317
2897
|
* t=4s) — a genuine, evidence-based correction to this task's own initial
|
|
@@ -2324,13 +2904,25 @@ var CodexProcessRunner = class {
|
|
|
2324
2904
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
2325
2905
|
*/
|
|
2326
2906
|
kill() {
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
this.
|
|
2907
|
+
requestOwnedProcessTreeTermination(this.processTreeOptions());
|
|
2908
|
+
}
|
|
2909
|
+
dispose() {
|
|
2910
|
+
if (!this.disposalAttempt) {
|
|
2911
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
2912
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
2913
|
+
this.disposalAttempt = void 0;
|
|
2914
|
+
throw error;
|
|
2915
|
+
});
|
|
2333
2916
|
}
|
|
2917
|
+
return this.disposalAttempt;
|
|
2918
|
+
}
|
|
2919
|
+
processTreeOptions() {
|
|
2920
|
+
return {
|
|
2921
|
+
child: this.child,
|
|
2922
|
+
waitClosed: () => this.closedPromise,
|
|
2923
|
+
isClosed: () => this.closed,
|
|
2924
|
+
label: "codex"
|
|
2925
|
+
};
|
|
2334
2926
|
}
|
|
2335
2927
|
/** Builds a descriptive error folding in the exit code/signal and the stderr tail — mirrors `PiRpcClient.buildExitError`'s reasoning: a post-mortem on a failed start/resume should never need separately re-running codex by hand with a raw JSONL logger to learn why. */
|
|
2336
2928
|
buildExitError(context) {
|
|
@@ -2381,8 +2973,17 @@ var CodexAdapter = class {
|
|
|
2381
2973
|
this.options = options;
|
|
2382
2974
|
}
|
|
2383
2975
|
options;
|
|
2384
|
-
|
|
2385
|
-
|
|
2976
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
2977
|
+
id: "codex",
|
|
2978
|
+
supportsDispatchSelection: true,
|
|
2979
|
+
capabilities: {
|
|
2980
|
+
steer: false,
|
|
2981
|
+
resume: true,
|
|
2982
|
+
approvalInteractive: false,
|
|
2983
|
+
permissionModes: ["auto", "readonly"]
|
|
2984
|
+
},
|
|
2985
|
+
environmentRequirements: { credentialNames: [] }
|
|
2986
|
+
});
|
|
2386
2987
|
async detect() {
|
|
2387
2988
|
const bin = this.resolveBin();
|
|
2388
2989
|
try {
|
|
@@ -2431,61 +3032,100 @@ ${result.stderr}`);
|
|
|
2431
3032
|
${withStreams.stderr ?? ""}`);
|
|
2432
3033
|
}
|
|
2433
3034
|
}
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
* API-key passthrough remains a separate, pending product decision. No
|
|
2443
|
-
* `baseNames` either: nothing in this adapter reads a codex-specific
|
|
2444
|
-
* config-discovery variable (e.g. `CODEX_HOME`) today.
|
|
2445
|
-
*/
|
|
2446
|
-
environmentRequirements() {
|
|
2447
|
-
return { credentialNames: [] };
|
|
2448
|
-
}
|
|
2449
|
-
async start(task, ctx) {
|
|
2450
|
-
if (typeof task.instruction !== "string") {
|
|
2451
|
-
throw new PolicyUnsupportedError("codex adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
3035
|
+
async prepare(input) {
|
|
3036
|
+
const mapping = mapPermissionPolicyToCodexArgs(input.policy);
|
|
3037
|
+
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by codex adapter", retryable: false };
|
|
3038
|
+
let modelId;
|
|
3039
|
+
try {
|
|
3040
|
+
modelId = subscriptionModel2(input.offer.dispatchSelection);
|
|
3041
|
+
} catch (error) {
|
|
3042
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: false };
|
|
2452
3043
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
3044
|
+
let command;
|
|
3045
|
+
try {
|
|
3046
|
+
command = this.resolveBin().command;
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
3049
|
+
}
|
|
3050
|
+
return {
|
|
3051
|
+
kind: "prepared",
|
|
3052
|
+
operation: {
|
|
3053
|
+
start: (startInput) => this.startPrepared(startInput, mapping.args, modelId, command)
|
|
3054
|
+
}
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
async startPrepared(startInput, policyArgs, modelId, command) {
|
|
3058
|
+
if (typeof startInput.instruction !== "string") {
|
|
3059
|
+
throw new RuntimeExecutionFailure({
|
|
3060
|
+
phase: "start",
|
|
3061
|
+
category: "authority",
|
|
3062
|
+
retry: "non-retryable",
|
|
3063
|
+
reason: "prepared codex operation requires a resolved string instruction"
|
|
3064
|
+
});
|
|
2456
3065
|
}
|
|
2457
|
-
const modelId = subscriptionModel2(task);
|
|
2458
|
-
const bin = this.resolveBin();
|
|
2459
3066
|
const queue = new AsyncQueue();
|
|
3067
|
+
const terminal = {};
|
|
2460
3068
|
const recordUnmapped = makeUnmappedFrameRecorder(/* @__PURE__ */ new Map());
|
|
2461
|
-
|
|
2462
|
-
|
|
3069
|
+
let workspaceDir;
|
|
3070
|
+
try {
|
|
3071
|
+
workspaceDir = await resolveRealWorkspaceDir(startInput.manifest.workspace.workspaceDir);
|
|
3072
|
+
} catch (cause) {
|
|
3073
|
+
throw new RuntimeExecutionFailure({
|
|
3074
|
+
phase: "start",
|
|
3075
|
+
category: "infrastructure",
|
|
3076
|
+
retry: "retryable",
|
|
3077
|
+
reason: "codex runtime workspace could not be resolved"
|
|
3078
|
+
}, { cause });
|
|
3079
|
+
}
|
|
3080
|
+
const runtimeEnv = withoutProviderCredentials(startInput.env);
|
|
3081
|
+
let manifestModelId;
|
|
3082
|
+
try {
|
|
3083
|
+
manifestModelId = subscriptionModel2(startInput.manifest.dispatchSelection);
|
|
3084
|
+
} catch (cause) {
|
|
3085
|
+
throw new RuntimeExecutionFailure({
|
|
3086
|
+
phase: "start",
|
|
3087
|
+
category: "authority",
|
|
3088
|
+
retry: "non-retryable",
|
|
3089
|
+
reason: "prepared codex operation received an invalid runtime selection manifest"
|
|
3090
|
+
}, { cause });
|
|
3091
|
+
}
|
|
3092
|
+
if (manifestModelId !== modelId) {
|
|
3093
|
+
throw new RuntimeExecutionFailure({
|
|
3094
|
+
phase: "start",
|
|
3095
|
+
category: "authority",
|
|
3096
|
+
retry: "non-retryable",
|
|
3097
|
+
reason: "prepared codex operation received a manifest with different runtime selection"
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
2463
3100
|
const { sessionRef, runner } = await runCodexTurn({
|
|
2464
|
-
command
|
|
2465
|
-
resumeRef:
|
|
2466
|
-
instruction:
|
|
2467
|
-
modelId,
|
|
2468
|
-
policyArgs:
|
|
2469
|
-
cwd:
|
|
3101
|
+
command,
|
|
3102
|
+
resumeRef: startInput.manifest.sessionRef,
|
|
3103
|
+
instruction: startInput.instruction,
|
|
3104
|
+
modelId: manifestModelId,
|
|
3105
|
+
policyArgs: [...policyArgs],
|
|
3106
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
2470
3107
|
env: runtimeEnv,
|
|
2471
3108
|
spawnFn: this.options.spawnFn,
|
|
2472
3109
|
workspaceDir,
|
|
2473
3110
|
queue,
|
|
2474
3111
|
recordUnmapped,
|
|
2475
|
-
expectedSessionRef:
|
|
2476
|
-
preparedGit:
|
|
3112
|
+
expectedSessionRef: startInput.manifest.sessionRef,
|
|
3113
|
+
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
3114
|
+
failurePhase: "start",
|
|
3115
|
+
terminal
|
|
2477
3116
|
});
|
|
2478
3117
|
return new CodexSession({
|
|
2479
3118
|
sessionRef,
|
|
2480
|
-
command
|
|
3119
|
+
command,
|
|
2481
3120
|
workspaceDir,
|
|
2482
|
-
env:
|
|
3121
|
+
env: startInput.env,
|
|
2483
3122
|
spawnFn: this.options.spawnFn,
|
|
2484
3123
|
queue,
|
|
2485
3124
|
recordUnmapped,
|
|
2486
3125
|
initialRunner: runner,
|
|
2487
|
-
preparedGit:
|
|
2488
|
-
modelId
|
|
3126
|
+
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
3127
|
+
modelId: manifestModelId,
|
|
3128
|
+
terminal
|
|
2489
3129
|
});
|
|
2490
3130
|
}
|
|
2491
3131
|
resolveBin() {
|
|
@@ -2533,37 +3173,69 @@ async function runCodexTurn(params) {
|
|
|
2533
3173
|
rejectFirstLine = reject;
|
|
2534
3174
|
});
|
|
2535
3175
|
let turnEnded = false;
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
if (
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
3176
|
+
let runner;
|
|
3177
|
+
try {
|
|
3178
|
+
runner = new CodexProcessRunner({
|
|
3179
|
+
command: params.command,
|
|
3180
|
+
args: argv,
|
|
3181
|
+
cwd: params.cwd,
|
|
3182
|
+
env: params.env,
|
|
3183
|
+
spawnFn: params.spawnFn,
|
|
3184
|
+
onEvent: (evt) => {
|
|
3185
|
+
if (!firstLineSettled) {
|
|
3186
|
+
firstLineSettled = true;
|
|
3187
|
+
if (evt.type === "thread.started" && typeof evt.thread_id === "string" && evt.thread_id.length > 0) {
|
|
3188
|
+
resolveFirstLine(evt.thread_id);
|
|
3189
|
+
} else {
|
|
3190
|
+
rejectFirstLine(
|
|
3191
|
+
new RuntimeExecutionFailure({
|
|
3192
|
+
phase: params.failurePhase,
|
|
3193
|
+
category: "authority",
|
|
3194
|
+
retry: "non-retryable",
|
|
3195
|
+
reason: `codex did not yield thread.started as its first event (got ${JSON.stringify(evt).slice(0, 200)})`
|
|
3196
|
+
})
|
|
3197
|
+
);
|
|
3198
|
+
}
|
|
3199
|
+
return;
|
|
3200
|
+
}
|
|
3201
|
+
const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
3202
|
+
for (const agentEvent of mapped) {
|
|
3203
|
+
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
3204
|
+
params.queue.push(agentEvent);
|
|
3205
|
+
}
|
|
3206
|
+
if (evt.type === "turn.failed") {
|
|
3207
|
+
params.terminal.failure = new RuntimeExecutionFailure({
|
|
3208
|
+
phase: "run",
|
|
3209
|
+
category: "semantic",
|
|
3210
|
+
retry: "non-retryable",
|
|
3211
|
+
reason: "codex reported terminal task failure"
|
|
3212
|
+
});
|
|
3213
|
+
params.queue.end();
|
|
3214
|
+
}
|
|
3215
|
+
if (mapped.length === 0 && !isRoutineCodexEvent(evt)) {
|
|
3216
|
+
params.recordUnmapped(unmappedFrameKey(evt));
|
|
2551
3217
|
}
|
|
2552
|
-
return;
|
|
2553
|
-
}
|
|
2554
|
-
const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
2555
|
-
for (const agentEvent of mapped) {
|
|
2556
|
-
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
2557
|
-
params.queue.push(agentEvent);
|
|
2558
|
-
}
|
|
2559
|
-
if (mapped.length === 0 && !isRoutineCodexEvent(evt)) {
|
|
2560
|
-
params.recordUnmapped(unmappedFrameKey(evt));
|
|
2561
3218
|
}
|
|
2562
|
-
}
|
|
2563
|
-
})
|
|
3219
|
+
});
|
|
3220
|
+
} catch (cause) {
|
|
3221
|
+
throw new RuntimeExecutionFailure({
|
|
3222
|
+
phase: params.failurePhase,
|
|
3223
|
+
category: "infrastructure",
|
|
3224
|
+
retry: "retryable",
|
|
3225
|
+
reason: "codex runtime process could not be spawned"
|
|
3226
|
+
}, { cause });
|
|
3227
|
+
}
|
|
3228
|
+
params.onRunnerCreated?.(runner);
|
|
2564
3229
|
void runner.waitClosed().then(() => {
|
|
2565
|
-
if (turnEnded) return;
|
|
2566
|
-
|
|
3230
|
+
if (turnEnded || params.terminal.failure) return;
|
|
3231
|
+
const cause = runner.buildExitError("codex exited without completing the turn");
|
|
3232
|
+
params.terminal.failure = new RuntimeExecutionFailure({
|
|
3233
|
+
phase: "run",
|
|
3234
|
+
category: "infrastructure",
|
|
3235
|
+
retry: "retryable",
|
|
3236
|
+
reason: cause.message
|
|
3237
|
+
}, { cause });
|
|
3238
|
+
params.queue.push({ type: "error", message: cause.message });
|
|
2567
3239
|
params.queue.end();
|
|
2568
3240
|
});
|
|
2569
3241
|
let sessionRef;
|
|
@@ -2587,7 +3259,13 @@ async function runCodexTurn(params) {
|
|
|
2587
3259
|
void runner.waitClosed().then(() => {
|
|
2588
3260
|
if (!settled) {
|
|
2589
3261
|
settled = true;
|
|
2590
|
-
|
|
3262
|
+
const cause = runner.buildExitError("codex exited before yielding an authoritative thread id");
|
|
3263
|
+
reject(new RuntimeExecutionFailure({
|
|
3264
|
+
phase: params.failurePhase,
|
|
3265
|
+
category: "infrastructure",
|
|
3266
|
+
retry: "retryable",
|
|
3267
|
+
reason: cause.message
|
|
3268
|
+
}, { cause }));
|
|
2591
3269
|
}
|
|
2592
3270
|
});
|
|
2593
3271
|
});
|
|
@@ -2597,9 +3275,12 @@ async function runCodexTurn(params) {
|
|
|
2597
3275
|
}
|
|
2598
3276
|
if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
|
|
2599
3277
|
runner.kill();
|
|
2600
|
-
throw new
|
|
2601
|
-
|
|
2602
|
-
|
|
3278
|
+
throw new RuntimeExecutionFailure({
|
|
3279
|
+
phase: params.failurePhase,
|
|
3280
|
+
category: "authority",
|
|
3281
|
+
retry: "non-retryable",
|
|
3282
|
+
reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
|
|
3283
|
+
});
|
|
2603
3284
|
}
|
|
2604
3285
|
return { sessionRef, runner };
|
|
2605
3286
|
}
|
|
@@ -2626,8 +3307,12 @@ var CodexSession = class {
|
|
|
2626
3307
|
recordUnmapped;
|
|
2627
3308
|
preparedGit;
|
|
2628
3309
|
modelId;
|
|
3310
|
+
terminal;
|
|
2629
3311
|
currentRunner;
|
|
3312
|
+
ownedRunners = /* @__PURE__ */ new Set();
|
|
3313
|
+
followUpAttempts = /* @__PURE__ */ new Set();
|
|
2630
3314
|
closed = false;
|
|
3315
|
+
closeAttempt;
|
|
2631
3316
|
constructor(options) {
|
|
2632
3317
|
this.sessionRef = options.sessionRef;
|
|
2633
3318
|
this.command = options.command;
|
|
@@ -2638,11 +3323,36 @@ var CodexSession = class {
|
|
|
2638
3323
|
this.recordUnmapped = options.recordUnmapped;
|
|
2639
3324
|
this.preparedGit = options.preparedGit;
|
|
2640
3325
|
this.modelId = options.modelId;
|
|
3326
|
+
this.terminal = options.terminal;
|
|
2641
3327
|
this.currentRunner = options.initialRunner;
|
|
3328
|
+
this.ownedRunners.add(options.initialRunner);
|
|
2642
3329
|
void this.forgetRunnerOnceClosed(options.initialRunner);
|
|
2643
3330
|
}
|
|
2644
3331
|
get events() {
|
|
2645
|
-
|
|
3332
|
+
const queue = this.queue;
|
|
3333
|
+
const session = this;
|
|
3334
|
+
return {
|
|
3335
|
+
[Symbol.asyncIterator]() {
|
|
3336
|
+
const inner = queue[Symbol.asyncIterator]();
|
|
3337
|
+
return {
|
|
3338
|
+
async next() {
|
|
3339
|
+
let result;
|
|
3340
|
+
try {
|
|
3341
|
+
result = await inner.next();
|
|
3342
|
+
} catch (cause) {
|
|
3343
|
+
throw new RuntimeExecutionFailure({
|
|
3344
|
+
phase: "run",
|
|
3345
|
+
category: "infrastructure",
|
|
3346
|
+
retry: "retryable",
|
|
3347
|
+
reason: "codex runtime event transport failed"
|
|
3348
|
+
}, { cause });
|
|
3349
|
+
}
|
|
3350
|
+
if (result.done && session.terminal.failure) throw session.terminal.failure;
|
|
3351
|
+
return result;
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
2646
3356
|
}
|
|
2647
3357
|
async forgetRunnerOnceClosed(runner) {
|
|
2648
3358
|
await runner.waitClosed();
|
|
@@ -2687,7 +3397,14 @@ var CodexSession = class {
|
|
|
2687
3397
|
* stale id even after codex had moved on) — it just can now only ever be
|
|
2688
3398
|
* the SAME id this call asked to resume, never a silently-different one.
|
|
2689
3399
|
*/
|
|
2690
|
-
|
|
3400
|
+
followUp(task) {
|
|
3401
|
+
const attempt = this.runFollowUp(task);
|
|
3402
|
+
this.followUpAttempts.add(attempt);
|
|
3403
|
+
void attempt.finally(() => this.followUpAttempts.delete(attempt)).catch(() => {
|
|
3404
|
+
});
|
|
3405
|
+
return attempt;
|
|
3406
|
+
}
|
|
3407
|
+
async runFollowUp(task) {
|
|
2691
3408
|
if (typeof task.instruction !== "string") {
|
|
2692
3409
|
throw new PolicyUnsupportedError("codex adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
2693
3410
|
}
|
|
@@ -2698,7 +3415,7 @@ var CodexSession = class {
|
|
|
2698
3415
|
if (!mapping.ok) {
|
|
2699
3416
|
throw new PolicyUnsupportedError(mapping.reason ?? "policy rejected by codex adapter");
|
|
2700
3417
|
}
|
|
2701
|
-
const requestedModel = subscriptionModel2(task);
|
|
3418
|
+
const requestedModel = subscriptionModel2(task.dispatchSelection);
|
|
2702
3419
|
if (requestedModel !== void 0 && requestedModel !== this.modelId) {
|
|
2703
3420
|
throw new PolicyUnsupportedError(
|
|
2704
3421
|
`codex persistent session cannot change model from ${this.modelId ?? "(legacy default)"} to ${requestedModel}`
|
|
@@ -2708,6 +3425,7 @@ var CodexSession = class {
|
|
|
2708
3425
|
const resumeRef = this.sessionRef;
|
|
2709
3426
|
let sessionRef;
|
|
2710
3427
|
let runner;
|
|
3428
|
+
const terminal = {};
|
|
2711
3429
|
try {
|
|
2712
3430
|
({ sessionRef, runner } = await runCodexTurn({
|
|
2713
3431
|
command: this.command,
|
|
@@ -2722,25 +3440,54 @@ var CodexSession = class {
|
|
|
2722
3440
|
queue: this.queue,
|
|
2723
3441
|
recordUnmapped: this.recordUnmapped,
|
|
2724
3442
|
expectedSessionRef: resumeRef,
|
|
2725
|
-
preparedGit: this.preparedGit
|
|
3443
|
+
preparedGit: this.preparedGit,
|
|
3444
|
+
failurePhase: "run",
|
|
3445
|
+
terminal,
|
|
3446
|
+
onRunnerCreated: (created) => {
|
|
3447
|
+
this.ownedRunners.add(created);
|
|
3448
|
+
void this.forgetRunnerOnceClosed(created);
|
|
3449
|
+
}
|
|
2726
3450
|
}));
|
|
2727
3451
|
} catch (err) {
|
|
2728
3452
|
this.queue.end();
|
|
3453
|
+
this.terminal = {
|
|
3454
|
+
failure: isRuntimeExecutionFailure(err) ? err : new RuntimeExecutionFailure({
|
|
3455
|
+
phase: "run",
|
|
3456
|
+
category: "authority",
|
|
3457
|
+
retry: "non-retryable",
|
|
3458
|
+
reason: "codex follow-up violated the runtime adapter contract"
|
|
3459
|
+
}, { cause: err })
|
|
3460
|
+
};
|
|
2729
3461
|
throw err;
|
|
2730
3462
|
}
|
|
3463
|
+
if (this.closed) {
|
|
3464
|
+
await runner.dispose();
|
|
3465
|
+
throw new Error("codex session closed while follow-up was starting");
|
|
3466
|
+
}
|
|
3467
|
+
this.terminal = terminal;
|
|
2731
3468
|
this.sessionRef = sessionRef;
|
|
2732
3469
|
this.currentRunner = runner;
|
|
2733
|
-
void this.forgetRunnerOnceClosed(runner);
|
|
2734
3470
|
}
|
|
2735
3471
|
/** Best-effort abort of the current turn. SIGTERM's the currently-running child, if any — see `process-runner.ts`'s `kill()` doc comment for why SIGTERM (not SIGINT) and why this is safe: the underlying codex thread survives and stays resumable, confirmed empirically. A no-op when no turn is currently in flight. */
|
|
2736
3472
|
async interrupt() {
|
|
2737
3473
|
this.currentRunner?.kill();
|
|
2738
3474
|
}
|
|
2739
3475
|
async close() {
|
|
2740
|
-
if (this.
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
3476
|
+
if (!this.closeAttempt) {
|
|
3477
|
+
this.closed = true;
|
|
3478
|
+
this.queue.end();
|
|
3479
|
+
const attempt = (async () => {
|
|
3480
|
+
const runners = [...this.ownedRunners];
|
|
3481
|
+
await Promise.all(runners.map((runner) => runner.dispose()));
|
|
3482
|
+
for (const runner of runners) this.ownedRunners.delete(runner);
|
|
3483
|
+
await Promise.allSettled([...this.followUpAttempts]);
|
|
3484
|
+
})();
|
|
3485
|
+
this.closeAttempt = attempt.catch((error) => {
|
|
3486
|
+
this.closeAttempt = void 0;
|
|
3487
|
+
throw error;
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
await this.closeAttempt;
|
|
2744
3491
|
}
|
|
2745
3492
|
/**
|
|
2746
3493
|
* `codex exec` has no in-band channel to inject text into an already-
|
|
@@ -2776,8 +3523,7 @@ var CodexSession = class {
|
|
|
2776
3523
|
);
|
|
2777
3524
|
}
|
|
2778
3525
|
};
|
|
2779
|
-
function subscriptionModel2(
|
|
2780
|
-
const selection = task.dispatchSelection;
|
|
3526
|
+
function subscriptionModel2(selection) {
|
|
2781
3527
|
if (selection === void 0) return void 0;
|
|
2782
3528
|
if (selection.lane !== "subscription" || selection.runtimeId !== "codex") {
|
|
2783
3529
|
throw new PolicyUnsupportedError(
|
|
@@ -2842,7 +3588,7 @@ var ApprovalRegistry = class {
|
|
|
2842
3588
|
* `requestApproval` timeout and `finish()` fail-closed cleanup
|
|
2843
3589
|
* (`task-runner.ts`) — resolves a decision this device made on its own.
|
|
2844
3590
|
* The one exception, a server-sent wire `task.approve`/`task.reject`
|
|
2845
|
-
* relayed through `
|
|
3591
|
+
* relayed through `RuntimeOperationStartInput.approvalChannel.resolve`
|
|
2846
3592
|
* (`task-runner.ts`'s `handleOffer`), passes `'wire'` explicitly.
|
|
2847
3593
|
*/
|
|
2848
3594
|
resolve(approvalId, decision, reason, origin = "local") {
|
|
@@ -3473,7 +4219,10 @@ var PresencePublisher = class {
|
|
|
3473
4219
|
{
|
|
3474
4220
|
method: "PUT",
|
|
3475
4221
|
headers: { "content-type": "application/json" },
|
|
3476
|
-
body: JSON.stringify({
|
|
4222
|
+
body: JSON.stringify({
|
|
4223
|
+
level: "online",
|
|
4224
|
+
...this.opts.configuredToolsets === void 0 ? {} : { configuredToolsets: this.opts.configuredToolsets }
|
|
4225
|
+
})
|
|
3477
4226
|
},
|
|
3478
4227
|
this.opts.auth
|
|
3479
4228
|
);
|
|
@@ -3667,12 +4416,12 @@ var AnotherControlServerRunningError = class extends Error {
|
|
|
3667
4416
|
}
|
|
3668
4417
|
};
|
|
3669
4418
|
var MAX_HALF_OPEN_CONNECTIONS = 8;
|
|
3670
|
-
function
|
|
4419
|
+
function errorMessage3(err) {
|
|
3671
4420
|
return err instanceof Error ? err.message : String(err);
|
|
3672
4421
|
}
|
|
3673
4422
|
function toControlErrorShape(err) {
|
|
3674
4423
|
if (err instanceof ControlError) return { code: err.code, message: err.message };
|
|
3675
|
-
return { code: "internal_error", message:
|
|
4424
|
+
return { code: "internal_error", message: errorMessage3(err) };
|
|
3676
4425
|
}
|
|
3677
4426
|
function probeUnixSocketAlive(socketPath) {
|
|
3678
4427
|
return new Promise((resolve) => {
|
|
@@ -3879,12 +4628,19 @@ async function startControlServer(opts) {
|
|
|
3879
4628
|
}
|
|
3880
4629
|
throw err;
|
|
3881
4630
|
}
|
|
4631
|
+
let stopServingPromise;
|
|
4632
|
+
async function stopServing() {
|
|
4633
|
+
stopServingPromise ??= (async () => {
|
|
4634
|
+
for (const socket of sockets) socket.destroy();
|
|
4635
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
4636
|
+
})();
|
|
4637
|
+
await stopServingPromise;
|
|
4638
|
+
}
|
|
3882
4639
|
let closed = false;
|
|
3883
4640
|
async function close() {
|
|
3884
4641
|
if (closed) return;
|
|
3885
4642
|
closed = true;
|
|
3886
|
-
|
|
3887
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
4643
|
+
await stopServing();
|
|
3888
4644
|
if (process.platform !== "win32") {
|
|
3889
4645
|
await promises.rm(endpoint, { force: true }).catch(() => {
|
|
3890
4646
|
});
|
|
@@ -3892,7 +4648,7 @@ async function startControlServer(opts) {
|
|
|
3892
4648
|
await promises.rm(tokenPath, { force: true }).catch(() => {
|
|
3893
4649
|
});
|
|
3894
4650
|
}
|
|
3895
|
-
return { endpoint, close };
|
|
4651
|
+
return { endpoint, stopServing, close };
|
|
3896
4652
|
}
|
|
3897
4653
|
function deterministicJitterMs(input) {
|
|
3898
4654
|
const ratio = input.ratio ?? 0.2;
|
|
@@ -4186,6 +4942,7 @@ var WsTransport = class {
|
|
|
4186
4942
|
deviceId: this.opts.deviceId,
|
|
4187
4943
|
productId: this.opts.productId,
|
|
4188
4944
|
runtimes: this.opts.runtimes,
|
|
4945
|
+
configuredToolsets: this.opts.configuredToolsets === void 0 ? void 0 : [...this.opts.configuredToolsets],
|
|
4189
4946
|
cursor: this.opts.getCursor?.()
|
|
4190
4947
|
});
|
|
4191
4948
|
socket.send(encodeEnvelope(hello));
|
|
@@ -4275,6 +5032,7 @@ var ConnectionManager = class {
|
|
|
4275
5032
|
productId: opts.productId,
|
|
4276
5033
|
capabilities: opts.capabilities,
|
|
4277
5034
|
runtimes: opts.runtimes,
|
|
5035
|
+
configuredToolsets: opts.configuredToolsets,
|
|
4278
5036
|
getCursor: () => this.cursor,
|
|
4279
5037
|
onEnvelope: (envelope) => this.deliver(envelope),
|
|
4280
5038
|
onStateChange: (state) => {
|
|
@@ -4397,7 +5155,7 @@ var ConnectionManager = class {
|
|
|
4397
5155
|
* already-delivered seqs (see its own doc comment) so the failed seq's own
|
|
4398
5156
|
* redelivery can get through — but that same frozen watermark also means
|
|
4399
5157
|
* every OTHER seq above it rides along on every re-poll too. Without this,
|
|
4400
|
-
* a seq already mid-flight (e.g. a `task.offer` whose
|
|
5158
|
+
* a seq already mid-flight (e.g. a `task.offer` whose prepared operation start()
|
|
4401
5159
|
* hasn't resolved yet) would be re-enqueued into `processingChain` on
|
|
4402
5160
|
* every such re-poll, piling up duplicate copies that — once the first
|
|
4403
5161
|
* finally resolves and the chain unwinds through them — run its handler
|
|
@@ -5273,6 +6031,12 @@ var RECLAIM_FILENAME = `${DAEMON_OWNER_FILENAME}.reclaim`;
|
|
|
5273
6031
|
var MAX_OWNER_BYTES = 4096;
|
|
5274
6032
|
var RECLAIM_MALFORMED_GRACE_MS = 3e4;
|
|
5275
6033
|
var SELF_PROCESS_STARTED_AT = new Date(Date.now() - process.uptime() * 1e3).toISOString();
|
|
6034
|
+
function endOwnershipProbe(socket, response) {
|
|
6035
|
+
socket.on("error", () => {
|
|
6036
|
+
});
|
|
6037
|
+
if (response === void 0) socket.end();
|
|
6038
|
+
else socket.end(response);
|
|
6039
|
+
}
|
|
5276
6040
|
var STORE_MUTEX_ID_PREFIX = "byok-store-mutex-v1:";
|
|
5277
6041
|
var STORE_MUTEX_PROBE_TIMEOUT_MS = 1e3;
|
|
5278
6042
|
var STORE_MUTEX_SOCKET_FILENAME = "mutex.sock";
|
|
@@ -5379,7 +6143,7 @@ async function portIsBound(port) {
|
|
|
5379
6143
|
});
|
|
5380
6144
|
}
|
|
5381
6145
|
async function createLivenessListener() {
|
|
5382
|
-
const server = createServer((socket) => socket
|
|
6146
|
+
const server = createServer((socket) => endOwnershipProbe(socket));
|
|
5383
6147
|
const port = await new Promise((resolve, reject) => {
|
|
5384
6148
|
server.once("error", reject);
|
|
5385
6149
|
server.listen({ host: "127.0.0.1", port: 0, exclusive: true }, () => {
|
|
@@ -5464,7 +6228,7 @@ async function acquireStoreMutex(canonicalStoreDir) {
|
|
|
5464
6228
|
}
|
|
5465
6229
|
await clearStaleStoreMutexSocket(endpoint, identity);
|
|
5466
6230
|
}
|
|
5467
|
-
const server = createServer((socket) => socket
|
|
6231
|
+
const server = createServer((socket) => endOwnershipProbe(socket, `${STORE_MUTEX_ID_PREFIX}${identity}
|
|
5468
6232
|
`));
|
|
5469
6233
|
try {
|
|
5470
6234
|
await new Promise((resolve, reject) => {
|
|
@@ -5606,7 +6370,7 @@ function toRuntimeInfoCapabilities(caps) {
|
|
|
5606
6370
|
resume: caps.resume,
|
|
5607
6371
|
approvalInteractive: caps.approvalInteractive,
|
|
5608
6372
|
...caps.mcpToolsets === void 0 ? {} : { mcpToolsets: caps.mcpToolsets },
|
|
5609
|
-
permissionModes: caps.permissionModes
|
|
6373
|
+
permissionModes: [...caps.permissionModes]
|
|
5610
6374
|
};
|
|
5611
6375
|
}
|
|
5612
6376
|
var CursorStore = class {
|
|
@@ -5846,6 +6610,9 @@ var DaemonObserver = class {
|
|
|
5846
6610
|
noteGitWorkspace(event) {
|
|
5847
6611
|
this.emit({ kind: "git-workspace", ts: nowIso(), ...event });
|
|
5848
6612
|
}
|
|
6613
|
+
noteRuntimeDisposalFailure(event) {
|
|
6614
|
+
this.emit({ kind: "runtime-disposal-failed", ts: nowIso(), ...event });
|
|
6615
|
+
}
|
|
5849
6616
|
/**
|
|
5850
6617
|
* Finding F4: wired from `TaskRunnerDeps.onApprovalDispatched`, called
|
|
5851
6618
|
* synchronously by `TaskRunner.dispatchApproval` BEFORE its own
|
|
@@ -7499,20 +8266,20 @@ function isKnownRuntimeId(id) {
|
|
|
7499
8266
|
var DEFAULT_RUNTIME_PREFERENCE = ["claude", "codex", "pi"];
|
|
7500
8267
|
function orderByPreference(candidates, preference) {
|
|
7501
8268
|
const rank = new Map(preference.map((id, index) => [id, index]));
|
|
7502
|
-
return [...candidates].sort((a, b) => (rank.get(a.id) ?? preference.length) - (rank.get(b.id) ?? preference.length));
|
|
8269
|
+
return [...candidates].sort((a, b) => (rank.get(a.descriptor.id) ?? preference.length) - (rank.get(b.descriptor.id) ?? preference.length));
|
|
7503
8270
|
}
|
|
7504
|
-
function adapterSupportsMode(
|
|
7505
|
-
return
|
|
8271
|
+
function adapterSupportsMode(descriptor, mode) {
|
|
8272
|
+
return descriptor.capabilities.permissionModes.includes(mode);
|
|
7506
8273
|
}
|
|
7507
|
-
function adapterSupportsMcpToolsets(
|
|
7508
|
-
return
|
|
8274
|
+
function adapterSupportsMcpToolsets(descriptor) {
|
|
8275
|
+
return descriptor.capabilities.mcpToolsets === true;
|
|
7509
8276
|
}
|
|
7510
8277
|
function withoutRequiredToolsets(payload) {
|
|
7511
8278
|
if (!("requiredToolsets" in payload)) return payload;
|
|
7512
8279
|
const { requiredToolsets, ...offer } = payload;
|
|
7513
8280
|
return offer;
|
|
7514
8281
|
}
|
|
7515
|
-
function
|
|
8282
|
+
function errorMessage4(err) {
|
|
7516
8283
|
return err instanceof Error ? err.message : String(err);
|
|
7517
8284
|
}
|
|
7518
8285
|
function raceSettleFirst(fn, timeoutMs) {
|
|
@@ -7565,7 +8332,7 @@ async function openArtifact(workspaceDir, name) {
|
|
|
7565
8332
|
try {
|
|
7566
8333
|
handle = await promises.open(candidate, constants.O_RDONLY | O_NOFOLLOW);
|
|
7567
8334
|
} catch (err) {
|
|
7568
|
-
return { ok: false, reason: `artifact "${name}" could not be opened: ${
|
|
8335
|
+
return { ok: false, reason: `artifact "${name}" could not be opened: ${errorMessage4(err)}` };
|
|
7569
8336
|
}
|
|
7570
8337
|
try {
|
|
7571
8338
|
const st = await handle.stat();
|
|
@@ -7577,7 +8344,7 @@ async function openArtifact(workspaceDir, name) {
|
|
|
7577
8344
|
} catch (err) {
|
|
7578
8345
|
await handle.close().catch(() => {
|
|
7579
8346
|
});
|
|
7580
|
-
return { ok: false, reason: `artifact "${name}" could not be verified: ${
|
|
8347
|
+
return { ok: false, reason: `artifact "${name}" could not be verified: ${errorMessage4(err)}` };
|
|
7581
8348
|
}
|
|
7582
8349
|
return { ok: true, handle };
|
|
7583
8350
|
}
|
|
@@ -7591,13 +8358,13 @@ var TaskRunner = class {
|
|
|
7591
8358
|
* Finding F4 (cancel lost during the offer-processing window): a
|
|
7592
8359
|
* `task.cancel` for a taskId that hasn't finished `handleOffer` yet (still
|
|
7593
8360
|
* awaiting adapter detection / instruction resolution / workspace setup /
|
|
7594
|
-
* `
|
|
8361
|
+
* prepared operation `start()`) has no `this.tasks` entry to land on — it used to be
|
|
7595
8362
|
* silently dropped, and the runtime session `handleOffer` was about to
|
|
7596
8363
|
* register would then run an unsupervised ("zombie") turn nobody asked
|
|
7597
8364
|
* for anymore. Recording the taskId here lets `handleOffer` consult it at
|
|
7598
8365
|
* the two points where it can still safely react (see its body): before
|
|
7599
8366
|
* claiming at all (decline instead of ever starting a session), and right
|
|
7600
|
-
* after
|
|
8367
|
+
* after the prepared operation resolves but before this task is registered as
|
|
7601
8368
|
* active (tear the just-started session down immediately, before its
|
|
7602
8369
|
* event loop ever pumps a single event). Consumed (deleted) at whichever
|
|
7603
8370
|
* checkpoint handles it; a cancel for a taskId that's already active,
|
|
@@ -7623,12 +8390,12 @@ var TaskRunner = class {
|
|
|
7623
8390
|
* checkpoint-2 cancel-teardown, or successful registration into
|
|
7624
8391
|
* `this.tasks`). Bounded eviction on `pendingCancelled` (below) must never
|
|
7625
8392
|
* remove an entry for a taskId in this set: doing so is exactly the bug —
|
|
7626
|
-
* block task A in `
|
|
8393
|
+
* block task A in prepared-operation `start()`, deliver A's own `task.cancel` (so
|
|
7627
8394
|
* `pendingCancelled` gets an entry for A while A is still in-flight),
|
|
7628
8395
|
* then deliver `MAX_TRACKED_TASK_IDS` more cancels for unrelated taskIds
|
|
7629
8396
|
* nobody ever offered — under naive oldest-wins eviction, A's entry (the
|
|
7630
8397
|
* single oldest) gets evicted purely because of unrelated churn, so when
|
|
7631
|
-
*
|
|
8398
|
+
* the prepared operation finally resolves, checkpoint 2 finds no cancel marker
|
|
7632
8399
|
* and the already-cancelled task starts a real session. See
|
|
7633
8400
|
* `evictPendingCancelled` below for the fix, and
|
|
7634
8401
|
* `task-runner-bounded-collections.test.ts` for a test mirroring this
|
|
@@ -7648,7 +8415,7 @@ var TaskRunner = class {
|
|
|
7648
8415
|
* explicitly relies on redelivered handlers being idempotent for exactly
|
|
7649
8416
|
* this reason). `handleOffer` must treat a redelivered offer for a taskId
|
|
7650
8417
|
* that's already active (`this.tasks`) or already finished (this set) as
|
|
7651
|
-
* a no-op — never a second `
|
|
8418
|
+
* a no-op — never a second prepared-operation `start()` call, which would orphan the
|
|
7652
8419
|
* first session.
|
|
7653
8420
|
*
|
|
7654
8421
|
* M3-B: unbounded otherwise — a long-lived daemon that's finished many
|
|
@@ -7717,10 +8484,9 @@ var TaskRunner = class {
|
|
|
7717
8484
|
this.stoppingOffers = true;
|
|
7718
8485
|
}
|
|
7719
8486
|
/**
|
|
7720
|
-
*
|
|
7721
|
-
*
|
|
7722
|
-
*
|
|
7723
|
-
* terminal message is sent either way) but reports `task.fail` rather than
|
|
8487
|
+
* Shutdown of every currently ACTIVE task for the control socket's
|
|
8488
|
+
* `shutdown` RPC. Soft interrupt remains bounded, but each task's
|
|
8489
|
+
* authoritative close receipt must settle successfully. Reports `task.fail` rather than
|
|
7724
8490
|
* `task.cancelled` — these tasks aren't ending because the SERVER
|
|
7725
8491
|
* cancelled them, they're ending because this device is shutting down.
|
|
7726
8492
|
* `retryable: true` throughout: nothing about the task/policy itself was
|
|
@@ -7774,28 +8540,13 @@ var TaskRunner = class {
|
|
|
7774
8540
|
* unconditionally, so a hung `interrupt()` (a misbehaving adapter) can
|
|
7775
8541
|
* never block `task.fail` from being sent at all.
|
|
7776
8542
|
*
|
|
7777
|
-
*
|
|
7778
|
-
*
|
|
7779
|
-
*
|
|
7780
|
-
*
|
|
7781
|
-
* far more here than it used to for the pre-existing graceful-shutdown-only
|
|
7782
|
-
* caller, since THAT path is additionally bounded by an outer deadline
|
|
7783
|
-
* (`SHUTDOWN_TASK_TEARDOWN_DEADLINE_MS`/`DaemonConfig.shutdownGraceMs`,
|
|
7784
|
-
* `create-daemon.ts`), while resource-limit enforcement fires during
|
|
7785
|
-
* ordinary operation with no such outer bound watching it). `close()` is
|
|
7786
|
-
* every adapter's harder teardown primitive — an actual process-level kill
|
|
7787
|
-
* (SIGTERM, or `taskkill /F` on Windows — see e.g.
|
|
7788
|
-
* `ClaudeProcessClient.kill()`/`PiRpcClient.kill()`) as opposed to pi's own
|
|
7789
|
-
* soft in-band `interrupt()` (an RPC `abort` message that leaves the
|
|
7790
|
-
* process alive and resumable) — so escalating to it is the closest thing
|
|
7791
|
-
* to a "hard kill" the `Session` interface exposes. `finish()` below calls
|
|
7792
|
-
* `session.close()` again regardless (documented idempotent) — this isn't
|
|
7793
|
-
* a substitute for that, only an earlier, bounded attempt at actually
|
|
7794
|
-
* stopping a stuck runtime before this method gives up and reports failure
|
|
7795
|
-
* anyway.
|
|
8543
|
+
* After the bounded soft interrupt, `finish()` always awaits the authoritative
|
|
8544
|
+
* `Session.close()` receipt. A failed receipt retains active/Git ownership;
|
|
8545
|
+
* shutdown surfaces the rejection while resource enforcement leaves local
|
|
8546
|
+
* evidence for a later retry.
|
|
7796
8547
|
*
|
|
7797
8548
|
* Re-checks task identity (`this.tasks.get(...) === active`) immediately
|
|
7798
|
-
* before sending `task.fail`: the interrupt
|
|
8549
|
+
* before sending `task.fail`: the interrupt race above has await
|
|
7799
8550
|
* points during which a DIFFERENT path (a racing `task.cancel`/
|
|
7800
8551
|
* `task.reject`, or the session completing normally on its own) may have
|
|
7801
8552
|
* already finished this exact task and sent its own terminal message.
|
|
@@ -7804,20 +8555,25 @@ var TaskRunner = class {
|
|
|
7804
8555
|
* identity-check guard for the same class of race.
|
|
7805
8556
|
*/
|
|
7806
8557
|
async teardownActiveTask(active, reason, retryable) {
|
|
8558
|
+
if (active.finalizationStarted) return this.finish(active.taskId);
|
|
8559
|
+
if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
|
|
7807
8560
|
active.beingTornDown = true;
|
|
7808
8561
|
await this.observeGit(active, "salvage");
|
|
7809
8562
|
const timeoutMs = this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS;
|
|
7810
|
-
|
|
7811
|
-
if (
|
|
7812
|
-
await raceSettleFirst(() => active.session.close(), timeoutMs);
|
|
7813
|
-
}
|
|
7814
|
-
if (this.tasks.get(active.taskId) !== active) return;
|
|
8563
|
+
await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
|
|
8564
|
+
if (this.tasks.get(active.taskId) !== active) return true;
|
|
7815
8565
|
this.deps.send(createEnvelope("task.fail", { reason, retryable }, { taskId: active.taskId }));
|
|
7816
|
-
|
|
8566
|
+
return this.finish(active.taskId);
|
|
7817
8567
|
}
|
|
7818
8568
|
/** Graceful-shutdown caller of {@link teardownActiveTask} — see `shutdownActiveTasks`'s own doc comment. `retryable: true`: nothing about the task/policy itself was ever at fault, only this device's own availability right now. */
|
|
7819
8569
|
async shutdownTask(active, reason) {
|
|
7820
|
-
await this.teardownActiveTask(active, `daemon shutting down: ${reason}`, true);
|
|
8570
|
+
const disposed = await this.teardownActiveTask(active, `daemon shutting down: ${reason}`, true);
|
|
8571
|
+
if (!disposed) {
|
|
8572
|
+
throw new RuntimeDisposalFailure({
|
|
8573
|
+
stage: "quiescence",
|
|
8574
|
+
reason: `${active.adapter.descriptor.id} runtime ownership remains active after shutdown disposal failed`
|
|
8575
|
+
});
|
|
8576
|
+
}
|
|
7821
8577
|
}
|
|
7822
8578
|
/**
|
|
7823
8579
|
* M5 batch-3 (workstream 2): shared entry point for both resource-limit
|
|
@@ -7937,15 +8693,33 @@ var TaskRunner = class {
|
|
|
7937
8693
|
this.decline(taskId, resolvedMcp.reason, true);
|
|
7938
8694
|
return;
|
|
7939
8695
|
}
|
|
8696
|
+
const decision = computeEffectivePolicy(payload.policy, this.deps.permissionDefaults);
|
|
8697
|
+
if (!decision.ok) {
|
|
8698
|
+
this.decline(taskId, decision.reason ?? "policy rejected", false);
|
|
8699
|
+
return;
|
|
8700
|
+
}
|
|
8701
|
+
const offered = withoutRequiredToolsets(payload);
|
|
7940
8702
|
const requestedRuntime = payload.dispatchSelection?.runtimeId ?? payload.runtime;
|
|
7941
8703
|
const pick = await this.pickAdapter(requestedRuntime, payload.policy.mode, requiredToolsets !== void 0);
|
|
7942
8704
|
if (!pick.ok) {
|
|
7943
8705
|
this.decline(taskId, pick.reason, pick.retryable);
|
|
7944
8706
|
return;
|
|
7945
8707
|
}
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
8708
|
+
let prepared;
|
|
8709
|
+
try {
|
|
8710
|
+
prepared = await pick.adapter.prepare({
|
|
8711
|
+
offer: offered,
|
|
8712
|
+
policy: decision.policy,
|
|
8713
|
+
descriptor: pick.descriptor,
|
|
8714
|
+
requiredToolsetIds: requiredToolsets ?? [],
|
|
8715
|
+
...resolvedMcp?.ok ? { mcpServers: resolvedMcp.servers } : {}
|
|
8716
|
+
});
|
|
8717
|
+
} catch (error) {
|
|
8718
|
+
this.decline(taskId, `runtime preparation failed: ${errorMessage4(error)}`, true);
|
|
8719
|
+
return;
|
|
8720
|
+
}
|
|
8721
|
+
if (prepared.kind === "reject") {
|
|
8722
|
+
this.decline(taskId, prepared.reason, prepared.retryable);
|
|
7949
8723
|
return;
|
|
7950
8724
|
}
|
|
7951
8725
|
let known = void 0;
|
|
@@ -7980,6 +8754,7 @@ var TaskRunner = class {
|
|
|
7980
8754
|
}
|
|
7981
8755
|
} else {
|
|
7982
8756
|
workspaceDir = path20.join(this.deps.workspaceRoot, taskId);
|
|
8757
|
+
gitWorkspaceId = randomUUID();
|
|
7983
8758
|
}
|
|
7984
8759
|
try {
|
|
7985
8760
|
gitLease = await gitManager.acquireLease(workspaceDir, payload.sessionRef);
|
|
@@ -7995,6 +8770,26 @@ var TaskRunner = class {
|
|
|
7995
8770
|
this.decline(taskId, "workspace mode is unavailable", true);
|
|
7996
8771
|
return;
|
|
7997
8772
|
}
|
|
8773
|
+
const env = buildRuntimeEnv({
|
|
8774
|
+
ambient: process.env,
|
|
8775
|
+
requirements: pick.descriptor.environmentRequirements,
|
|
8776
|
+
locallyAllowedNames: this.deps.runtimeEnvironment?.[pick.descriptor.id]?.allow
|
|
8777
|
+
});
|
|
8778
|
+
const manifest = sealRuntimeOperationManifest({
|
|
8779
|
+
taskId,
|
|
8780
|
+
runtimeId: pick.descriptor.id,
|
|
8781
|
+
descriptor: pick.descriptor,
|
|
8782
|
+
policy: decision.policy,
|
|
8783
|
+
requiredToolsetIds: requiredToolsets ?? [],
|
|
8784
|
+
...offered.dispatchSelection === void 0 ? {} : { dispatchSelection: offered.dispatchSelection },
|
|
8785
|
+
...known === void 0 || payload.sessionRef === void 0 ? {} : { sessionRef: payload.sessionRef },
|
|
8786
|
+
workspace: {
|
|
8787
|
+
workspaceDir,
|
|
8788
|
+
...gitWorkspaceId === void 0 ? {} : { workspaceId: gitWorkspaceId },
|
|
8789
|
+
...gitBaseline === void 0 ? {} : { baseline: gitBaseline }
|
|
8790
|
+
},
|
|
8791
|
+
forwardedEnvironmentNames: Object.freeze(Object.keys(env).sort())
|
|
8792
|
+
});
|
|
7998
8793
|
this.deps.send(
|
|
7999
8794
|
createEnvelope(
|
|
8000
8795
|
"task.claim",
|
|
@@ -8010,7 +8805,7 @@ var TaskRunner = class {
|
|
|
8010
8805
|
// (the merely REQUESTED runtime): this is what closes the gap
|
|
8011
8806
|
// where an auto-selected task left the server never learning
|
|
8012
8807
|
// which runtime actually ran.
|
|
8013
|
-
runtime: isKnownRuntimeId(
|
|
8808
|
+
runtime: isKnownRuntimeId(manifest.descriptor.id) ? manifest.descriptor.id : void 0,
|
|
8014
8809
|
// S0/D-4 (`task.claim.capabilities`, docs/protocol.md §2.4): the
|
|
8015
8810
|
// selected adapter's own capability self-report, carried on the
|
|
8016
8811
|
// same message that establishes the task↔runtime binding. The
|
|
@@ -8027,7 +8822,7 @@ var TaskRunner = class {
|
|
|
8027
8822
|
// Gating them would silently strip a custom steer-capable
|
|
8028
8823
|
// adapter's own truth and leave the server fail-closing on it
|
|
8029
8824
|
// forever.
|
|
8030
|
-
capabilities: toRuntimeInfoCapabilities(
|
|
8825
|
+
capabilities: toRuntimeInfoCapabilities(manifest.descriptor.capabilities)
|
|
8031
8826
|
},
|
|
8032
8827
|
{ taskId }
|
|
8033
8828
|
)
|
|
@@ -8038,7 +8833,7 @@ var TaskRunner = class {
|
|
|
8038
8833
|
if (plainWorkspaceNeedsResolve) workspaceDir = await this.resolveWorkspaceDir(taskId, known?.workspaceDir);
|
|
8039
8834
|
} catch (err) {
|
|
8040
8835
|
gitLease?.release();
|
|
8041
|
-
await this.fail(taskId, `failed to resolve instruction blob: ${
|
|
8836
|
+
await this.fail(taskId, `failed to resolve instruction blob: ${errorMessage4(err)}`, true);
|
|
8042
8837
|
return;
|
|
8043
8838
|
}
|
|
8044
8839
|
if (this.deps.gitWorkspaceManager && gitLease) {
|
|
@@ -8047,7 +8842,7 @@ var TaskRunner = class {
|
|
|
8047
8842
|
if (gitExisting) {
|
|
8048
8843
|
observation = await this.deps.gitWorkspaceManager.validateExisting(workspaceDir);
|
|
8049
8844
|
} else {
|
|
8050
|
-
const workspaceId2 = randomUUID();
|
|
8845
|
+
const workspaceId2 = gitWorkspaceId ?? randomUUID();
|
|
8051
8846
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
8052
8847
|
gitWorkspaceId = workspaceId2;
|
|
8053
8848
|
await this.deps.gitWorkspaceStore?.upsert({
|
|
@@ -8098,30 +8893,11 @@ var TaskRunner = class {
|
|
|
8098
8893
|
return;
|
|
8099
8894
|
}
|
|
8100
8895
|
}
|
|
8101
|
-
const
|
|
8102
|
-
|
|
8103
|
-
|
|
8896
|
+
const startInput = {
|
|
8897
|
+
manifest,
|
|
8898
|
+
instruction: gitWorkspaceId ? prependGitWorkspaceGuidance(resolvedInstruction) : resolvedInstruction,
|
|
8899
|
+
env,
|
|
8104
8900
|
...resolvedMcp?.ok ? { mcpServers: resolvedMcp.servers } : {},
|
|
8105
|
-
...gitWorkspaceId ? { gitWorkspace: { workspaceId: gitWorkspaceId, baseline: gitBaseline } } : {},
|
|
8106
|
-
// M5: no longer `process.env` verbatim (see `environment.ts`'s own
|
|
8107
|
-
// module doc comment for the credential-leak gap that closed) —
|
|
8108
|
-
// built fresh per task from the SPECIFIC adapter `pickAdapter`
|
|
8109
|
-
// above already selected, so this always runs after adapter
|
|
8110
|
-
// selection: `pick.adapter.environmentRequirements?.()` (undefined
|
|
8111
|
-
// ⇒ platform baseline only, fail-closed) plus this device's own
|
|
8112
|
-
// `runtimeEnvironment` override, keyed by that same adapter's `id`.
|
|
8113
|
-
env: buildRuntimeEnv({
|
|
8114
|
-
ambient: process.env,
|
|
8115
|
-
requirements: pick.adapter.environmentRequirements?.(),
|
|
8116
|
-
locallyAllowedNames: this.deps.runtimeEnvironment?.[pick.adapter.id]?.allow
|
|
8117
|
-
}),
|
|
8118
|
-
// M4 Phase 3: adapter-agnostic and cheap to always populate — only an
|
|
8119
|
-
// adapter whose runtime genuinely supports an out-of-band approval
|
|
8120
|
-
// pause (claude, today) ever reads this. `resolve` is a closure over
|
|
8121
|
-
// `taskId` (not a pre-bound approvalId): it looks up whichever
|
|
8122
|
-
// approval is CURRENTLY pending for this task at call time, since one
|
|
8123
|
-
// task/session can face several approval requests, one at a time,
|
|
8124
|
-
// over its life. See `types.ts`'s `ApprovalChannel` doc comment.
|
|
8125
8901
|
approvalChannel: {
|
|
8126
8902
|
taskId,
|
|
8127
8903
|
storeDir: this.deps.storeDir,
|
|
@@ -8137,45 +8913,19 @@ var TaskRunner = class {
|
|
|
8137
8913
|
}
|
|
8138
8914
|
}
|
|
8139
8915
|
};
|
|
8140
|
-
const effectiveOffer = {
|
|
8141
|
-
...withoutRequiredToolsets(payload),
|
|
8142
|
-
instruction: gitWorkspaceId ? prependGitWorkspaceGuidance(resolvedInstruction) : resolvedInstruction,
|
|
8143
|
-
// Never forward a sessionRef this device has no recorded workspace
|
|
8144
|
-
// for (stale, from another device, or simply made up) — an adapter
|
|
8145
|
-
// that tries to resume an id it never minted fails outright (pi:
|
|
8146
|
-
// "No session found matching '<id>'", exit 1, empirically confirmed)
|
|
8147
|
-
// instead of silently starting fresh, so an unresolvable sessionRef
|
|
8148
|
-
// must look identical to "none supplied" by the time it reaches the
|
|
8149
|
-
// adapter, not get forwarded as a resume attempt doomed to fail.
|
|
8150
|
-
sessionRef: known ? payload.sessionRef : void 0
|
|
8151
|
-
};
|
|
8152
8916
|
let session;
|
|
8153
8917
|
try {
|
|
8154
|
-
session = await
|
|
8918
|
+
session = await prepared.operation.start(startInput);
|
|
8155
8919
|
} catch (err) {
|
|
8156
|
-
const
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
await this.fail(taskId, `adapter failed to start: ${errorMessage3(err)}`, retryable);
|
|
8160
|
-
return;
|
|
8161
|
-
}
|
|
8162
|
-
if (this.pendingCancelled.has(taskId)) {
|
|
8163
|
-
const reason = this.pendingCancelled.get(taskId);
|
|
8164
|
-
this.pendingCancelled.delete(taskId);
|
|
8165
|
-
try {
|
|
8166
|
-
await session.interrupt();
|
|
8167
|
-
} catch {
|
|
8168
|
-
}
|
|
8169
|
-
try {
|
|
8170
|
-
await session.close();
|
|
8171
|
-
} catch {
|
|
8920
|
+
const failure = projectRuntimeBoundaryFailure(err, "start");
|
|
8921
|
+
if (failure.contractViolation) {
|
|
8922
|
+
console.error("[byok/client] runtime adapter start() returned an untyped failure", err);
|
|
8172
8923
|
}
|
|
8924
|
+
await this.updateGitPhaseBestEffort(gitWorkspaceId, "failed", "repository-invalid");
|
|
8173
8925
|
gitLease?.release();
|
|
8174
|
-
await this.
|
|
8175
|
-
this.deps.send(createEnvelope("task.cancelled", { reason }, { taskId }));
|
|
8926
|
+
await this.fail(taskId, failure.reason, failure.retryable);
|
|
8176
8927
|
return;
|
|
8177
8928
|
}
|
|
8178
|
-
this.deps.send(createEnvelope("task.started", {}, { taskId }));
|
|
8179
8929
|
const active = {
|
|
8180
8930
|
taskId,
|
|
8181
8931
|
adapter: pick.adapter,
|
|
@@ -8192,6 +8942,21 @@ var TaskRunner = class {
|
|
|
8192
8942
|
approvalQueue: [],
|
|
8193
8943
|
outputBytesSoFar: 0
|
|
8194
8944
|
};
|
|
8945
|
+
if (this.pendingCancelled.has(taskId)) {
|
|
8946
|
+
const reason = this.pendingCancelled.get(taskId);
|
|
8947
|
+
this.pendingCancelled.delete(taskId);
|
|
8948
|
+
this.tasks.set(taskId, active);
|
|
8949
|
+
this.reserveSemanticTerminal(active);
|
|
8950
|
+
try {
|
|
8951
|
+
await session.interrupt();
|
|
8952
|
+
} catch {
|
|
8953
|
+
}
|
|
8954
|
+
await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
|
|
8955
|
+
this.deps.send(createEnvelope("task.cancelled", { reason }, { taskId }));
|
|
8956
|
+
await this.finish(taskId);
|
|
8957
|
+
return;
|
|
8958
|
+
}
|
|
8959
|
+
this.deps.send(createEnvelope("task.started", {}, { taskId }));
|
|
8195
8960
|
this.tasks.set(taskId, active);
|
|
8196
8961
|
if (payload.limits?.maxDurationMs !== void 0) {
|
|
8197
8962
|
this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
|
|
@@ -8249,6 +9014,7 @@ var TaskRunner = class {
|
|
|
8249
9014
|
async pump(active) {
|
|
8250
9015
|
try {
|
|
8251
9016
|
for await (const event of active.session.events) {
|
|
9017
|
+
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
8252
9018
|
if (this.tasks.get(active.taskId) !== active) return;
|
|
8253
9019
|
active.outputBytesSoFar += estimateEventBytes(event);
|
|
8254
9020
|
if (active.outputBytesSoFar > this.maxTaskOutputBytes) {
|
|
@@ -8271,7 +9037,7 @@ var TaskRunner = class {
|
|
|
8271
9037
|
try {
|
|
8272
9038
|
await active.session.resolveApproval(approved, reason);
|
|
8273
9039
|
} catch (err) {
|
|
8274
|
-
await this.fail(taskId, `failed to resume session after approval decision: ${
|
|
9040
|
+
await this.fail(taskId, `failed to resume session after approval decision: ${errorMessage4(err)}`, false);
|
|
8275
9041
|
}
|
|
8276
9042
|
});
|
|
8277
9043
|
continue;
|
|
@@ -8283,6 +9049,7 @@ var TaskRunner = class {
|
|
|
8283
9049
|
const outcome = await this.resolveResultDocument(active, finalOutput);
|
|
8284
9050
|
if (!outcome.deliver) return;
|
|
8285
9051
|
await this.observeGit(active, "completed");
|
|
9052
|
+
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
8286
9053
|
if (outcome.document !== void 0 && !this.hasResultDocumentCapability()) {
|
|
8287
9054
|
await this.fail(
|
|
8288
9055
|
active.taskId,
|
|
@@ -8291,6 +9058,7 @@ var TaskRunner = class {
|
|
|
8291
9058
|
);
|
|
8292
9059
|
return;
|
|
8293
9060
|
}
|
|
9061
|
+
if (!this.reserveSemanticTerminal(active)) return;
|
|
8294
9062
|
this.deps.send(
|
|
8295
9063
|
createEnvelope(
|
|
8296
9064
|
"task.complete",
|
|
@@ -8326,11 +9094,17 @@ var TaskRunner = class {
|
|
|
8326
9094
|
active.batcher.push(event);
|
|
8327
9095
|
}
|
|
8328
9096
|
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
8329
|
-
|
|
9097
|
+
const failure = projectRuntimeBoundaryFailure(void 0, "run");
|
|
9098
|
+
console.error("[byok/client] runtime adapter events iterable ended without terminal authority");
|
|
9099
|
+
await this.fail(active.taskId, failure.reason, failure.retryable);
|
|
8330
9100
|
} catch (err) {
|
|
8331
9101
|
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
8332
9102
|
active.batcher.flush();
|
|
8333
|
-
|
|
9103
|
+
const failure = projectRuntimeBoundaryFailure(err, "run");
|
|
9104
|
+
if (failure.contractViolation) {
|
|
9105
|
+
console.error("[byok/client] runtime adapter events iterable returned an untyped failure", err);
|
|
9106
|
+
}
|
|
9107
|
+
await this.fail(active.taskId, failure.reason, failure.retryable);
|
|
8334
9108
|
}
|
|
8335
9109
|
}
|
|
8336
9110
|
/**
|
|
@@ -8369,7 +9143,7 @@ var TaskRunner = class {
|
|
|
8369
9143
|
try {
|
|
8370
9144
|
bytes = await opened.handle.readFile();
|
|
8371
9145
|
} catch (err) {
|
|
8372
|
-
this.reportArtifactError(active, name, `failed to read artifact "${name}": ${
|
|
9146
|
+
this.reportArtifactError(active, name, `failed to read artifact "${name}": ${errorMessage4(err)}`);
|
|
8373
9147
|
return;
|
|
8374
9148
|
} finally {
|
|
8375
9149
|
await opened.handle.close().catch(() => {
|
|
@@ -8386,7 +9160,7 @@ var TaskRunner = class {
|
|
|
8386
9160
|
const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType);
|
|
8387
9161
|
this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
|
|
8388
9162
|
} catch (err) {
|
|
8389
|
-
this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${
|
|
9163
|
+
this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage4(err)}`);
|
|
8390
9164
|
}
|
|
8391
9165
|
}
|
|
8392
9166
|
/** Loud, non-silent artifact failure (finding F7): logged, and folded into this task's own progress stream as an `error` AgentEvent rather than swallowed — the task itself can still complete normally, but the omission is now visible. */
|
|
@@ -8400,6 +9174,14 @@ var TaskRunner = class {
|
|
|
8400
9174
|
this.setPendingCancelled(taskId, reason);
|
|
8401
9175
|
return;
|
|
8402
9176
|
}
|
|
9177
|
+
if (active.finalizationStarted) {
|
|
9178
|
+
await this.finish(taskId);
|
|
9179
|
+
return;
|
|
9180
|
+
}
|
|
9181
|
+
if (!this.reserveSemanticTerminal(active)) {
|
|
9182
|
+
await active.semanticTerminalSettled;
|
|
9183
|
+
return;
|
|
9184
|
+
}
|
|
8403
9185
|
try {
|
|
8404
9186
|
await active.session.interrupt();
|
|
8405
9187
|
} catch {
|
|
@@ -8428,7 +9210,7 @@ var TaskRunner = class {
|
|
|
8428
9210
|
*
|
|
8429
9211
|
* `inFlightOffers` is naturally tiny (bounded by this device's real
|
|
8430
9212
|
* concurrent-offer-processing count — normally single digits, driven by
|
|
8431
|
-
* how many `task.offer`s are simultaneously mid
|
|
9213
|
+
* how many `task.offer`s are simultaneously mid-prepared-operation start() — nowhere
|
|
8432
9214
|
* near `MAX_TRACKED_TASK_IDS`), so this scan is cheap in practice: it
|
|
8433
9215
|
* finds a safe entry at or near the front almost always. The only case
|
|
8434
9216
|
* where NO entry is safe to evict is every single tracked cancel
|
|
@@ -8781,7 +9563,7 @@ var TaskRunner = class {
|
|
|
8781
9563
|
this.deps.onStaleApprovalDecision?.(taskId, "approve");
|
|
8782
9564
|
return;
|
|
8783
9565
|
}
|
|
8784
|
-
await this.fail(taskId, `failed to resume session after approval: ${
|
|
9566
|
+
await this.fail(taskId, `failed to resume session after approval: ${errorMessage4(err)}`, false);
|
|
8785
9567
|
return;
|
|
8786
9568
|
}
|
|
8787
9569
|
this.clearPendingApproval(resolvedId, "approve", void 0);
|
|
@@ -8812,6 +9594,10 @@ var TaskRunner = class {
|
|
|
8812
9594
|
async handleReject(taskId, reason, approvalId) {
|
|
8813
9595
|
const active = this.tasks.get(taskId);
|
|
8814
9596
|
if (!active) return;
|
|
9597
|
+
if (active.finalizationStarted) {
|
|
9598
|
+
await this.finish(taskId);
|
|
9599
|
+
return;
|
|
9600
|
+
}
|
|
8815
9601
|
if (approvalId !== void 0 && approvalId !== active.pendingApprovalId) {
|
|
8816
9602
|
this.deps.onStaleApprovalDecision?.(
|
|
8817
9603
|
taskId,
|
|
@@ -8830,6 +9616,10 @@ var TaskRunner = class {
|
|
|
8830
9616
|
}
|
|
8831
9617
|
}
|
|
8832
9618
|
this.clearPendingApproval(resolvedId, "reject", reason);
|
|
9619
|
+
if (!this.reserveSemanticTerminal(active)) {
|
|
9620
|
+
await active.semanticTerminalSettled;
|
|
9621
|
+
return;
|
|
9622
|
+
}
|
|
8833
9623
|
try {
|
|
8834
9624
|
await active.session.interrupt();
|
|
8835
9625
|
} catch {
|
|
@@ -8844,6 +9634,14 @@ var TaskRunner = class {
|
|
|
8844
9634
|
}
|
|
8845
9635
|
async fail(taskId, reason, retryable) {
|
|
8846
9636
|
const active = this.tasks.get(taskId);
|
|
9637
|
+
if (active?.finalizationStarted) {
|
|
9638
|
+
await this.finish(taskId);
|
|
9639
|
+
return;
|
|
9640
|
+
}
|
|
9641
|
+
if (active && !this.reserveSemanticTerminal(active)) {
|
|
9642
|
+
await active.semanticTerminalSettled;
|
|
9643
|
+
return;
|
|
9644
|
+
}
|
|
8847
9645
|
if (active) await this.observeGit(active, "salvage");
|
|
8848
9646
|
this.deps.send(createEnvelope("task.fail", { reason, retryable }, { taskId }));
|
|
8849
9647
|
await this.finish(taskId);
|
|
@@ -8903,7 +9701,7 @@ var TaskRunner = class {
|
|
|
8903
9701
|
} catch (err) {
|
|
8904
9702
|
await this.fail(
|
|
8905
9703
|
active.taskId,
|
|
8906
|
-
`${RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX}: the configured resultDocument.extract threw: ${
|
|
9704
|
+
`${RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX}: the configured resultDocument.extract threw: ${errorMessage4(err)}`,
|
|
8907
9705
|
false
|
|
8908
9706
|
);
|
|
8909
9707
|
return { deliver: false };
|
|
@@ -8978,32 +9776,64 @@ var TaskRunner = class {
|
|
|
8978
9776
|
}
|
|
8979
9777
|
async finish(taskId) {
|
|
8980
9778
|
const active = this.tasks.get(taskId);
|
|
8981
|
-
if (!active) return;
|
|
8982
|
-
if (active.
|
|
8983
|
-
|
|
8984
|
-
active.
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9779
|
+
if (!active) return true;
|
|
9780
|
+
if (!active.finalizationStarted) {
|
|
9781
|
+
active.finalizationStarted = true;
|
|
9782
|
+
active.beingTornDown = true;
|
|
9783
|
+
if (active.maxDurationTimer) {
|
|
9784
|
+
clearTimeout(active.maxDurationTimer);
|
|
9785
|
+
active.maxDurationTimer = void 0;
|
|
9786
|
+
}
|
|
9787
|
+
active.batcher.stop();
|
|
9788
|
+
this.addFinishedTaskId(taskId);
|
|
9789
|
+
const queued = active.approvalQueue.splice(0);
|
|
9790
|
+
for (const request of queued) {
|
|
9791
|
+
request.resolve({
|
|
9792
|
+
approved: false,
|
|
9793
|
+
reason: `task ${taskId} finished before this queued approval request could be dispatched`
|
|
9794
|
+
});
|
|
9795
|
+
}
|
|
9796
|
+
if (active.pendingApprovalId !== void 0) {
|
|
9797
|
+
try {
|
|
9798
|
+
this.deps.approvalRegistry.resolve(active.pendingApprovalId, "reject", `task ${taskId} finished`);
|
|
9799
|
+
} catch {
|
|
9800
|
+
}
|
|
9000
9801
|
}
|
|
9001
9802
|
}
|
|
9002
|
-
|
|
9803
|
+
const attempt = active.disposalAttempt ?? active.session.close();
|
|
9804
|
+
active.disposalAttempt = attempt;
|
|
9003
9805
|
try {
|
|
9004
|
-
await
|
|
9005
|
-
} catch {
|
|
9806
|
+
await attempt;
|
|
9807
|
+
} catch (caught) {
|
|
9808
|
+
if (active.disposalAttempt === attempt) active.disposalAttempt = void 0;
|
|
9809
|
+
const failure = isRuntimeDisposalFailure(caught) ? caught : new RuntimeDisposalFailure({
|
|
9810
|
+
stage: "quiescence",
|
|
9811
|
+
reason: `${active.adapter.descriptor.id} session.close() returned an untyped disposal failure`
|
|
9812
|
+
}, { cause: caught });
|
|
9813
|
+
console.error(`[byok/client] runtime disposal failed for task ${taskId}: ${failure.message}`);
|
|
9814
|
+
this.deps.onRuntimeDisposalFailure?.({
|
|
9815
|
+
taskId,
|
|
9816
|
+
runtimeId: active.adapter.descriptor.id,
|
|
9817
|
+
stage: failure.stage,
|
|
9818
|
+
reason: failure.message
|
|
9819
|
+
});
|
|
9820
|
+
active.resolveSemanticTerminalSettled?.(false);
|
|
9821
|
+
return false;
|
|
9006
9822
|
}
|
|
9823
|
+
if (this.tasks.get(taskId) !== active) return true;
|
|
9824
|
+
active.gitLease?.release();
|
|
9825
|
+
this.tasks.delete(taskId);
|
|
9826
|
+
active.resolveSemanticTerminalSettled?.(true);
|
|
9827
|
+
return true;
|
|
9828
|
+
}
|
|
9829
|
+
reserveSemanticTerminal(active) {
|
|
9830
|
+
if (active.semanticTerminalReserved || active.finalizationStarted) return false;
|
|
9831
|
+
active.semanticTerminalReserved = true;
|
|
9832
|
+
active.beingTornDown = true;
|
|
9833
|
+
active.semanticTerminalSettled = new Promise((resolve) => {
|
|
9834
|
+
active.resolveSemanticTerminalSettled = resolve;
|
|
9835
|
+
});
|
|
9836
|
+
return true;
|
|
9007
9837
|
}
|
|
9008
9838
|
/** M3-B: bounded insert for `finishedTaskIds` — see its class-level doc comment and `MAX_TRACKED_TASK_IDS`. Evicts the oldest (first-inserted) entry once over cap, same idiom as `ConnectionHub.checkAndRecordDuplicate` (packages/server/src/hub.ts). */
|
|
9009
9839
|
addFinishedTaskId(taskId) {
|
|
@@ -9056,18 +9886,19 @@ var TaskRunner = class {
|
|
|
9056
9886
|
retryable: false
|
|
9057
9887
|
};
|
|
9058
9888
|
}
|
|
9059
|
-
const adapter = this.deps.adapters.find((a) => a.id === requestedRuntime);
|
|
9889
|
+
const adapter = this.deps.adapters.find((a) => a.descriptor.id === requestedRuntime);
|
|
9060
9890
|
if (!adapter) {
|
|
9061
9891
|
return { ok: false, reason: `unknown runtime "${requestedRuntime}"`, retryable: false };
|
|
9062
9892
|
}
|
|
9063
|
-
|
|
9893
|
+
const descriptor = freezeRuntimeAdapterDescriptor(adapter.descriptor);
|
|
9894
|
+
if (!adapterSupportsMode(descriptor, policyMode)) {
|
|
9064
9895
|
return {
|
|
9065
9896
|
ok: false,
|
|
9066
9897
|
reason: `runtime "${requestedRuntime}" cannot express permission mode "${policyMode}"`,
|
|
9067
9898
|
retryable: false
|
|
9068
9899
|
};
|
|
9069
9900
|
}
|
|
9070
|
-
if (requiresMcpToolsets && !adapterSupportsMcpToolsets(
|
|
9901
|
+
if (requiresMcpToolsets && !adapterSupportsMcpToolsets(descriptor)) {
|
|
9071
9902
|
return {
|
|
9072
9903
|
ok: false,
|
|
9073
9904
|
reason: `runtime "${requestedRuntime}" cannot project required MCP toolsets`,
|
|
@@ -9082,15 +9913,16 @@ var TaskRunner = class {
|
|
|
9082
9913
|
retryable: true
|
|
9083
9914
|
};
|
|
9084
9915
|
}
|
|
9085
|
-
return { ok: true, adapter };
|
|
9916
|
+
return { ok: true, adapter, descriptor };
|
|
9086
9917
|
}
|
|
9087
|
-
const eligible = allowlist ? this.deps.adapters.filter((a) => allowlist.includes(a.id)) : this.deps.adapters;
|
|
9918
|
+
const eligible = allowlist ? this.deps.adapters.filter((a) => allowlist.includes(a.descriptor.id)) : this.deps.adapters;
|
|
9088
9919
|
const candidates = orderByPreference(eligible, this.deps.runtimePreference ?? DEFAULT_RUNTIME_PREFERENCE);
|
|
9089
9920
|
for (const adapter of candidates) {
|
|
9090
|
-
|
|
9091
|
-
if (
|
|
9921
|
+
const descriptor = freezeRuntimeAdapterDescriptor(adapter.descriptor);
|
|
9922
|
+
if (!adapterSupportsMode(descriptor, policyMode)) continue;
|
|
9923
|
+
if (requiresMcpToolsets && !adapterSupportsMcpToolsets(descriptor)) continue;
|
|
9092
9924
|
const detected = await adapter.detect();
|
|
9093
|
-
if (detected.present) return { ok: true, adapter };
|
|
9925
|
+
if (detected.present) return { ok: true, adapter, descriptor };
|
|
9094
9926
|
}
|
|
9095
9927
|
return {
|
|
9096
9928
|
ok: false,
|
|
@@ -9135,27 +9967,27 @@ async function detectRuntimes(adapters) {
|
|
|
9135
9967
|
const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: await adapter.detect() })));
|
|
9136
9968
|
const runtimes = [];
|
|
9137
9969
|
for (const { adapter, detected } of detections) {
|
|
9138
|
-
if (!detected.present || !isRuntimeId(adapter.id)) continue;
|
|
9139
|
-
const info = { id: adapter.id };
|
|
9970
|
+
if (!detected.present || !isRuntimeId(adapter.descriptor.id)) continue;
|
|
9971
|
+
const info = { id: adapter.descriptor.id };
|
|
9140
9972
|
if (detected.version !== void 0) info.version = detected.version;
|
|
9141
9973
|
if (detected.authPresent !== void 0) info.authPresent = detected.authPresent;
|
|
9142
|
-
info.capabilities = toRuntimeInfoCapabilities(adapter.capabilities
|
|
9974
|
+
info.capabilities = toRuntimeInfoCapabilities(adapter.descriptor.capabilities);
|
|
9143
9975
|
runtimes.push(info);
|
|
9144
9976
|
}
|
|
9145
9977
|
return runtimes;
|
|
9146
9978
|
}
|
|
9147
9979
|
function computeCapabilities(adapters) {
|
|
9148
9980
|
const flags = [];
|
|
9149
|
-
if (adapters.some((adapter) => adapter.capabilities
|
|
9981
|
+
if (adapters.some((adapter) => adapter.descriptor.capabilities.steer)) flags.push("steer");
|
|
9150
9982
|
flags.push("blob-upload");
|
|
9151
9983
|
flags.push("approval-targeting");
|
|
9152
9984
|
const selectionAdapters = adapters.filter(
|
|
9153
|
-
(adapter) => ALL_RUNTIME_IDS.includes(adapter.id)
|
|
9985
|
+
(adapter) => ALL_RUNTIME_IDS.includes(adapter.descriptor.id)
|
|
9154
9986
|
);
|
|
9155
|
-
if (selectionAdapters.length > 0 && selectionAdapters.every((adapter) => adapter.supportsDispatchSelection === true)) {
|
|
9987
|
+
if (selectionAdapters.length > 0 && selectionAdapters.every((adapter) => adapter.descriptor.supportsDispatchSelection === true)) {
|
|
9156
9988
|
flags.push("dispatch-selection");
|
|
9157
9989
|
}
|
|
9158
|
-
if (adapters.some((adapter) => adapter.capabilities
|
|
9990
|
+
if (adapters.some((adapter) => adapter.descriptor.capabilities.mcpToolsets === true)) {
|
|
9159
9991
|
flags.push("toolset-selection");
|
|
9160
9992
|
}
|
|
9161
9993
|
return flags;
|
|
@@ -9215,7 +10047,6 @@ function validatePiByokLauncherConfig(launcher) {
|
|
|
9215
10047
|
throw new Error("DaemonConfig.piByokLauncher.args must contain only non-empty single-line strings");
|
|
9216
10048
|
}
|
|
9217
10049
|
}
|
|
9218
|
-
var MAX_LOCAL_MCP_TOOLSETS = 64;
|
|
9219
10050
|
var MAX_LOCAL_MCP_SERVERS_PER_TOOLSET = 16;
|
|
9220
10051
|
var MAX_LOCAL_MCP_ARGS = 64;
|
|
9221
10052
|
var MAX_LOCAL_MCP_TOKEN_CHARS = 4096;
|
|
@@ -9228,8 +10059,10 @@ function resolveMcpToolsets(configured) {
|
|
|
9228
10059
|
throw new Error("DaemonConfig.mcpToolsets must be an object keyed by logical toolset id");
|
|
9229
10060
|
}
|
|
9230
10061
|
const toolsetEntries = Object.entries(configured);
|
|
9231
|
-
if (toolsetEntries.length >
|
|
9232
|
-
throw new Error(
|
|
10062
|
+
if (toolsetEntries.length > CONFIGURED_TOOLSETS_MAX_ITEMS) {
|
|
10063
|
+
throw new Error(
|
|
10064
|
+
`DaemonConfig.mcpToolsets may contain at most ${CONFIGURED_TOOLSETS_MAX_ITEMS} toolsets`
|
|
10065
|
+
);
|
|
9233
10066
|
}
|
|
9234
10067
|
const resolved = /* @__PURE__ */ new Map();
|
|
9235
10068
|
for (const [toolsetId, rawToolset] of toolsetEntries) {
|
|
@@ -9342,6 +10175,9 @@ function createDaemonWithAdapters(config, adapters, overrides = {}) {
|
|
|
9342
10175
|
}
|
|
9343
10176
|
function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProbe) {
|
|
9344
10177
|
const mcpToolsets = resolveMcpToolsets(config.mcpToolsets);
|
|
10178
|
+
const configuredToolsets = Object.freeze(
|
|
10179
|
+
[...mcpToolsets?.keys() ?? []].sort()
|
|
10180
|
+
);
|
|
9345
10181
|
if (config.piByokLauncher !== void 0) {
|
|
9346
10182
|
validatePiByokLauncherConfig(config.piByokLauncher);
|
|
9347
10183
|
}
|
|
@@ -9648,12 +10484,13 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
9648
10484
|
dirty: event.observation ? { staged: event.observation.staged, unstaged: event.observation.unstaged, untracked: event.observation.untracked, conflicted: event.observation.conflicted } : void 0,
|
|
9649
10485
|
errorCategory: event.errorCategory
|
|
9650
10486
|
}),
|
|
10487
|
+
onRuntimeDisposalFailure: (event) => observer.noteRuntimeDisposalFailure(event),
|
|
9651
10488
|
// M4 Phase 3: the SAME `ApprovalRegistry` instance the control
|
|
9652
10489
|
// socket's own `approvals.list`/`approvals.resolve` methods already
|
|
9653
10490
|
// share (see that field's own construction above) — `TaskRunner
|
|
9654
10491
|
// .requestApproval` registers into it directly, so a decision arriving
|
|
9655
10492
|
// via either the server wire or the local CLI resolves the identical
|
|
9656
|
-
// entry. `storeDir`/`productId` let
|
|
10493
|
+
// entry. `storeDir`/`productId` let the prepared operation approval channel
|
|
9657
10494
|
// (populated per-task by `TaskRunner`) tell an out-of-process helper
|
|
9658
10495
|
// (`bin/byok-approval-mcp.ts`) exactly which control socket to dial.
|
|
9659
10496
|
approvalRegistry,
|
|
@@ -9705,6 +10542,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
9705
10542
|
productId: config.productId,
|
|
9706
10543
|
capabilities,
|
|
9707
10544
|
runtimes,
|
|
10545
|
+
configuredToolsets,
|
|
9708
10546
|
auth,
|
|
9709
10547
|
cursorStore,
|
|
9710
10548
|
// Finding F3: return (not void-and-forget) so ConnectionManager can
|
|
@@ -9791,6 +10629,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
9791
10629
|
presencePublisher ??= new PresencePublisher({
|
|
9792
10630
|
serverUrl: config.serverUrl,
|
|
9793
10631
|
auth,
|
|
10632
|
+
configuredToolsets,
|
|
9794
10633
|
...presenceCadence,
|
|
9795
10634
|
onDegraded: (reason) => console.warn(`[byok/client] ${reason}`)
|
|
9796
10635
|
});
|
|
@@ -9884,8 +10723,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
9884
10723
|
await attempt(() => auth.stop(), true);
|
|
9885
10724
|
connectionState = "closed";
|
|
9886
10725
|
await attempt(async () => {
|
|
9887
|
-
await controlServerHandle?.
|
|
9888
|
-
controlServerHandle = void 0;
|
|
10726
|
+
await controlServerHandle?.stopServing();
|
|
9889
10727
|
}, true);
|
|
9890
10728
|
if (mutationBarrierComplete && daemonOwnerLease) {
|
|
9891
10729
|
await attempt(() => operationalHealth.markCleanStop(), false);
|
|
@@ -9894,6 +10732,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
9894
10732
|
daemonOwnerLease = void 0;
|
|
9895
10733
|
}, false);
|
|
9896
10734
|
}
|
|
10735
|
+
if (daemonOwnerLease === void 0) {
|
|
10736
|
+
await attempt(async () => {
|
|
10737
|
+
await controlServerHandle?.close();
|
|
10738
|
+
controlServerHandle = void 0;
|
|
10739
|
+
}, false);
|
|
10740
|
+
}
|
|
9897
10741
|
if (errors.length === 1) throw errors[0];
|
|
9898
10742
|
if (errors.length > 1) {
|
|
9899
10743
|
throw new AggregateError(errors, "daemon shutdown completed with errors");
|
|
@@ -9973,7 +10817,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
9973
10817
|
deviceId: auth.deviceId,
|
|
9974
10818
|
transport: connectionState,
|
|
9975
10819
|
activeTasks,
|
|
9976
|
-
runtimeIds: adapters.map((adapter) => adapter.id),
|
|
10820
|
+
runtimeIds: adapters.map((adapter) => adapter.descriptor.id),
|
|
9977
10821
|
// M4 Phase 4 (part B.3): queue watermarks come from TaskRunner's own
|
|
9978
10822
|
// active-task map (distinct from `observer.tasks()` above, which is
|
|
9979
10823
|
// derived from the envelope feed) — see `TaskRunner.getQueueWatermarks`'s
|
|
@@ -10181,7 +11025,7 @@ function createDaemon(config) {
|
|
|
10181
11025
|
return createDaemonWithAdapters(config, buildDefaultAdapters(config));
|
|
10182
11026
|
}
|
|
10183
11027
|
var MAX_CONTROL_TOKEN_BYTES = 256;
|
|
10184
|
-
function
|
|
11028
|
+
function errorMessage5(err) {
|
|
10185
11029
|
return err instanceof Error ? err.message : String(err);
|
|
10186
11030
|
}
|
|
10187
11031
|
function sameFileState3(left, right) {
|
|
@@ -10234,7 +11078,7 @@ async function connectControlClient(opts) {
|
|
|
10234
11078
|
}
|
|
10235
11079
|
token = read;
|
|
10236
11080
|
} catch (err) {
|
|
10237
|
-
return { ok: false, reason: `could not read the control token: ${
|
|
11081
|
+
return { ok: false, reason: `could not read the control token: ${errorMessage5(err)}` };
|
|
10238
11082
|
}
|
|
10239
11083
|
if (!token) {
|
|
10240
11084
|
return { ok: false, reason: "control token file is empty" };
|
|
@@ -10244,7 +11088,7 @@ async function connectControlClient(opts) {
|
|
|
10244
11088
|
const client = await connectAndHandshake(endpoint, token, opts);
|
|
10245
11089
|
return { ok: true, client };
|
|
10246
11090
|
} catch (err) {
|
|
10247
|
-
return { ok: false, reason: `daemon control socket not reachable: ${
|
|
11091
|
+
return { ok: false, reason: `daemon control socket not reachable: ${errorMessage5(err)}` };
|
|
10248
11092
|
}
|
|
10249
11093
|
}
|
|
10250
11094
|
function connectAndHandshake(endpoint, token, opts) {
|
|
@@ -10863,19 +11707,7 @@ function quote(text) {
|
|
|
10863
11707
|
function redactedByteCountPlaceholder(text) {
|
|
10864
11708
|
return `[redacted: ${Buffer.byteLength(text, "utf8")} bytes]`;
|
|
10865
11709
|
}
|
|
10866
|
-
var STABLE_GIT_ERROR_CATEGORIES =
|
|
10867
|
-
"git-unavailable",
|
|
10868
|
-
"git-timeout",
|
|
10869
|
-
"git-output-limit",
|
|
10870
|
-
"git-command-failed",
|
|
10871
|
-
"workspace-root-invalid",
|
|
10872
|
-
"workspace-root-conflict",
|
|
10873
|
-
"workspace-not-owned",
|
|
10874
|
-
"repository-root-mismatch",
|
|
10875
|
-
"repository-invalid",
|
|
10876
|
-
"lease-busy",
|
|
10877
|
-
"ledger-invalid"
|
|
10878
|
-
]);
|
|
11710
|
+
var STABLE_GIT_ERROR_CATEGORIES = new Set(GIT_ERROR_CATEGORIES);
|
|
10879
11711
|
function stableGitErrorCategory(value) {
|
|
10880
11712
|
return value !== void 0 && STABLE_GIT_ERROR_CATEGORIES.has(value) ? value : void 0;
|
|
10881
11713
|
}
|
|
@@ -10941,6 +11773,8 @@ function formatDaemonEventLine(event, options = {}) {
|
|
|
10941
11773
|
return `${prefix} shutdown-complete reason=${quote(event.reason)}${event.undeliveredOutboxCount !== void 0 ? ` undeliveredOutboxCount=${event.undeliveredOutboxCount}` : ""}`;
|
|
10942
11774
|
case "stale-approval-decision":
|
|
10943
11775
|
return `${prefix} stale-approval-decision taskId=${event.taskId} decision=${event.decision}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
|
|
11776
|
+
case "runtime-disposal-failed":
|
|
11777
|
+
return `${prefix} runtime-disposal-failed taskId=${event.taskId} runtime=${event.runtimeId} stage=${event.stage} reason=${quote(event.reason)}`;
|
|
10944
11778
|
case "git-workspace": {
|
|
10945
11779
|
const parts = [
|
|
10946
11780
|
`${prefix} git-workspace taskId=${event.taskId}`,
|
|
@@ -11219,8 +12053,8 @@ async function probeRuntimes(adapters, options = {}) {
|
|
|
11219
12053
|
let resume = false;
|
|
11220
12054
|
let permissionModes = [];
|
|
11221
12055
|
try {
|
|
11222
|
-
id = boundedSingleLine(adapter.id, MAX_RUNTIME_ID_CHARS);
|
|
11223
|
-
const caps = adapter.capabilities
|
|
12056
|
+
id = boundedSingleLine(adapter.descriptor.id, MAX_RUNTIME_ID_CHARS);
|
|
12057
|
+
const caps = adapter.descriptor.capabilities;
|
|
11224
12058
|
steer = caps.steer === true;
|
|
11225
12059
|
resume = caps.resume === true;
|
|
11226
12060
|
permissionModes = caps.permissionModes.slice(0, MAX_PERMISSION_MODES).map((mode) => boundedSingleLine(mode, MAX_PERMISSION_MODE_CHARS));
|
|
@@ -12085,19 +12919,7 @@ function valueByteSize(value) {
|
|
|
12085
12919
|
function placeholderFor(size) {
|
|
12086
12920
|
return size === void 0 ? "[redacted]" : `[redacted: ${size} bytes]`;
|
|
12087
12921
|
}
|
|
12088
|
-
var STABLE_GIT_ERROR_CATEGORIES2 =
|
|
12089
|
-
"git-unavailable",
|
|
12090
|
-
"git-timeout",
|
|
12091
|
-
"git-output-limit",
|
|
12092
|
-
"git-command-failed",
|
|
12093
|
-
"workspace-root-invalid",
|
|
12094
|
-
"workspace-root-conflict",
|
|
12095
|
-
"workspace-not-owned",
|
|
12096
|
-
"repository-root-mismatch",
|
|
12097
|
-
"repository-invalid",
|
|
12098
|
-
"lease-busy",
|
|
12099
|
-
"ledger-invalid"
|
|
12100
|
-
]);
|
|
12922
|
+
var STABLE_GIT_ERROR_CATEGORIES2 = new Set(GIT_ERROR_CATEGORIES);
|
|
12101
12923
|
function stableGitErrorCategory2(value) {
|
|
12102
12924
|
return typeof value === "string" && STABLE_GIT_ERROR_CATEGORIES2.has(value) ? value : void 0;
|
|
12103
12925
|
}
|
|
@@ -12196,6 +13018,8 @@ function redactForAudit(event) {
|
|
|
12196
13018
|
return { ...base, reason: event.reason, undeliveredOutboxCount: event.undeliveredOutboxCount };
|
|
12197
13019
|
case "stale-approval-decision":
|
|
12198
13020
|
return { ...base, taskId: event.taskId, decision: event.decision, reasonSize: byteSize(event.reason) };
|
|
13021
|
+
case "runtime-disposal-failed":
|
|
13022
|
+
return { ...base, taskId: event.taskId, runtimeId: event.runtimeId, stage: event.stage, reason: event.reason };
|
|
12199
13023
|
case "device-assertion":
|
|
12200
13024
|
return event.result === "issued" ? {
|
|
12201
13025
|
...base,
|
|
@@ -12344,6 +13168,15 @@ function reconstructDaemonEvent(raw) {
|
|
|
12344
13168
|
reason: reasonSize === void 0 ? void 0 : placeholderFor(reasonSize)
|
|
12345
13169
|
};
|
|
12346
13170
|
}
|
|
13171
|
+
case "runtime-disposal-failed":
|
|
13172
|
+
return {
|
|
13173
|
+
kind: "runtime-disposal-failed",
|
|
13174
|
+
ts,
|
|
13175
|
+
taskId: str(raw.taskId),
|
|
13176
|
+
runtimeId: str(raw.runtimeId),
|
|
13177
|
+
stage: str(raw.stage),
|
|
13178
|
+
reason: str(raw.reason)
|
|
13179
|
+
};
|
|
12347
13180
|
case "device-assertion": {
|
|
12348
13181
|
if (raw.result === "issued") {
|
|
12349
13182
|
return {
|
|
@@ -12616,20 +13449,8 @@ async function runStartCommand(config, deps) {
|
|
|
12616
13449
|
throw err;
|
|
12617
13450
|
}
|
|
12618
13451
|
}
|
|
12619
|
-
var STABLE_GIT_PHASES =
|
|
12620
|
-
var STABLE_GIT_ERROR_CATEGORIES3 =
|
|
12621
|
-
"git-unavailable",
|
|
12622
|
-
"git-timeout",
|
|
12623
|
-
"git-output-limit",
|
|
12624
|
-
"git-command-failed",
|
|
12625
|
-
"workspace-root-invalid",
|
|
12626
|
-
"workspace-root-conflict",
|
|
12627
|
-
"workspace-not-owned",
|
|
12628
|
-
"repository-root-mismatch",
|
|
12629
|
-
"repository-invalid",
|
|
12630
|
-
"lease-busy",
|
|
12631
|
-
"ledger-invalid"
|
|
12632
|
-
]);
|
|
13452
|
+
var STABLE_GIT_PHASES = new Set(GIT_WORKSPACE_PHASES);
|
|
13453
|
+
var STABLE_GIT_ERROR_CATEGORIES3 = new Set(GIT_ERROR_CATEGORIES);
|
|
12633
13454
|
function nonNegativeSafeInteger(value) {
|
|
12634
13455
|
return value !== void 0 && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
12635
13456
|
}
|
|
@@ -13128,19 +13949,7 @@ async function runUnpairCommand(daemon, deps = {}) {
|
|
|
13128
13949
|
}
|
|
13129
13950
|
|
|
13130
13951
|
// src/bin/commands/workspaces.ts
|
|
13131
|
-
var STABLE_ERROR_CATEGORIES =
|
|
13132
|
-
"git-unavailable",
|
|
13133
|
-
"git-timeout",
|
|
13134
|
-
"git-output-limit",
|
|
13135
|
-
"git-command-failed",
|
|
13136
|
-
"workspace-root-invalid",
|
|
13137
|
-
"workspace-root-conflict",
|
|
13138
|
-
"workspace-not-owned",
|
|
13139
|
-
"repository-root-mismatch",
|
|
13140
|
-
"repository-invalid",
|
|
13141
|
-
"lease-busy",
|
|
13142
|
-
"ledger-invalid"
|
|
13143
|
-
]);
|
|
13952
|
+
var STABLE_ERROR_CATEGORIES = new Set(GIT_ERROR_CATEGORIES);
|
|
13144
13953
|
function abbreviateCommit(value) {
|
|
13145
13954
|
if (!value) return "-";
|
|
13146
13955
|
return value.length > 8 ? value.slice(0, 8) : value;
|