codexteer 0.18.2 → 0.19.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/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/docs/distribution.md +2 -0
- package/package.json +1 -1
- package/src/app-server.mjs +4 -4
- package/src/checkpoint.mjs +10 -10
- package/src/cli.mjs +76 -68
- package/src/cloud-cli.mjs +22 -8
- package/src/connection-error.mjs +7 -0
- package/src/desktop-cloud-id.mjs +9 -1
- package/src/desktop-cloud-project.mjs +37 -4
- package/src/desktop-cloud.mjs +10 -2
- package/src/desktop.mjs +9 -9
- package/src/directive.mjs +4 -4
- package/src/evidence.mjs +3 -3
- package/src/findings.mjs +6 -3
- package/src/help-cloud-remote.mjs +399 -0
- package/src/help-errors.mjs +10 -0
- package/src/help-intervention.mjs +492 -0
- package/src/help-local.mjs +508 -0
- package/src/help-observation.mjs +200 -0
- package/src/help-supervision.mjs +253 -0
- package/src/help.mjs +58 -304
- package/src/journal.mjs +22 -7
- package/src/launcher.mjs +6 -6
- package/src/monitor.mjs +2 -2
- package/src/notify.mjs +3 -3
- package/src/observe.mjs +4 -4
- package/src/paged-read.mjs +2 -2
- package/src/resource.mjs +6 -6
- package/src/runner.mjs +5 -5
- package/src/store.mjs +2 -2
- package/src/supervise.mjs +1 -1
- package/src/task-id.mjs +5 -4
- package/src/thread-id.mjs +5 -5
- package/src/wrapper.mjs +2 -2
package/src/cli.mjs
CHANGED
|
@@ -39,6 +39,7 @@ export function fail(error, json) {
|
|
|
39
39
|
if (json) console.log(JSON.stringify({ ok: false, ...(error.watch ? { command: "watch", data: error.watch } : {}), error: {
|
|
40
40
|
message, code: error.code, ...hostFields(), delivery_status: error.delivery_status,
|
|
41
41
|
operation: error.operation, capability: error.capability,
|
|
42
|
+
expected_connection: error.expected_connection, connection: error.connection,
|
|
42
43
|
...((error.execution_environment === "cloud" || (typeof error.code === "string" && error.code.startsWith("CLOUD_"))) ? {
|
|
43
44
|
execution_environment: error.execution_environment, task_id: error.task_id, url: error.url,
|
|
44
45
|
submission_status: error.submission_status, uncertain: error.uncertain,
|
|
@@ -65,7 +66,7 @@ export function fail(error, json) {
|
|
|
65
66
|
if (error.execution_environment === "cloud" || (typeof error.code === "string" && error.code.startsWith("DESKTOP_CLOUD_"))) {
|
|
66
67
|
// Recovery metadata must survive an uncertain submission or a stale
|
|
67
68
|
// on-disk receipt. Never print the child error, prompt, or diagnostics.
|
|
68
|
-
for (const key of ["creation_status", "submission_status", "delivery_status", "attempt_id", "receipt_path", "existing_attempt_id", "existing_receipt_path", "task_id", "thread_id", "url"]) {
|
|
69
|
+
for (const key of ["creation_status", "submission_status", "delivery_status", "attempt_id", "receipt_path", "existing_message_id", "existing_attempt_id", "existing_receipt_path", "task_id", "thread_id", "url"]) {
|
|
69
70
|
if (typeof error[key] === "string") console.error(`${key}: ${JSON.stringify(error[key])}`);
|
|
70
71
|
}
|
|
71
72
|
if (error.receipt_update_required) console.error("receipt_update_required: true");
|
|
@@ -76,11 +77,13 @@ export function fail(error, json) {
|
|
|
76
77
|
process.exitCode = 1;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
const usage = message => Object.assign(new Error(message), { code: "OPTIONS_INVALID" });
|
|
81
|
+
|
|
79
82
|
function takeOption(args, name, defaultValue) {
|
|
80
83
|
const index = args.indexOf(name);
|
|
81
84
|
if (index === -1 || (args.includes("--") && index > args.indexOf("--"))) return defaultValue;
|
|
82
85
|
const value = args[index + 1];
|
|
83
|
-
if (value == null || value.startsWith("--")) throw
|
|
86
|
+
if (value == null || value.startsWith("--")) throw usage(`${name} requires a value.`);
|
|
84
87
|
args.splice(index, 2);
|
|
85
88
|
return value;
|
|
86
89
|
}
|
|
@@ -123,7 +126,7 @@ function readMessage(parts) {
|
|
|
123
126
|
|
|
124
127
|
function backendOption(args, fallback) {
|
|
125
128
|
const backend = takeOption(args, "--backend", fallback);
|
|
126
|
-
if (backend != null && !["app-server", "ui"].includes(backend)) throw
|
|
129
|
+
if (backend != null && !["app-server", "ui"].includes(backend)) throw usage("--backend must be app-server or ui.");
|
|
127
130
|
return backend;
|
|
128
131
|
}
|
|
129
132
|
|
|
@@ -161,7 +164,7 @@ export async function main(argv) {
|
|
|
161
164
|
try {
|
|
162
165
|
const transport = validateRemoteTransport(takeOption(args, "--remote-transport", "native"));
|
|
163
166
|
const explicitHost = takeOption(args, "--host", undefined);
|
|
164
|
-
if (args.slice(0, args.includes("--") ? args.indexOf("--") : args.length).includes("--host")) throw
|
|
167
|
+
if (args.slice(0, args.includes("--") ? args.indexOf("--") : args.length).includes("--host")) throw usage("Specify --host only once.");
|
|
165
168
|
if (explicitHost !== undefined) validateHostId(explicitHost);
|
|
166
169
|
const probe = [...args];
|
|
167
170
|
takeFlag(probe, "--json");
|
|
@@ -196,7 +199,7 @@ async function mainOnTarget(argv) {
|
|
|
196
199
|
const connectionDependencies = observationConnection(connection);
|
|
197
200
|
const requiredNode = takeOption(args, "--require-node-version", undefined);
|
|
198
201
|
if (requiredNode !== undefined) {
|
|
199
|
-
if (!/^v\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(requiredNode)) throw
|
|
202
|
+
if (!/^v\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(requiredNode)) throw usage("--require-node-version requires a Node version such as v25.1.0.");
|
|
200
203
|
if (requiredNode !== process.version) throw Object.assign(new Error(`This supervision command requires Node ${requiredNode}, but is running ${process.version}. Regenerate the supervisor prompt with the intended Node runtime.`), { code: "NODE_VERSION_MISMATCH" });
|
|
201
204
|
}
|
|
202
205
|
if (takeFlag(args, "--version")) {
|
|
@@ -216,6 +219,7 @@ async function mainOnTarget(argv) {
|
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
let command = args.shift();
|
|
222
|
+
if (command === "diff") throw usage("diff reads a Web Cloud task's recorded patch and is not available for local or remote Desktop tasks. Use read --include-output to inspect file changes.");
|
|
219
223
|
const control = isControlTransport();
|
|
220
224
|
if (targetHost() !== "local" && ["desktop", "debug-ui"].includes(command)) throw Object.assign(new Error("Desktop process/update and Accessibility diagnostics cannot target the remote host. No local operation was run."), { code: "REMOTE_HOST_OPERATION_UNSUPPORTED" });
|
|
221
225
|
if (targetHost() !== "local" && !["remote", "projects", "read", "status", "watch", "doctor", "supervise", "findings", "history", "instructions", "thread", "open", ...(control ? ["send"] : []), ...(remoteTransport() === "paired" ? ["threads", "resource", "checkpoint", "orchestrate"] : [])].includes(command)
|
|
@@ -229,14 +233,14 @@ async function mainOnTarget(argv) {
|
|
|
229
233
|
if (command === "remote") {
|
|
230
234
|
if (supervisor) throw Object.assign(new Error("A bound supervisor cannot enroll, pair or manage account credentials."), { code: "SUPERVISOR_MISMATCH" });
|
|
231
235
|
const action = args.shift(), codeStdin = takeFlag(args, "--code-stdin"), provider = takeOption(args, "--key-provider", undefined);
|
|
232
|
-
if (provider !== undefined && action !== "prepare") throw
|
|
233
|
-
if (!["prepare", "recover-key", "enroll", "status", "identity", "pair", "verify"].includes(action) || args.length || (codeStdin && action !== "pair") || (action === "pair" && !codeStdin)) throw
|
|
234
|
-
if (["prepare", "enroll"].includes(action) && targetHost() !== "local") throw
|
|
236
|
+
if (provider !== undefined && action !== "prepare") throw usage("--key-provider is only available for remote prepare.");
|
|
237
|
+
if (!["prepare", "recover-key", "enroll", "status", "identity", "pair", "verify"].includes(action) || args.length || (codeStdin && action !== "pair") || (action === "pair" && !codeStdin)) throw usage("Expected: remote prepare|recover-key|enroll|status|identity|verify, or --host HOST remote pair --code-stdin.");
|
|
238
|
+
if (["prepare", "enroll"].includes(action) && targetHost() !== "local") throw usage("remote prepare/enroll registers this local controller; omit --host.");
|
|
235
239
|
// Code input is never an argv option or a chat message.
|
|
236
|
-
if (action === "pair" && !/^remote-control:env_[A-Za-z0-9_-]+$/.test(targetHost())) throw new Error("remote pair requires --host remote-control:env_...");
|
|
240
|
+
if (action === "pair" && !/^remote-control:env_[A-Za-z0-9_-]+$/.test(targetHost())) throw Object.assign(new Error("remote pair requires --host remote-control:env_..."), { code: "REMOTE_PAIR_INPUT_INVALID" });
|
|
237
241
|
if (action === "pair" && process.stdin.isTTY) console.error("codexteer: Enter the selected host's fresh pairing code (hidden), then press Enter.");
|
|
238
242
|
const code = action === "pair" ? await readPairingCode() : undefined;
|
|
239
|
-
if (code && code.length > 128) throw new Error("Invalid pairing code input.");
|
|
243
|
+
if (code && code.length > 128) throw Object.assign(new Error("Invalid pairing code input."), { code: "REMOTE_PAIR_INPUT_INVALID" });
|
|
240
244
|
const data = await withDeviceAccount(context => action === "prepare" ? prepareDevice(context, { ...(provider ? { provider } : {}) }) : action === "recover-key" ? recoverDeviceKey(context) : action === "enroll" ? enrollDevice(context, { progress: text => console.error(`codexteer: ${text}`) })
|
|
241
245
|
: action === "pair" ? pairDevice(context, targetHost(), code)
|
|
242
246
|
: action === "verify" ? verifyDevice(context) : action === "identity" ? deviceIdentityInfo(context) : readDevice(context.home, context.identity).then(deviceStatus));
|
|
@@ -244,12 +248,13 @@ async function mainOnTarget(argv) {
|
|
|
244
248
|
}
|
|
245
249
|
if (command === "supervise" && ["register", "status", "list", "pause", "resume", "stop"].includes(args[0])) {
|
|
246
250
|
const action = args.shift(), owner = action === "register" ? takeOption(args, "--owner", "external") : undefined;
|
|
247
|
-
if (args.length !== (action === "list" ? 0 : 1)) throw
|
|
251
|
+
if (args.length !== (action === "list" ? 0 : 1)) throw usage(`Expected: supervise ${action}${action === "list" ? "" : " <THREAD>"}.`);
|
|
248
252
|
let data;
|
|
249
253
|
if (action === "register") {
|
|
250
|
-
if (!supervisor) throw
|
|
254
|
+
if (!supervisor) throw usage("supervise register requires the --supervisor ID from a generated prompt.");
|
|
251
255
|
data = await registerSupervisor(args[0], supervisor, { owner, connection });
|
|
252
256
|
} else {
|
|
257
|
+
if (supervisor && action === "list") throw Object.assign(new Error("A bound supervisor cannot list other supervisor sessions. Use supervise status <THREAD> for its own task, or run supervise list from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
253
258
|
if (supervisor && action !== "status") throw Object.assign(new Error("Supervisors cannot change their own control state. Run this control command from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
254
259
|
if (supervisor) {
|
|
255
260
|
const record = await getSupervisor(args[0]);
|
|
@@ -280,7 +285,7 @@ async function mainOnTarget(argv) {
|
|
|
280
285
|
else if (action === "list") options = { all: takeFlag(args, "--all"), limit: Number(takeOption(args, "--limit", "50")) };
|
|
281
286
|
const handlers = { create: createFinding, show: findingStatus, list: listFindings, resolve: resolveFinding, dismiss: dismissFinding, reopen: reopenFinding, evaluate: evaluateFinding, stats: findingStats };
|
|
282
287
|
const single = ["create", "list", "stats"].includes(action);
|
|
283
|
-
if (!Object.hasOwn(handlers, action) || args.length !== (single ? 1 : 2) || args.some(arg => arg.startsWith("--"))) throw
|
|
288
|
+
if (!Object.hasOwn(handlers, action) || args.length !== (single ? 1 : 2) || args.some(arg => arg.startsWith("--"))) throw usage("See codexteer help findings.");
|
|
284
289
|
if (supervisor) await assertSupervisor(args[0], supervisor, { connection });
|
|
285
290
|
data = single ? await handlers[action](args[0], options) : await handlers[action](args[0], args[1], options);
|
|
286
291
|
success(`findings.${action}`, data, json);
|
|
@@ -291,7 +296,7 @@ async function mainOnTarget(argv) {
|
|
|
291
296
|
if (supervisor) throw Object.assign(new Error("Managed supervision is bound to its existing task and cannot orchestrate a project."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
292
297
|
const promptOnly = args[0] === "prompt";
|
|
293
298
|
if (promptOnly) args.shift();
|
|
294
|
-
else if (json) throw
|
|
299
|
+
else if (json) throw usage("orchestrate inherits agent output and does not support --json. Use help orchestrate --json or orchestrate prompt --project ID|NAME <GOAL> --json.");
|
|
295
300
|
const separator = promptOnly ? -1 : args.indexOf("--");
|
|
296
301
|
const ownArgs = separator < 0 ? args : args.slice(0, separator);
|
|
297
302
|
const agentArgs = separator < 0 ? [] : args.slice(separator + 1);
|
|
@@ -299,8 +304,8 @@ async function mainOnTarget(argv) {
|
|
|
299
304
|
const agent = takeOption(ownArgs, "--agent", promptOnly ? undefined : "claude");
|
|
300
305
|
const usePathCli = takeFlag(ownArgs, "--use-path-cli");
|
|
301
306
|
if (promptOnly && ownArgs[0] === "--") ownArgs.shift();
|
|
302
|
-
else if (ownArgs.some(arg => arg.startsWith("--"))) throw
|
|
303
|
-
if (ownArgs.length !== 1) throw
|
|
307
|
+
else if (ownArgs.some(arg => arg.startsWith("--"))) throw usage("Unknown orchestrate option. See help orchestrate; agent arguments belong after --.");
|
|
308
|
+
if (ownArgs.length !== 1) throw usage("Expected: orchestrate [prompt] --project <ID-or-exact-name> <GOAL>. Quote the goal as one argument, or use - for stdin.");
|
|
304
309
|
const goal = ownArgs[0] === "-" ? readFileSync(0, "utf8") : ownArgs[0];
|
|
305
310
|
if (promptOnly) {
|
|
306
311
|
const data = await prepareOrchestrationPrompt(project, goal, { agent, connection, usePathCli });
|
|
@@ -313,47 +318,49 @@ async function mainOnTarget(argv) {
|
|
|
313
318
|
args.shift();
|
|
314
319
|
const agent = takeOption(args, "--agent", "claude");
|
|
315
320
|
const usePathCli = takeFlag(args, "--use-path-cli");
|
|
316
|
-
if (args.length < 1 || args.length > 2 || args.some(arg => arg.startsWith("--"))) throw
|
|
317
|
-
if (supervisor) throw new Error("A supervisor cannot generate a replacement session.");
|
|
321
|
+
if (args.length < 1 || args.length > 2 || args.some(arg => arg.startsWith("--"))) throw usage("Expected: supervise prompt <THREAD> [MESSAGE]. See codexteer help supervise prompt.");
|
|
322
|
+
if (supervisor) throw Object.assign(new Error("A supervisor cannot generate a replacement session."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
318
323
|
const data = await prepareSupervisorPrompt(args[0], args[1], { agent, connection, usePathCli });
|
|
319
324
|
success("supervise.prompt", data, json);
|
|
320
325
|
if (!json) console.log(data.prompt);
|
|
321
326
|
return;
|
|
322
327
|
}
|
|
323
328
|
if (command === "supervise") {
|
|
324
|
-
if (supervisor) throw new Error("A supervisor cannot start a replacement session.");
|
|
325
|
-
if (json) throw
|
|
329
|
+
if (supervisor) throw Object.assign(new Error("A supervisor cannot start a replacement session."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
330
|
+
if (json) throw usage("supervise inherits agent output and does not support --json. Use help supervise --json or supervise prompt <THREAD> --json.");
|
|
326
331
|
const separator = args.indexOf("--");
|
|
327
332
|
const ownArgs = separator < 0 ? args : args.slice(0, separator);
|
|
328
333
|
const agentArgs = separator < 0 ? [] : args.slice(separator + 1);
|
|
329
334
|
const agent = takeOption(ownArgs, "--agent", "claude");
|
|
330
335
|
const usePathCli = takeFlag(ownArgs, "--use-path-cli");
|
|
331
|
-
if (ownArgs.length < 1 || ownArgs.length > 2 || ownArgs.some(arg => arg.startsWith("--"))) throw
|
|
336
|
+
if (ownArgs.length < 1 || ownArgs.length > 2 || ownArgs.some(arg => arg.startsWith("--"))) throw usage("Expected: supervise <THREAD> [MESSAGE] [--agent claude|codex] [--use-path-cli] [-- <AGENT-ARGS...>].");
|
|
332
337
|
process.exitCode = await superviseAgent(ownArgs[0], { agent, agentArgs, policy: ownArgs[1], connection, usePathCli });
|
|
333
338
|
return;
|
|
334
339
|
}
|
|
335
340
|
if (command === "resource") {
|
|
336
|
-
const action = args.shift(); let data;
|
|
341
|
+
const threadId = takeOption(args, "--thread", undefined), action = args.shift(); let data;
|
|
342
|
+
if (supervisor && threadId === undefined) throw Object.assign(new Error("A supervisor resource command requires its --thread target."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
343
|
+
// Like read/watch, inspecting leases and giving one up stay available while intervention is paused.
|
|
344
|
+
if (supervisor) await assertSupervisor(threadId, supervisor, { connection, sending: ["acquire", "renew", "run"].includes(action) });
|
|
337
345
|
if (["acquire", "run"].includes(action)) {
|
|
338
|
-
const options = { owner: takeOption(args, "--owner", undefined), ttlMs: Number(takeOption(args, "--ttl-ms", "600000")), reason: takeOption(args, "--reason", ""), condition: takeOption(args, "--condition", ""), threadId
|
|
346
|
+
const options = { owner: takeOption(args, "--owner", undefined), ttlMs: Number(takeOption(args, "--ttl-ms", "600000")), reason: takeOption(args, "--reason", ""), condition: takeOption(args, "--condition", ""), threadId, cwd: takeOption(args, "--cwd", undefined) };
|
|
339
347
|
if (action === "run") {
|
|
340
348
|
const timeoutMs = Number(takeOption(args, "--timeout-ms", "0")), includeOutput = takeFlag(args, "--include-output"), execBackend = takeOption(args, "--exec-backend", "app-server"), remoteShell = takeOption(args, "--shell", undefined);
|
|
341
|
-
if (
|
|
342
|
-
if (args[1] !== "--" || args.length < 3) throw new Error("Expected: resource run <NAME> --owner NAME -- <COMMAND> [ARGS...]");
|
|
349
|
+
if (args[1] !== "--" || args.length < 3) throw usage("Expected: resource run <NAME> --owner NAME -- <COMMAND> [ARGS...]");
|
|
343
350
|
data = await runWithResource(args[0], args.slice(2), { ...options, timeoutMs, execBackend, remoteShell, supervisor, connection });
|
|
344
351
|
if (!includeOutput) delete data.output_tail;
|
|
345
352
|
if (!data.valid) process.exitCode = 1;
|
|
346
353
|
} else {
|
|
347
|
-
if (args.length !== 1) throw
|
|
354
|
+
if (args.length !== 1) throw usage("Expected: resource acquire <NAME> --owner NAME.");
|
|
348
355
|
data = await acquireResource(args[0], options);
|
|
349
356
|
}
|
|
350
357
|
} else if (["renew", "release"].includes(action)) {
|
|
351
358
|
const token = takeOption(args, "--token", undefined), ttlMs = action === "renew" ? Number(takeOption(args, "--ttl-ms", "600000")) : undefined;
|
|
352
|
-
if (args.length !== 1) throw
|
|
359
|
+
if (args.length !== 1) throw usage(`Expected: resource ${action} <NAME> --token TOKEN.`);
|
|
353
360
|
data = action === "renew" ? await renewResource(args[0], token, { ttlMs }) : await releaseResource(args[0], token);
|
|
354
361
|
} else if (action === "status" && args.length === 1) data = await resourceStatus(args[0]);
|
|
355
362
|
else if (action === "list" && args.length === 0) data = await listResources();
|
|
356
|
-
else throw
|
|
363
|
+
else throw usage("See codexteer help resource.");
|
|
357
364
|
success(`resource.${action}`, data, json);
|
|
358
365
|
if (!json) console.log(JSON.stringify(data, null, 2));
|
|
359
366
|
return;
|
|
@@ -364,27 +371,27 @@ async function mainOnTarget(argv) {
|
|
|
364
371
|
let data;
|
|
365
372
|
if (action === "capture") {
|
|
366
373
|
const paths = takeOptions(args, "--path"), excludes = takeOptions(args, "--exclude");
|
|
367
|
-
if (args.length !== 2) throw
|
|
374
|
+
if (args.length !== 2) throw usage("Expected: checkpoint capture <THREAD> <NAME> --path PATH ...");
|
|
368
375
|
data = await createCheckpoint(args[0], args[1], { paths, excludes });
|
|
369
376
|
} else if (action === "run") {
|
|
370
377
|
const timeoutMs = Number(takeOption(args, "--timeout-ms", "0")), execBackend = takeOption(args, "--exec-backend", "app-server"), remoteShell = takeOption(args, "--shell", undefined);
|
|
371
378
|
if (supervisor && execBackend === "thread-shell") await assertSupervisor(args[0], supervisor, { connection, sending: true });
|
|
372
|
-
if (args[2] !== "--" || args.length < 4) throw
|
|
379
|
+
if (args[2] !== "--" || args.length < 4) throw usage("Expected: checkpoint run <THREAD> <ID> -- <COMMAND> [ARGS...]");
|
|
373
380
|
data = await runCheckpoint(args[0], args[1], args.slice(3), { timeoutMs, execBackend, remoteShell, supervisor, connection });
|
|
374
381
|
if (!includeOutput) delete data.output_tail;
|
|
375
382
|
if (!data.valid) process.exitCode = 1;
|
|
376
383
|
} else if (action === "attach") {
|
|
377
384
|
const refs = takeOptions(args, "--artifact");
|
|
378
|
-
if (args.length !== 2) throw
|
|
385
|
+
if (args.length !== 2) throw usage("Expected: checkpoint attach <THREAD> <ID> --artifact FILE ...");
|
|
379
386
|
data = await attachArtifacts(args[0], args[1], refs);
|
|
380
387
|
} else if (action === "list") {
|
|
381
|
-
if (args.length !== 1) throw
|
|
388
|
+
if (args.length !== 1) throw usage("Expected: checkpoint list <THREAD>.");
|
|
382
389
|
data = await listCheckpoints(args[0]);
|
|
383
390
|
} else if (["show", "verify"].includes(action)) {
|
|
384
|
-
if (args.length !== 2) throw
|
|
391
|
+
if (args.length !== 2) throw usage(`Expected: checkpoint ${action} <THREAD> <ID>.`);
|
|
385
392
|
data = action === "verify" ? await verifyCheckpoint(args[0], args[1]) : checkpointSummary(await getCheckpoint(args[0], args[1]), includeOutput);
|
|
386
393
|
if (action === "verify" && !data.valid) process.exitCode = 1;
|
|
387
|
-
} else throw
|
|
394
|
+
} else throw usage("See codexteer help checkpoint.");
|
|
388
395
|
success(`checkpoint.${action}`, data, json);
|
|
389
396
|
if (!json) console.log(JSON.stringify(data, null, 2));
|
|
390
397
|
return;
|
|
@@ -393,18 +400,18 @@ async function mainOnTarget(argv) {
|
|
|
393
400
|
const action = args.shift();
|
|
394
401
|
if (action === "list") {
|
|
395
402
|
const all = takeFlag(args, "--all");
|
|
396
|
-
if (args.length !== 1) throw
|
|
403
|
+
if (args.length !== 1) throw usage("Expected: instructions list <THREAD> [--all].");
|
|
397
404
|
if (supervisor) await assertSupervisor(args[0], supervisor, { connection });
|
|
398
405
|
const data = await listInstructions(args[0], { all });
|
|
399
406
|
success("instructions.list", data, json);
|
|
400
407
|
if (!json) { for (const entry of data.instructions) console.log(`${entry.id} ${entry.instruction_status} ${entry.metadata?.source ?? "unspecified"}\n${entry.body}`); if (data.pending_changes.length) console.log(`配送未確認: ${data.pending_changes.map(e => e.id).join(", ")}`); }
|
|
401
408
|
return;
|
|
402
409
|
}
|
|
403
|
-
if (action !== "retract") throw
|
|
410
|
+
if (action !== "retract") throw usage("See codexteer help instructions.");
|
|
404
411
|
if (connection === "desktop") throw desktopReadOnly();
|
|
405
412
|
const reason = takeOption(args, "--reason", undefined), source = takeOption(args, "--source", undefined), basedOn = takeOption(args, "--based-on", undefined);
|
|
406
413
|
const dryRun = takeFlag(args, "--dry-run"), newTurn = takeFlag(args, "--new-turn");
|
|
407
|
-
if (args.length !== 2 || !reason?.trim()) throw
|
|
414
|
+
if (args.length !== 2 || !reason?.trim()) throw usage("Expected: instructions retract <THREAD> <MESSAGE-ID> --reason TEXT.");
|
|
408
415
|
const data = await sendTrackedMessage(args[0], reason, { retracts: args[1], source, basedOn, dryRun, newTurn, supervisor, connection });
|
|
409
416
|
success("instructions.retract", data, json);
|
|
410
417
|
if (!json) console.log(dryRun ? "撤回の送信プレビューです。" : `撤回を受け付けました。message_id: ${data.message_id}`);
|
|
@@ -416,7 +423,7 @@ async function mainOnTarget(argv) {
|
|
|
416
423
|
const pending = takeFlag(args, "--pending");
|
|
417
424
|
const report = action === "mark" ? { status: takeOption(args, "--status", undefined), note: takeOption(args, "--note", undefined), evidence: takeOptions(args, "--evidence"), by: takeOption(args, "--by", "local") } : {};
|
|
418
425
|
const [threadId, id] = args;
|
|
419
|
-
if (!threadId || args.length > 2 || !["list", "show", "check", "mark"].includes(action) || (["show", "mark"].includes(action) && !id) || (action === "list" && id)) throw
|
|
426
|
+
if (!threadId || args.length > 2 || !["list", "show", "check", "mark"].includes(action) || (["show", "mark"].includes(action) && !id) || (action === "list" && id)) throw usage("See codexteer help history.");
|
|
420
427
|
if (supervisor) await assertSupervisor(threadId, supervisor, { connection });
|
|
421
428
|
const data = action === "list" ? await listMessages(threadId, { includeText, pending }) : action === "show" ? messageSummary(await getMessage(threadId, id), includeText) : action === "check" ? await reconcileMessages(threadId, id, { includeText }, connectionDependencies) : await markMessage(threadId, id, report);
|
|
422
429
|
success(`history.${action}`, data, json);
|
|
@@ -437,14 +444,14 @@ async function mainOnTarget(argv) {
|
|
|
437
444
|
if (stream) json = true;
|
|
438
445
|
if (command === "watch") {
|
|
439
446
|
const until = takeOption(args, "--until", undefined), timeout = takeOption(args, "--timeout-ms", undefined);
|
|
440
|
-
if (stream && (until !== undefined || timeout !== undefined)) throw
|
|
447
|
+
if (stream && (until !== undefined || timeout !== undefined)) throw usage("--stream runs until cancelled; do not combine it with --until or --timeout-ms. See codexteer help monitor.");
|
|
441
448
|
const notify = takeOption(args, "--notify", stream ? "digest" : "all"), settle = takeOption(args, "--settle-ms", undefined), maxHold = takeOption(args, "--max-hold-ms", undefined);
|
|
442
|
-
if (!stream && (notify === "digest" || settle !== undefined || maxHold !== undefined)) throw
|
|
443
|
-
if (notify !== "digest" && (settle !== undefined || maxHold !== undefined)) throw
|
|
449
|
+
if (!stream && (notify === "digest" || settle !== undefined || maxHold !== undefined)) throw usage("--notify digest, --settle-ms and --max-hold-ms require --stream.");
|
|
450
|
+
if (notify !== "digest" && (settle !== undefined || maxHold !== undefined)) throw usage("--settle-ms and --max-hold-ms require --notify digest.");
|
|
444
451
|
Object.assign(options, notificationOptions({ notify, ...(settle === undefined ? {} : { settleMs: Number(settle) }), ...(maxHold === undefined ? {} : { maxHoldMs: Number(maxHold) }) }));
|
|
445
452
|
Object.assign(options, { watch: true, until: until ?? "change", timeoutMs: Number(timeout ?? "30000"), pollMs: Number(takeOption(args, "--poll-ms", "1000")) });
|
|
446
453
|
}
|
|
447
|
-
if (args.length !== 1) throw
|
|
454
|
+
if (args.length !== 1) throw usage(`Expected: ${command} <THREAD>. See codexteer help ${command}.`);
|
|
448
455
|
if (stream) { await monitorCommand(args[0], options, connectionDependencies); return; }
|
|
449
456
|
const data = await observeThread(args[0], options, connectionDependencies);
|
|
450
457
|
data.connection_mode = connection;
|
|
@@ -457,10 +464,10 @@ async function mainOnTarget(argv) {
|
|
|
457
464
|
const backend = backendOption(args, "app-server");
|
|
458
465
|
if (connection === "desktop" && backend === "ui") throw desktopReadOnly();
|
|
459
466
|
const target = takeOption(args, "--thread", undefined);
|
|
460
|
-
if (target !== undefined && backend !== "app-server") throw
|
|
467
|
+
if (target !== undefined && backend !== "app-server") throw usage("doctor --thread requires --backend app-server.");
|
|
461
468
|
const threadId = target === undefined ? undefined : normalizeThreadId(target);
|
|
462
469
|
if (supervisor && threadId) await assertSupervisorRegistration(threadId, supervisor, { connection });
|
|
463
|
-
if (args.length) throw
|
|
470
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
464
471
|
if (targetHost() !== "local" && backend === "ui") throw remoteReadOnly();
|
|
465
472
|
const report = backend === "ui" ? desktopDoctor() : await appServerDoctor({ threadId, ...connectionDependencies });
|
|
466
473
|
if (report.connection) Object.assign(report.connection, { mode: connection, observation_only: connection === "desktop" || (targetHost() !== "local" && !control), ...hostFields() });
|
|
@@ -492,7 +499,7 @@ async function mainOnTarget(argv) {
|
|
|
492
499
|
if (command === "desktop") {
|
|
493
500
|
const action = args.shift();
|
|
494
501
|
if (action === "update-status") {
|
|
495
|
-
if (args.length) throw
|
|
502
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
496
503
|
const data = await desktopUpdateStatus();
|
|
497
504
|
success("desktop.update-status", data, json);
|
|
498
505
|
if (!json) console.log(JSON.stringify(data, null, 2));
|
|
@@ -502,15 +509,15 @@ async function mainOnTarget(argv) {
|
|
|
502
509
|
if (supervisor) throw Object.assign(new Error("A supervisor cannot update or restart Desktop. Run this command from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
503
510
|
const dryRun = takeFlag(args, "--dry-run");
|
|
504
511
|
const timeoutMs = Number(takeOption(args, "--timeout-ms", "600000"));
|
|
505
|
-
if (args.length) throw
|
|
512
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
506
513
|
const data = await launchDesktopUpdate({ dryRun, timeoutMs });
|
|
507
514
|
success("desktop.update", data, json);
|
|
508
515
|
if (!json) console.log(dryRun ? "Dry run: official update, relaunch, normal quit, wrapper startup, and verification." : `Update worker started (${data.worker_pid}). Check: ${data.status_command}`);
|
|
509
516
|
return;
|
|
510
517
|
}
|
|
511
|
-
if (action !== "start") throw
|
|
518
|
+
if (action !== "start") throw usage("Expected: codexteer desktop start|update|update-status");
|
|
512
519
|
const dryRun = takeFlag(args, "--dry-run");
|
|
513
|
-
if (args.length) throw
|
|
520
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
514
521
|
const data = await startDesktop({ dryRun });
|
|
515
522
|
success("desktop.start", data, json);
|
|
516
523
|
if (!json) console.log(dryRun ? "Dry run: would launch Desktop with the shared App Server wrapper." : "Desktop shared App Server is ready.");
|
|
@@ -519,9 +526,9 @@ async function mainOnTarget(argv) {
|
|
|
519
526
|
|
|
520
527
|
if (command === "projects") {
|
|
521
528
|
if (connection === "desktop") throw desktopReadOnly();
|
|
522
|
-
if (args.shift() !== "list") throw
|
|
529
|
+
if (args.shift() !== "list") throw usage("Expected: codexteer projects list [--remote | --all] [--host HOST-ID]");
|
|
523
530
|
const remote = takeFlag(args, "--remote"), all = takeFlag(args, "--all");
|
|
524
|
-
if (args.length || (remote && all) || (all && targetHost() !== "local")) throw
|
|
531
|
+
if (args.length || (remote && all) || (all && targetHost() !== "local")) throw usage("Use projects list [--remote | --all] or projects list --host HOST-ID.");
|
|
525
532
|
const saved = targetHost() !== "local" && remoteTransport() === "paired" ? await listRemoteProjects()
|
|
526
533
|
: remote || all || targetHost() !== "local" ? await registeredRemoteProjects() : [];
|
|
527
534
|
const projects = [...(!remote && (all || targetHost() === "local") ? await listProjects() : []),
|
|
@@ -532,15 +539,15 @@ async function mainOnTarget(argv) {
|
|
|
532
539
|
}
|
|
533
540
|
|
|
534
541
|
if (command === "threads") {
|
|
535
|
-
if (args.shift() !== "list") throw
|
|
542
|
+
if (args.shift() !== "list") throw usage("Expected: codexteer threads list");
|
|
536
543
|
const limit = Number.parseInt(takeOption(args, "--limit", "20"), 10);
|
|
537
544
|
const desktopOnly = takeFlag(args, "--desktop-only");
|
|
538
545
|
const cursor = takeOption(args, "--cursor", undefined), projectId = takeOption(args, "--project", undefined), search = takeOption(args, "--search", undefined);
|
|
539
|
-
if (args.length > 0) throw
|
|
546
|
+
if (args.length > 0) throw usage(`Unexpected argument: ${args[0]}`);
|
|
540
547
|
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
541
|
-
throw
|
|
548
|
+
throw usage("--limit must be an integer between 1 and 500.");
|
|
542
549
|
}
|
|
543
|
-
if (targetHost() === "local" && [cursor, projectId, search].some(value => value !== undefined)) throw
|
|
550
|
+
if (targetHost() === "local" && [cursor, projectId, search].some(value => value !== undefined)) throw usage("--cursor/--project/--search currently require a paired remote host.");
|
|
544
551
|
const data = targetHost() === "local" ? { threads: await listLocalThreads({ limit, desktopOnly }) } : await listRemoteThreads({ limit, desktopOnly, cursor, projectId, search });
|
|
545
552
|
const threads = data.threads;
|
|
546
553
|
success("threads.list", { ...data, count: threads.length }, json);
|
|
@@ -554,7 +561,7 @@ async function mainOnTarget(argv) {
|
|
|
554
561
|
if (connection === "desktop") throw desktopReadOnly();
|
|
555
562
|
if (supervisor) throw Object.assign(new Error("Managed supervision is bound to its existing task. Verify a project creation receipt without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
556
563
|
const timeoutMs = Number(takeOption(args, "--timeout-ms", "120000"));
|
|
557
|
-
if (args.length !== 1 || args[0].startsWith("--")) throw
|
|
564
|
+
if (args.length !== 1 || args[0].startsWith("--")) throw usage("Expected: thread verify <ATTEMPT-ID> [--timeout-ms N].");
|
|
558
565
|
const data = await (targetHost() === "local" ? verifyProjectThread : verifyRemoteProjectThread)(args[0], { timeoutMs });
|
|
559
566
|
success("thread.verify", data, json);
|
|
560
567
|
if (!json) console.log(`Verified existing ${data.deep_link}; creation and initial instruction were not repeated.`);
|
|
@@ -572,7 +579,7 @@ async function mainOnTarget(argv) {
|
|
|
572
579
|
options.timeoutMs = Number(options.timeoutMs);
|
|
573
580
|
}
|
|
574
581
|
const literal = args.indexOf("--");
|
|
575
|
-
if (args.slice(0, literal < 0 ? args.length : literal).some(arg => arg.startsWith("--"))) throw
|
|
582
|
+
if (args.slice(0, literal < 0 ? args.length : literal).some(arg => arg.startsWith("--"))) throw usage("Unknown thread create option. Use -- before an instruction starting with --.");
|
|
576
583
|
const message = readMessage(args);
|
|
577
584
|
const data = await (targetHost() === "local" ? createProjectThread : createRemoteProjectThread)(selector, message, options);
|
|
578
585
|
success("thread.create", data, json);
|
|
@@ -581,7 +588,7 @@ async function mainOnTarget(argv) {
|
|
|
581
588
|
}
|
|
582
589
|
if (action === "repair") {
|
|
583
590
|
const apply = takeFlag(args, "--apply"), dryRun = takeFlag(args, "--dry-run");
|
|
584
|
-
if (args.length !== 1 || args[0].startsWith("--") || (apply && dryRun)) throw
|
|
591
|
+
if (args.length !== 1 || args[0].startsWith("--") || (apply && dryRun)) throw usage("Expected: thread repair <THREAD> [--dry-run | --apply].");
|
|
585
592
|
if (connection === "desktop") throw desktopReadOnly();
|
|
586
593
|
if (supervisor) throw Object.assign(new Error("A supervisor cannot repair task history. Run this command from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
|
|
587
594
|
const data = await repairTurns(args[0], { apply });
|
|
@@ -589,8 +596,8 @@ async function mainOnTarget(argv) {
|
|
|
589
596
|
if (!json) console.log(JSON.stringify(data, null, 2));
|
|
590
597
|
return;
|
|
591
598
|
}
|
|
592
|
-
if (action !== "resolve") throw
|
|
593
|
-
if (args.length !== 1) throw
|
|
599
|
+
if (action !== "resolve") throw usage("Expected: codexteer thread create|verify|resolve|repair. See help thread.");
|
|
600
|
+
if (args.length !== 1) throw usage("thread resolve requires exactly one thread ID or URL.");
|
|
594
601
|
const threadId = normalizeThreadId(args[0]);
|
|
595
602
|
const data = { thread_id: threadId, deep_link: threadDeepLink(threadId) };
|
|
596
603
|
success("thread.resolve", data, json);
|
|
@@ -599,7 +606,7 @@ async function mainOnTarget(argv) {
|
|
|
599
606
|
}
|
|
600
607
|
|
|
601
608
|
if (command === "open") {
|
|
602
|
-
if (args.length !== 1) throw
|
|
609
|
+
if (args.length !== 1) throw usage("open requires exactly one thread ID or URL.");
|
|
603
610
|
const threadId = normalizeThreadId(args[0]);
|
|
604
611
|
const data = targetHost() === "local" ? openDesktopThread(threadId) : await ensureRemoteDesktopSubscription(threadId);
|
|
605
612
|
success("open", data, json);
|
|
@@ -609,7 +616,7 @@ async function mainOnTarget(argv) {
|
|
|
609
616
|
|
|
610
617
|
if (command === "debug-ui") {
|
|
611
618
|
const waitMs = Number.parseInt(takeOption(args, "--wait-ms", "1500"), 10);
|
|
612
|
-
if (args.length !== 1) throw
|
|
619
|
+
if (args.length !== 1) throw usage("debug-ui requires exactly one thread ID or URL.");
|
|
613
620
|
const threadId = normalizeThreadId(args[0]);
|
|
614
621
|
const data = inspectDesktopUi(threadId, { waitMs });
|
|
615
622
|
success("debug-ui", data, json);
|
|
@@ -628,22 +635,23 @@ async function mainOnTarget(argv) {
|
|
|
628
635
|
if (remoteTransport() !== "paired") throw remoteReadOnly();
|
|
629
636
|
throw Object.assign(new Error("UI sending on the remote host is unsupported. No UI or other delivery route was used."), { code: "REMOTE_UI_UNSUPPORTED", sent: false, delivery_status: "not_sent" });
|
|
630
637
|
}
|
|
631
|
-
if (supervisor && backend !== "app-server") throw
|
|
638
|
+
if (supervisor && backend !== "app-server") throw usage("Managed supervision requires the app-server backend.");
|
|
632
639
|
const explicitSound = takeFlag(args, "--sound");
|
|
633
640
|
const noSound = takeFlag(args, "--no-sound");
|
|
634
|
-
if (explicitSound && noSound) throw
|
|
635
|
-
if (explicitSound && backend !== "app-server") throw
|
|
641
|
+
if (explicitSound && noSound) throw usage("--sound and --no-sound cannot be combined.");
|
|
642
|
+
if (explicitSound && backend !== "app-server") throw usage("--sound requires --backend app-server so receipt can be confirmed.");
|
|
636
643
|
const newTurn = takeFlag(args, "--new-turn");
|
|
637
644
|
const directive = { source: takeOption(args, "--source", undefined), kind: takeOption(args, "--kind", undefined), evidence: takeOptions(args, "--evidence"), basedOn: takeOption(args, "--based-on", undefined), supersedes: takeOption(args, "--supersedes", undefined), expiresAt: takeOption(args, "--expires-at", undefined), checkpoint: takeOption(args, "--checkpoint", undefined), finding: takeOption(args, "--finding", undefined) };
|
|
638
|
-
if (backend === "ui" && (directive.source || directive.kind || directive.evidence.length || directive.basedOn || directive.supersedes || directive.expiresAt || directive.checkpoint || directive.finding)) throw
|
|
645
|
+
if (backend === "ui" && (directive.source || directive.kind || directive.evidence.length || directive.basedOn || directive.supersedes || directive.expiresAt || directive.checkpoint || directive.finding)) throw usage("Directive metadata requires --backend app-server.");
|
|
639
646
|
const keepFocus = takeFlag(args, "--keep-focus");
|
|
640
647
|
const waitInput = takeOption(args, "--wait-ms", undefined);
|
|
641
|
-
if (backend !== "ui" && (keepFocus || waitInput != null)) throw
|
|
648
|
+
if (backend !== "ui" && (keepFocus || waitInput != null)) throw usage("--keep-focus and --wait-ms require --backend ui.");
|
|
642
649
|
const waitMs = Number(waitInput ?? "1500");
|
|
650
|
+
if (args.slice(0, args.includes("--") ? args.indexOf("--") : args.length).some(arg => arg.startsWith("--"))) throw usage("Unknown send option. Use -- before instruction text starting with --.");
|
|
643
651
|
const threadInput = args.shift();
|
|
644
|
-
if (!threadInput) throw
|
|
652
|
+
if (!threadInput) throw usage("send requires a thread ID or codex://threads URL.");
|
|
645
653
|
const message = readMessage(args);
|
|
646
|
-
if (message.trim() === "") throw
|
|
654
|
+
if (message.trim() === "") throw usage("send requires a non-empty message.");
|
|
647
655
|
const threadId = normalizeThreadId(threadInput);
|
|
648
656
|
const data = backend === "app-server"
|
|
649
657
|
? await sendTrackedMessage(threadId, message, { dryRun, newTurn, ...directive, supervisor, connection })
|
package/src/cloud-cli.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { desktopCloudProjectDoctor, assignDesktopCloudProject, desktopCloudProjectReceipt } from "./desktop-cloud-project.mjs";
|
|
1
|
+
import { desktopCloudProjectDoctor, assignDesktopCloudProject, desktopCloudProjectReceipt, resolveDesktopCloudProjectAttempt } from "./desktop-cloud-project.mjs";
|
|
2
2
|
import { listProjects as listDesktopProjects } from "./projects.mjs";
|
|
3
3
|
import { isDesktopCloudTask, desktopCloudId, desktopCloudLink } from "./desktop-cloud-id.mjs";
|
|
4
4
|
import { desktopCloudConnection, desktopCloudEnvironments } from "./desktop-cloud-api.mjs";
|
|
5
5
|
import { createDesktopCloudTask, listDesktopCloudTasks, desktopCloudReceipt } from "./desktop-cloud.mjs";
|
|
6
6
|
import { isCloudTask } from "./task-id.mjs";
|
|
7
7
|
import { cloudTaskId } from "./cloud-id.mjs";
|
|
8
|
+
import { normalizeThreadId } from "./thread-id.mjs";
|
|
9
|
+
import { connectionMismatch } from "./connection-error.mjs";
|
|
8
10
|
import { cloudConnection } from "./cloud-connection.mjs";
|
|
9
11
|
import { observeThread, printObservation } from "./observe.mjs";
|
|
10
12
|
import { monitorCommand } from "./monitor.mjs";
|
|
@@ -12,7 +14,7 @@ import { notificationOptions } from "./notify.mjs";
|
|
|
12
14
|
import { prepareSupervisorPrompt, superviseAgent } from "./supervise.mjs";
|
|
13
15
|
import { supervisorId, registerSupervisor, getSupervisor, assertSupervisor, assertSupervisorRegistration, controlSupervisor, supervisorStatus, listSupervisors } from "./supervision.mjs";
|
|
14
16
|
import { createFinding, findingStatus, listFindings, resolveFinding, dismissFinding, reopenFinding, evaluateFinding, findingStats } from "./findings.mjs";
|
|
15
|
-
import { sendTrackedMessage, listMessages, getMessage, messageSummary, reconcileMessages, markMessage, listInstructions } from "./journal.mjs";
|
|
17
|
+
import { sendTrackedMessage, listMessages, getMessage, messageSummary, reconcileMessages, markMessage, resolveUnknownDelivery, listInstructions } from "./journal.mjs";
|
|
16
18
|
import { createCloudTask, cloudTaskDiff, listCloudTasks, cloudCreationReceipt } from "./cloud.mjs";
|
|
17
19
|
import { cloudSendReceipt, reconcileCloudSend } from "./cloud-observe.mjs";
|
|
18
20
|
import { cloudEnvironments } from "./cloud-api.mjs";
|
|
@@ -67,8 +69,18 @@ export const cloudInvocation = argv => !!selectedCloudInvocation(argv);
|
|
|
67
69
|
export async function mainCloud(argv) {
|
|
68
70
|
const args = [...argv], json = flag(args, "--json"), connection = option(args, "--connection", selectedCloudInvocation(argv) || "cloud");
|
|
69
71
|
const desktopCloud = connection === "desktop-cloud";
|
|
70
|
-
const targetId = desktopCloud ? desktopCloudId :
|
|
71
|
-
|
|
72
|
+
const targetId = desktopCloud ? desktopCloudId : value => {
|
|
73
|
+
try { return cloudTaskId(value); }
|
|
74
|
+
catch (error) {
|
|
75
|
+
// Preserve the Web Cloud ID validator's existing error code.
|
|
76
|
+
if (isDesktopCloudTask(value)) throw connectionMismatch("desktop-cloud", connection, error.code);
|
|
77
|
+
let local = false;
|
|
78
|
+
try { normalizeThreadId(value); local = true; } catch {}
|
|
79
|
+
if (local) throw connectionMismatch("shared", connection, error.code);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (!["cloud", "desktop-cloud"].includes(connection)) throw Object.assign(connectionMismatch(selectedCloudInvocation(argv) || "cloud", connection), { execution_environment: "cloud" });
|
|
72
84
|
const own = args.slice(0, args.includes("--") ? args.indexOf("--") : args.length);
|
|
73
85
|
if (own.some(value => ["--host", "--remote-transport", "--context-thread"].includes(value))) throw failure("Desktop host routes cannot select a cloud task.", "CLOUD_TARGET_CONFLICT");
|
|
74
86
|
const requiredNode = option(args, "--require-node-version");
|
|
@@ -159,11 +171,12 @@ export async function mainCloud(argv) {
|
|
|
159
171
|
const settings = { taskId: id };
|
|
160
172
|
output(`history.${action}`, action === "show" ? await cloudSendReceipt(attempt, settings) : await reconcileCloudSend(attempt, settings)); return;
|
|
161
173
|
}
|
|
162
|
-
const includeText = flag(args, "--include-text"), pending = flag(args, "--pending");
|
|
163
|
-
const report = action === "mark" ? { status: option(args, "--status"), note: option(args, "--note"), evidence: repeated(args, "--evidence"), by: option(args, "--by", "local") } : {};
|
|
174
|
+
const includeText = flag(args, "--include-text"), pending = flag(args, "--pending"), resolveUnknown = desktopCloud && action === "mark" && flag(args, "--resolve-unknown");
|
|
175
|
+
const report = resolveUnknown ? { note: option(args, "--note") } : action === "mark" ? { status: option(args, "--status"), note: option(args, "--note"), evidence: repeated(args, "--evidence"), by: option(args, "--by", "local") } : {};
|
|
164
176
|
if (!args[0] || args.length > 2 || !["list", "show", "check", "mark"].includes(action) || (["show", "mark"].includes(action) && !args[1]) || (action === "list" && args[1])) throw failure("Invalid history arguments.");
|
|
177
|
+
if (resolveUnknown && supervisor) throw failure("A supervisor cannot release its own unknown delivery. The user must run this without --supervisor.", "SUPERVISOR_CONTROL_FORBIDDEN");
|
|
165
178
|
const id = await bound(args[0]); const data = action === "list" ? await listMessages(id, { includeText, pending }) : action === "show" ? messageSummary(await getMessage(id, args[1]), includeText)
|
|
166
|
-
: action === "check" ? await reconcileMessages(id, args[1], { includeText }, dependencies) : await markMessage(id, args[1], report);
|
|
179
|
+
: action === "check" ? await reconcileMessages(id, args[1], { includeText }, dependencies) : resolveUnknown ? await resolveUnknownDelivery(id, args[1], report) : await markMessage(id, args[1], report);
|
|
167
180
|
output(`history.${action}`, data); return;
|
|
168
181
|
}
|
|
169
182
|
if (command === "instructions") {
|
|
@@ -300,7 +313,8 @@ async function desktopCloudCommands(command, args, { supervisor, output, bound,
|
|
|
300
313
|
output("thread.create", await createDesktopCloudTask(environment, message(args), { project, title, dryRun })); return true;
|
|
301
314
|
}
|
|
302
315
|
if (action === "assign") { const project = option(args, "--project"); exact(args, 1); const data = await assignDesktopCloudProject(await bound(args[0]), project); output("thread.assign", data); if (data.verification_status !== "verified") process.exitCode = 1; return true; }
|
|
303
|
-
exact(args, 1);
|
|
316
|
+
const resolveUnknown = action === "receipt" && flag(args, "--resolve-unknown"), note = resolveUnknown ? option(args, "--note") : undefined; exact(args, 1);
|
|
317
|
+
if (resolveUnknown) { output("thread.receipt", await resolveDesktopCloudProjectAttempt(args[0], { note })); return true; }
|
|
304
318
|
if (action === "receipt") { let data; try { data = await desktopCloudReceipt(args[0]); } catch (error) { if (error.code !== "DESKTOP_CLOUD_CREATION_RECEIPT_NOT_FOUND") throw error; data = await desktopCloudProjectReceipt(args[0]); } output("thread.receipt", data); return true; }
|
|
305
319
|
if (action === "resolve") { const id = await bound(args[0]); output("thread.resolve", { thread_id: id, deep_link: desktopCloudLink(id) }); return true; }
|
|
306
320
|
throw failure("Expected thread create|assign|receipt|resolve.");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Diagnostics only: callers retain their existing routing and rejection rules.
|
|
2
|
+
export function connectionMismatch(expected, selected, code = "CLOUD_TARGET_CONFLICT") {
|
|
3
|
+
const message = expected === "shared"
|
|
4
|
+
? `For a local task, use --connection shared (the default local connection; got ${selected}).`
|
|
5
|
+
: `This task requires --connection ${expected} (got ${selected}).`;
|
|
6
|
+
return Object.assign(new Error(message), { code, expected_connection: expected, connection: selected });
|
|
7
|
+
}
|
package/src/desktop-cloud-id.mjs
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { normalizeThreadId } from "./thread-id.mjs";
|
|
2
|
+
import { isCloudTask } from "./cloud-id.mjs";
|
|
3
|
+
import { connectionMismatch } from "./connection-error.mjs";
|
|
2
4
|
export function isDesktopCloudTask(value) {
|
|
3
5
|
if (typeof value !== "string") return false;
|
|
4
6
|
if (value.startsWith("durable_")) return true;
|
|
5
7
|
try { const u = new URL(value); return u.protocol === "codex:" && u.hostname === "threads" && u.searchParams.getAll("hostId").includes("durable"); } catch { return false; }
|
|
6
8
|
}
|
|
7
9
|
export function desktopCloudId(value) {
|
|
8
|
-
if (!isDesktopCloudTask(value))
|
|
10
|
+
if (!isDesktopCloudTask(value)) {
|
|
11
|
+
if (isCloudTask(value)) throw connectionMismatch("cloud", "desktop-cloud");
|
|
12
|
+
let local = false;
|
|
13
|
+
try { normalizeThreadId(value); local = true; } catch {}
|
|
14
|
+
if (local) throw connectionMismatch("shared", "desktop-cloud");
|
|
15
|
+
throw Object.assign(new Error("Desktop Cloud requires a durable_ ID or a link with hostId=durable."), { code: "CLOUD_TARGET_CONFLICT" });
|
|
16
|
+
}
|
|
9
17
|
let uuid = value.slice(8);
|
|
10
18
|
if (!value.startsWith("durable_")) {
|
|
11
19
|
const url = new URL(value);
|