codexteer 0.18.1 → 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 +16 -0
- package/README.md +6 -0
- package/docs/distribution.md +4 -0
- package/package.json +1 -1
- package/src/app-server.mjs +4 -4
- package/src/checkpoint.mjs +10 -10
- package/src/cli.mjs +79 -75
- package/src/cloud-cli.mjs +25 -12
- 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/supervision.mjs +58 -16
- 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
|
@@ -15,7 +15,7 @@ import { acquireResource, resourceStatus, renewResource, releaseResource, listRe
|
|
|
15
15
|
import { playSendSound } from "./sound.mjs";
|
|
16
16
|
import { prepareSupervisorPrompt, superviseAgent } from "./supervise.mjs";
|
|
17
17
|
import { prepareOrchestrationPrompt, orchestrateAgent } from "./orchestrate.mjs";
|
|
18
|
-
import { supervisorId, registerSupervisor, getSupervisor, assertSupervisor, controlSupervisor, supervisorStatus, listSupervisors } from "./supervision.mjs";
|
|
18
|
+
import { supervisorId, registerSupervisor, getSupervisor, assertSupervisor, assertSupervisorRegistration, controlSupervisor, supervisorStatus, listSupervisors } from "./supervision.mjs";
|
|
19
19
|
import { createFinding, findingStatus, listFindings, resolveFinding, dismissFinding, reopenFinding, evaluateFinding, findingStats } from "./findings.mjs";
|
|
20
20
|
import { connectionMode, observationConnection, desktopReadOnly } from "./connection.mjs";
|
|
21
21
|
import { repairTurns } from "./turn-repair.mjs";
|
|
@@ -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
|
|
|
@@ -141,7 +144,7 @@ function printThreads(threads) {
|
|
|
141
144
|
function printSupervisor(data) {
|
|
142
145
|
if (Array.isArray(data)) {
|
|
143
146
|
if (!data.length) console.log("No supervisor sessions registered.");
|
|
144
|
-
for (const entry of data) console.log(`${entry.thread_id} ${entry.state} ${entry.owner} ${entry.connection} observed:${entry.observation?.last_observed_at ?? "none"}`);
|
|
147
|
+
for (const entry of data) console.log(`${entry.thread_id} ${entry.state} ${entry.owner} ${entry.connection} observed:${entry.observation?.last_observed_at ?? "none"}${entry.session_conflict ? ` session-conflict:${entry.session_conflict.thread_ids.join(",")}` : ""}${entry.observation_error ? ` observation-error:${entry.observation_error.code}` : ""}`);
|
|
145
148
|
return;
|
|
146
149
|
}
|
|
147
150
|
console.log(`${data.thread_id} ${data.state}${data.owner ? ` ${data.owner} ${data.connection}` : ""}`);
|
|
@@ -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,14 +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
|
-
if (supervisor && threadId) {
|
|
463
|
-
|
|
464
|
-
if (record && record.session_id !== supervisor && record.state !== "stopped") throw Object.assign(new Error("Another supervisor is registered for this task."), { code: "SUPERVISOR_CONFLICT" });
|
|
465
|
-
if (record?.session_id === supervisor) await assertSupervisor(threadId, supervisor, { connection });
|
|
466
|
-
}
|
|
467
|
-
if (args.length) throw new Error(`Unexpected argument: ${args[0]}`);
|
|
469
|
+
if (supervisor && threadId) await assertSupervisorRegistration(threadId, supervisor, { connection });
|
|
470
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
468
471
|
if (targetHost() !== "local" && backend === "ui") throw remoteReadOnly();
|
|
469
472
|
const report = backend === "ui" ? desktopDoctor() : await appServerDoctor({ threadId, ...connectionDependencies });
|
|
470
473
|
if (report.connection) Object.assign(report.connection, { mode: connection, observation_only: connection === "desktop" || (targetHost() !== "local" && !control), ...hostFields() });
|
|
@@ -496,7 +499,7 @@ async function mainOnTarget(argv) {
|
|
|
496
499
|
if (command === "desktop") {
|
|
497
500
|
const action = args.shift();
|
|
498
501
|
if (action === "update-status") {
|
|
499
|
-
if (args.length) throw
|
|
502
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
500
503
|
const data = await desktopUpdateStatus();
|
|
501
504
|
success("desktop.update-status", data, json);
|
|
502
505
|
if (!json) console.log(JSON.stringify(data, null, 2));
|
|
@@ -506,15 +509,15 @@ async function mainOnTarget(argv) {
|
|
|
506
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" });
|
|
507
510
|
const dryRun = takeFlag(args, "--dry-run");
|
|
508
511
|
const timeoutMs = Number(takeOption(args, "--timeout-ms", "600000"));
|
|
509
|
-
if (args.length) throw
|
|
512
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
510
513
|
const data = await launchDesktopUpdate({ dryRun, timeoutMs });
|
|
511
514
|
success("desktop.update", data, json);
|
|
512
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}`);
|
|
513
516
|
return;
|
|
514
517
|
}
|
|
515
|
-
if (action !== "start") throw
|
|
518
|
+
if (action !== "start") throw usage("Expected: codexteer desktop start|update|update-status");
|
|
516
519
|
const dryRun = takeFlag(args, "--dry-run");
|
|
517
|
-
if (args.length) throw
|
|
520
|
+
if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
|
|
518
521
|
const data = await startDesktop({ dryRun });
|
|
519
522
|
success("desktop.start", data, json);
|
|
520
523
|
if (!json) console.log(dryRun ? "Dry run: would launch Desktop with the shared App Server wrapper." : "Desktop shared App Server is ready.");
|
|
@@ -523,9 +526,9 @@ async function mainOnTarget(argv) {
|
|
|
523
526
|
|
|
524
527
|
if (command === "projects") {
|
|
525
528
|
if (connection === "desktop") throw desktopReadOnly();
|
|
526
|
-
if (args.shift() !== "list") throw
|
|
529
|
+
if (args.shift() !== "list") throw usage("Expected: codexteer projects list [--remote | --all] [--host HOST-ID]");
|
|
527
530
|
const remote = takeFlag(args, "--remote"), all = takeFlag(args, "--all");
|
|
528
|
-
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.");
|
|
529
532
|
const saved = targetHost() !== "local" && remoteTransport() === "paired" ? await listRemoteProjects()
|
|
530
533
|
: remote || all || targetHost() !== "local" ? await registeredRemoteProjects() : [];
|
|
531
534
|
const projects = [...(!remote && (all || targetHost() === "local") ? await listProjects() : []),
|
|
@@ -536,15 +539,15 @@ async function mainOnTarget(argv) {
|
|
|
536
539
|
}
|
|
537
540
|
|
|
538
541
|
if (command === "threads") {
|
|
539
|
-
if (args.shift() !== "list") throw
|
|
542
|
+
if (args.shift() !== "list") throw usage("Expected: codexteer threads list");
|
|
540
543
|
const limit = Number.parseInt(takeOption(args, "--limit", "20"), 10);
|
|
541
544
|
const desktopOnly = takeFlag(args, "--desktop-only");
|
|
542
545
|
const cursor = takeOption(args, "--cursor", undefined), projectId = takeOption(args, "--project", undefined), search = takeOption(args, "--search", undefined);
|
|
543
|
-
if (args.length > 0) throw
|
|
546
|
+
if (args.length > 0) throw usage(`Unexpected argument: ${args[0]}`);
|
|
544
547
|
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
545
|
-
throw
|
|
548
|
+
throw usage("--limit must be an integer between 1 and 500.");
|
|
546
549
|
}
|
|
547
|
-
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.");
|
|
548
551
|
const data = targetHost() === "local" ? { threads: await listLocalThreads({ limit, desktopOnly }) } : await listRemoteThreads({ limit, desktopOnly, cursor, projectId, search });
|
|
549
552
|
const threads = data.threads;
|
|
550
553
|
success("threads.list", { ...data, count: threads.length }, json);
|
|
@@ -558,7 +561,7 @@ async function mainOnTarget(argv) {
|
|
|
558
561
|
if (connection === "desktop") throw desktopReadOnly();
|
|
559
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" });
|
|
560
563
|
const timeoutMs = Number(takeOption(args, "--timeout-ms", "120000"));
|
|
561
|
-
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].");
|
|
562
565
|
const data = await (targetHost() === "local" ? verifyProjectThread : verifyRemoteProjectThread)(args[0], { timeoutMs });
|
|
563
566
|
success("thread.verify", data, json);
|
|
564
567
|
if (!json) console.log(`Verified existing ${data.deep_link}; creation and initial instruction were not repeated.`);
|
|
@@ -576,7 +579,7 @@ async function mainOnTarget(argv) {
|
|
|
576
579
|
options.timeoutMs = Number(options.timeoutMs);
|
|
577
580
|
}
|
|
578
581
|
const literal = args.indexOf("--");
|
|
579
|
-
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 --.");
|
|
580
583
|
const message = readMessage(args);
|
|
581
584
|
const data = await (targetHost() === "local" ? createProjectThread : createRemoteProjectThread)(selector, message, options);
|
|
582
585
|
success("thread.create", data, json);
|
|
@@ -585,7 +588,7 @@ async function mainOnTarget(argv) {
|
|
|
585
588
|
}
|
|
586
589
|
if (action === "repair") {
|
|
587
590
|
const apply = takeFlag(args, "--apply"), dryRun = takeFlag(args, "--dry-run");
|
|
588
|
-
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].");
|
|
589
592
|
if (connection === "desktop") throw desktopReadOnly();
|
|
590
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" });
|
|
591
594
|
const data = await repairTurns(args[0], { apply });
|
|
@@ -593,8 +596,8 @@ async function mainOnTarget(argv) {
|
|
|
593
596
|
if (!json) console.log(JSON.stringify(data, null, 2));
|
|
594
597
|
return;
|
|
595
598
|
}
|
|
596
|
-
if (action !== "resolve") throw
|
|
597
|
-
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.");
|
|
598
601
|
const threadId = normalizeThreadId(args[0]);
|
|
599
602
|
const data = { thread_id: threadId, deep_link: threadDeepLink(threadId) };
|
|
600
603
|
success("thread.resolve", data, json);
|
|
@@ -603,7 +606,7 @@ async function mainOnTarget(argv) {
|
|
|
603
606
|
}
|
|
604
607
|
|
|
605
608
|
if (command === "open") {
|
|
606
|
-
if (args.length !== 1) throw
|
|
609
|
+
if (args.length !== 1) throw usage("open requires exactly one thread ID or URL.");
|
|
607
610
|
const threadId = normalizeThreadId(args[0]);
|
|
608
611
|
const data = targetHost() === "local" ? openDesktopThread(threadId) : await ensureRemoteDesktopSubscription(threadId);
|
|
609
612
|
success("open", data, json);
|
|
@@ -613,7 +616,7 @@ async function mainOnTarget(argv) {
|
|
|
613
616
|
|
|
614
617
|
if (command === "debug-ui") {
|
|
615
618
|
const waitMs = Number.parseInt(takeOption(args, "--wait-ms", "1500"), 10);
|
|
616
|
-
if (args.length !== 1) throw
|
|
619
|
+
if (args.length !== 1) throw usage("debug-ui requires exactly one thread ID or URL.");
|
|
617
620
|
const threadId = normalizeThreadId(args[0]);
|
|
618
621
|
const data = inspectDesktopUi(threadId, { waitMs });
|
|
619
622
|
success("debug-ui", data, json);
|
|
@@ -632,22 +635,23 @@ async function mainOnTarget(argv) {
|
|
|
632
635
|
if (remoteTransport() !== "paired") throw remoteReadOnly();
|
|
633
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" });
|
|
634
637
|
}
|
|
635
|
-
if (supervisor && backend !== "app-server") throw
|
|
638
|
+
if (supervisor && backend !== "app-server") throw usage("Managed supervision requires the app-server backend.");
|
|
636
639
|
const explicitSound = takeFlag(args, "--sound");
|
|
637
640
|
const noSound = takeFlag(args, "--no-sound");
|
|
638
|
-
if (explicitSound && noSound) throw
|
|
639
|
-
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.");
|
|
640
643
|
const newTurn = takeFlag(args, "--new-turn");
|
|
641
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) };
|
|
642
|
-
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.");
|
|
643
646
|
const keepFocus = takeFlag(args, "--keep-focus");
|
|
644
647
|
const waitInput = takeOption(args, "--wait-ms", undefined);
|
|
645
|
-
if (backend !== "ui" && (keepFocus || waitInput != null)) throw
|
|
648
|
+
if (backend !== "ui" && (keepFocus || waitInput != null)) throw usage("--keep-focus and --wait-ms require --backend ui.");
|
|
646
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 --.");
|
|
647
651
|
const threadInput = args.shift();
|
|
648
|
-
if (!threadInput) throw
|
|
652
|
+
if (!threadInput) throw usage("send requires a thread ID or codex://threads URL.");
|
|
649
653
|
const message = readMessage(args);
|
|
650
|
-
if (message.trim() === "") throw
|
|
654
|
+
if (message.trim() === "") throw usage("send requires a non-empty message.");
|
|
651
655
|
const threadId = normalizeThreadId(threadInput);
|
|
652
656
|
const data = backend === "app-server"
|
|
653
657
|
? await sendTrackedMessage(threadId, message, { dryRun, newTurn, ...directive, supervisor, connection })
|
package/src/cloud-cli.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
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";
|
|
11
13
|
import { notificationOptions } from "./notify.mjs";
|
|
12
14
|
import { prepareSupervisorPrompt, superviseAgent } from "./supervise.mjs";
|
|
13
|
-
import { supervisorId, registerSupervisor, getSupervisor, assertSupervisor, controlSupervisor, supervisorStatus, listSupervisors } from "./supervision.mjs";
|
|
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");
|
|
@@ -84,9 +96,8 @@ export async function mainCloud(argv) {
|
|
|
84
96
|
const bound = async (target, { allowUnregistered = false } = {}) => {
|
|
85
97
|
const id = targetId(target);
|
|
86
98
|
if (supervisor) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
else if (record && record.state !== "stopped") throw failure("Another supervisor owns this cloud task.", "SUPERVISOR_CONFLICT");
|
|
99
|
+
if (allowUnregistered) await assertSupervisorRegistration(id, supervisor, { connection });
|
|
100
|
+
else await assertSupervisor(id, supervisor, { connection });
|
|
90
101
|
}
|
|
91
102
|
return id;
|
|
92
103
|
};
|
|
@@ -160,11 +171,12 @@ export async function mainCloud(argv) {
|
|
|
160
171
|
const settings = { taskId: id };
|
|
161
172
|
output(`history.${action}`, action === "show" ? await cloudSendReceipt(attempt, settings) : await reconcileCloudSend(attempt, settings)); return;
|
|
162
173
|
}
|
|
163
|
-
const includeText = flag(args, "--include-text"), pending = flag(args, "--pending");
|
|
164
|
-
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") } : {};
|
|
165
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");
|
|
166
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)
|
|
167
|
-
: 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);
|
|
168
180
|
output(`history.${action}`, data); return;
|
|
169
181
|
}
|
|
170
182
|
if (command === "instructions") {
|
|
@@ -301,7 +313,8 @@ async function desktopCloudCommands(command, args, { supervisor, output, bound,
|
|
|
301
313
|
output("thread.create", await createDesktopCloudTask(environment, message(args), { project, title, dryRun })); return true;
|
|
302
314
|
}
|
|
303
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; }
|
|
304
|
-
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; }
|
|
305
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; }
|
|
306
319
|
if (action === "resolve") { const id = await bound(args[0]); output("thread.resolve", { thread_id: id, deep_link: desktopCloudLink(id) }); return true; }
|
|
307
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);
|