cueline 0.1.3 → 0.1.5
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/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/CHANGELOG.md +36 -0
- package/README.ja.md +57 -22
- package/README.ko.md +57 -22
- package/README.md +64 -24
- package/README.zh-CN.md +57 -22
- package/README.zh-TW.md +60 -23
- package/dist/src/api-contracts.d.ts +77 -0
- package/dist/src/api-contracts.js +2 -0
- package/dist/src/api-contracts.js.map +1 -0
- package/dist/src/api-controller-handoff.d.ts +6 -0
- package/dist/src/api-controller-handoff.js +272 -0
- package/dist/src/api-controller-handoff.js.map +1 -0
- package/dist/src/api-runtime-lifecycle.d.ts +22 -0
- package/dist/src/api-runtime-lifecycle.js +564 -0
- package/dist/src/api-runtime-lifecycle.js.map +1 -0
- package/dist/src/api.d.ts +6 -46
- package/dist/src/api.js +125 -133
- package/dist/src/api.js.map +1 -1
- package/dist/src/browser/browser-adapter.d.ts +15 -1
- package/dist/src/browser/codex-iab/bootstrap.d.ts +11 -0
- package/dist/src/browser/codex-iab/bootstrap.js +56 -0
- package/dist/src/browser/codex-iab/bootstrap.js.map +1 -1
- package/dist/src/browser/codex-iab/chatgpt-client.js +243 -156
- package/dist/src/browser/codex-iab/chatgpt-client.js.map +1 -1
- package/dist/src/browser/codex-iab/recovery-evidence.d.ts +6 -0
- package/dist/src/browser/codex-iab/recovery-evidence.js +37 -0
- package/dist/src/browser/codex-iab/recovery-evidence.js.map +1 -0
- package/dist/src/browser/codex-iab/submission-url.d.ts +2 -0
- package/dist/src/browser/codex-iab/submission-url.js +57 -0
- package/dist/src/browser/codex-iab/submission-url.js.map +1 -0
- package/dist/src/browser/codex-iab/tab-discovery.d.ts +3 -0
- package/dist/src/browser/codex-iab/tab-discovery.js +61 -0
- package/dist/src/browser/codex-iab/tab-discovery.js.map +1 -0
- package/dist/src/cli/main.js +200 -45
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/core/controller-abort.d.ts +1 -0
- package/dist/src/core/controller-abort.js +11 -0
- package/dist/src/core/controller-abort.js.map +1 -0
- package/dist/src/core/controller-command-execution.d.ts +9 -0
- package/dist/src/core/controller-command-execution.js +299 -0
- package/dist/src/core/controller-command-execution.js.map +1 -0
- package/dist/src/core/controller-loop.d.ts +3 -43
- package/dist/src/core/controller-loop.js +261 -334
- package/dist/src/core/controller-loop.js.map +1 -1
- package/dist/src/core/controller-turn.d.ts +16 -0
- package/dist/src/core/controller-turn.js +315 -0
- package/dist/src/core/controller-turn.js.map +1 -0
- package/dist/src/core/controller-types.d.ts +58 -0
- package/dist/src/core/controller-types.js +2 -0
- package/dist/src/core/controller-types.js.map +1 -0
- package/dist/src/core/persisted-run.d.ts +4 -0
- package/dist/src/core/persisted-run.js +19 -0
- package/dist/src/core/persisted-run.js.map +1 -0
- package/dist/src/core/process-liveness.d.ts +2 -0
- package/dist/src/core/process-liveness.js +44 -0
- package/dist/src/core/process-liveness.js.map +1 -0
- package/dist/src/core/run-status.d.ts +6 -3
- package/dist/src/core/run-status.js +75 -10
- package/dist/src/core/run-status.js.map +1 -1
- package/dist/src/core/state-machine.d.ts +17 -3
- package/dist/src/core/state-machine.js +163 -15
- package/dist/src/core/state-machine.js.map +1 -1
- package/dist/src/jobs/status.d.ts +1 -1
- package/dist/src/jobs/supervisor.js +3 -1
- package/dist/src/jobs/supervisor.js.map +1 -1
- package/dist/src/router/resolver.d.ts +2 -1
- package/dist/src/router/resolver.js +31 -19
- package/dist/src/router/resolver.js.map +1 -1
- package/dist/src/runners/process-runner.js +59 -6
- package/dist/src/runners/process-runner.js.map +1 -1
- package/dist/src/state/atomic-write.js +17 -2
- package/dist/src/state/atomic-write.js.map +1 -1
- package/dist/src/state/cancellation.d.ts +1 -1
- package/dist/src/state/cancellation.js +10 -2
- package/dist/src/state/cancellation.js.map +1 -1
- package/dist/src/state/event-log.d.ts +17 -1
- package/dist/src/state/event-log.js +328 -18
- package/dist/src/state/event-log.js.map +1 -1
- package/dist/src/state/paths.d.ts +1 -0
- package/dist/src/state/paths.js +1 -0
- package/dist/src/state/paths.js.map +1 -1
- package/dist/src/state/runtime-lease.d.ts +25 -0
- package/dist/src/state/runtime-lease.js +607 -59
- package/dist/src/state/runtime-lease.js.map +1 -1
- package/dist/src/state/runtime-retirement.d.ts +16 -0
- package/dist/src/state/runtime-retirement.js +95 -0
- package/dist/src/state/runtime-retirement.js.map +1 -0
- package/dist/src/state/runtime-takeover-intent.d.ts +1 -0
- package/dist/src/state/runtime-takeover-intent.js +18 -0
- package/dist/src/state/runtime-takeover-intent.js.map +1 -0
- package/dist/src/state/store.d.ts +12 -1
- package/dist/src/state/store.js +246 -40
- package/dist/src/state/store.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/architecture.md +16 -11
- package/docs/assets/cueline-loop-en.svg +11 -10
- package/docs/assets/cueline-loop-ja.svg +11 -10
- package/docs/assets/cueline-loop-ko.svg +11 -10
- package/docs/assets/cueline-loop-zh-CN.svg +11 -10
- package/docs/assets/cueline-loop-zh-TW.svg +11 -10
- package/docs/compatibility.md +15 -9
- package/docs/controller-protocol.md +7 -5
- package/docs/runner-contract.md +19 -5
- package/docs/state-and-recovery.md +52 -22
- package/package.json +2 -2
- package/skills/cueline/SKILL.md +66 -17
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExpectedControllerIdentity } from "../../protocol/types.js";
|
|
2
|
+
export declare function normalizedConversationUrl(url: string): string;
|
|
3
|
+
export declare function isProLabel(label: string | null): label is string;
|
|
4
|
+
export declare function isProModelSlug(slug: string | null): slug is string;
|
|
5
|
+
export declare function normalizedMessageText(value: string | null): string;
|
|
6
|
+
export declare function hasExactControllerEnvelopeIdentity(text: string, expected: ExpectedControllerIdentity): boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const CONTROL_ENVELOPE = /<CueLineControl>([\s\S]*?)<\/CueLineControl>/g;
|
|
2
|
+
export function normalizedConversationUrl(url) {
|
|
3
|
+
try {
|
|
4
|
+
const parsed = new URL(url);
|
|
5
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return url;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function isProLabel(label) {
|
|
12
|
+
return /^Pro(?:\s+(?:Standard|Extended))?$/i.test(label ?? "");
|
|
13
|
+
}
|
|
14
|
+
export function isProModelSlug(slug) {
|
|
15
|
+
return /^gpt-\d+(?:[.-]\d+)*-pro$/i.test(slug ?? "");
|
|
16
|
+
}
|
|
17
|
+
export function normalizedMessageText(value) {
|
|
18
|
+
return (value ?? "").replace(/\r\n/g, "\n").trim();
|
|
19
|
+
}
|
|
20
|
+
export function hasExactControllerEnvelopeIdentity(text, expected) {
|
|
21
|
+
let body;
|
|
22
|
+
for (const match of text.matchAll(CONTROL_ENVELOPE))
|
|
23
|
+
body = match[1];
|
|
24
|
+
if (body === undefined)
|
|
25
|
+
return false;
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(body.trim());
|
|
28
|
+
return (parsed.protocol === "cueline/0.1" &&
|
|
29
|
+
parsed.run_id === expected.runId &&
|
|
30
|
+
parsed.round === expected.round &&
|
|
31
|
+
parsed.request_id === expected.requestId);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=recovery-evidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-evidence.js","sourceRoot":"","sources":["../../../../src/browser/codex-iab/recovery-evidence.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,+CAA+C,CAAC;AAEzE,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACnD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAoB;IAC7C,OAAO,qCAAqC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAmB;IAChD,OAAO,4BAA4B,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAoB;IACxD,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,kCAAkC,CAChD,IAAY,EACZ,QAAoC;IAEpC,IAAI,IAAwB,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACrE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAA4B,CAAC;QAClE,OAAO,CACL,MAAM,CAAC,QAAQ,KAAK,aAAa;YACjC,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,KAAK;YAChC,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,KAAK;YAC/B,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,SAAS,CACzC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CueLineError } from "../../core/errors.js";
|
|
2
|
+
// ChatGPT can spend several seconds converting a large composer payload into
|
|
3
|
+
// an attachment before the new `/c/...` route becomes visible. Keep this below
|
|
4
|
+
// the common 30-second outer tool window while leaving enough time for that
|
|
5
|
+
// real transition.
|
|
6
|
+
const CAPTURE_TIMEOUT_MS = 15_000;
|
|
7
|
+
function isConversationUrl(url) {
|
|
8
|
+
return /^https:\/\/chatgpt\.com\/c\/[A-Za-z0-9-]+/.test(url);
|
|
9
|
+
}
|
|
10
|
+
function normalizedConversationUrl(url) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(url);
|
|
13
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return url;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function wait(milliseconds, signal) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
if (signal?.aborted === true) {
|
|
22
|
+
reject(signal.reason ?? new CueLineError("RUN_CANCELLED", "Run cancelled."));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const onAbort = () => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
reject(signal?.reason ?? new CueLineError("RUN_CANCELLED", "Run cancelled."));
|
|
28
|
+
};
|
|
29
|
+
const timer = setTimeout(() => {
|
|
30
|
+
signal?.removeEventListener("abort", onAbort);
|
|
31
|
+
resolve();
|
|
32
|
+
}, milliseconds);
|
|
33
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function captureConversationUrlAfterSubmit(tab, knownUrl, timeoutMs, pollIntervalMs, signal) {
|
|
37
|
+
const expectedUrl = knownUrl !== undefined && isConversationUrl(knownUrl) ? knownUrl : undefined;
|
|
38
|
+
const deadline = Date.now() + Math.min(timeoutMs, CAPTURE_TIMEOUT_MS);
|
|
39
|
+
do {
|
|
40
|
+
const currentUrl = (await tab.url().catch(() => "")) ?? "";
|
|
41
|
+
if (isConversationUrl(currentUrl)) {
|
|
42
|
+
if (expectedUrl !== undefined &&
|
|
43
|
+
normalizedConversationUrl(currentUrl) !== normalizedConversationUrl(expectedUrl)) {
|
|
44
|
+
throw new CueLineError("CONTROLLER_RECONCILIATION_CONVERSATION_MISMATCH", "ChatGPT navigated to a different conversation after the single send click. Refusing to bind either conversation or click again.", {
|
|
45
|
+
details: {
|
|
46
|
+
expected_conversation_url: expectedUrl,
|
|
47
|
+
observed_conversation_url: currentUrl,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return expectedUrl ?? currentUrl;
|
|
52
|
+
}
|
|
53
|
+
await wait(Math.min(pollIntervalMs, 100), signal);
|
|
54
|
+
} while (Date.now() < deadline);
|
|
55
|
+
throw new CueLineError("CONTROLLER_CONVERSATION_URL_UNAVAILABLE", "ChatGPT accepted the single send click but did not expose an exact conversation URL in time. Refusing to return an unrecoverable controller wait or click again.");
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=submission-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submission-url.js","sourceRoot":"","sources":["../../../../src/browser/codex-iab/submission-url.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { normalizedConversationUrl } from "./recovery-evidence.js";
|
|
2
|
+
import { CHATGPT_URL } from "./selectors.js";
|
|
3
|
+
const TAB_DISCOVERY_RETRY_MS = 100;
|
|
4
|
+
export function isTabUnavailableError(error) {
|
|
5
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6
|
+
return /tab not found|existing tabs: none|webview.*attach|cdp operation exceeded|target closed|page closed|browser.*disconnected/i.test(message);
|
|
7
|
+
}
|
|
8
|
+
export async function acquireChatGptTab(browser, conversationUrl) {
|
|
9
|
+
const matchesTarget = (url) => conversationUrl === undefined
|
|
10
|
+
? url.startsWith(CHATGPT_URL)
|
|
11
|
+
: normalizedConversationUrl(url) === normalizedConversationUrl(conversationUrl);
|
|
12
|
+
const canDiscover = browser.tabs.selected !== undefined ||
|
|
13
|
+
browser.tabs.list !== undefined ||
|
|
14
|
+
browser.user?.openTabs !== undefined;
|
|
15
|
+
const discoveryPasses = canDiscover ? 2 : 1;
|
|
16
|
+
let discoveryError;
|
|
17
|
+
for (let pass = 0; pass < discoveryPasses; pass += 1) {
|
|
18
|
+
try {
|
|
19
|
+
const selected = await browser.tabs.selected?.();
|
|
20
|
+
if (selected && matchesTarget((await selected.url()) ?? "")) {
|
|
21
|
+
await waitUntilLoaded(selected);
|
|
22
|
+
return selected;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (!isTabUnavailableError(error))
|
|
27
|
+
throw error;
|
|
28
|
+
discoveryError = error;
|
|
29
|
+
}
|
|
30
|
+
const sessionTabs = (await browser.tabs.list?.()) ?? [];
|
|
31
|
+
const sessionCandidate = sessionTabs.find((candidate) => matchesTarget(String(candidate.url ?? "")));
|
|
32
|
+
if (sessionCandidate?.id && browser.tabs.get) {
|
|
33
|
+
const tab = await browser.tabs.get(sessionCandidate.id);
|
|
34
|
+
await waitUntilLoaded(tab);
|
|
35
|
+
return tab;
|
|
36
|
+
}
|
|
37
|
+
const openTabs = (await browser.user?.openTabs?.()) ?? [];
|
|
38
|
+
const userCandidate = openTabs.find((candidate) => matchesTarget(String(candidate.url ?? "")));
|
|
39
|
+
if (userCandidate && browser.user?.claimTab) {
|
|
40
|
+
const tab = await browser.user.claimTab(userCandidate);
|
|
41
|
+
await waitUntilLoaded(tab);
|
|
42
|
+
return tab;
|
|
43
|
+
}
|
|
44
|
+
if (pass < discoveryPasses - 1) {
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, TAB_DISCOVERY_RETRY_MS));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (discoveryError !== undefined)
|
|
49
|
+
throw discoveryError;
|
|
50
|
+
const tab = await browser.tabs.new();
|
|
51
|
+
await tab.goto(conversationUrl ?? CHATGPT_URL);
|
|
52
|
+
await waitUntilLoaded(tab);
|
|
53
|
+
return tab;
|
|
54
|
+
}
|
|
55
|
+
async function waitUntilLoaded(tab) {
|
|
56
|
+
await tab.playwright.waitForLoadState?.({
|
|
57
|
+
state: "domcontentloaded",
|
|
58
|
+
timeoutMs: 30_000,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=tab-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-discovery.js","sourceRoot":"","sources":["../../../../src/browser/codex-iab/tab-discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/src/cli/main.js
CHANGED
|
@@ -3,14 +3,14 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
import { cancelCueLineJob, cancelCueLineRun, loadCueLineRunStatus, routingConfigPath, } from "../api.js";
|
|
6
|
+
import { cancelCueLineJob, cancelCueLineRun, confirmManualControllerSubmission, loadCueLineRunStatus, reconcileCueLineRuntime, routingConfigPath, takeoverCueLineRuntime, } from "../api.js";
|
|
7
7
|
import { CueLineError } from "../core/errors.js";
|
|
8
8
|
import { executableAvailability } from "../router/availability.js";
|
|
9
9
|
import { loadRoutingConfig } from "../router/config-loader.js";
|
|
10
10
|
import { resolveRoute } from "../router/resolver.js";
|
|
11
11
|
import { defaultCueLineHome } from "../state/paths.js";
|
|
12
12
|
import { readRuntimeLease } from "../state/runtime-lease.js";
|
|
13
|
-
import {
|
|
13
|
+
import { readAuthoritativeRunEvents } from "../state/store.js";
|
|
14
14
|
import { CUELINE_VERSION } from "../version.js";
|
|
15
15
|
import { installSkill, uninstallSkill } from "./skill-links.js";
|
|
16
16
|
const processIo = {
|
|
@@ -18,7 +18,7 @@ const processIo = {
|
|
|
18
18
|
stderr: (line) => process.stderr.write(`${line}\n`),
|
|
19
19
|
};
|
|
20
20
|
function usage() {
|
|
21
|
-
return "usage: cueline <install|uninstall|doctor|routing|jobs|run status|run cancel|run stop|job cancel|api path|config path|help|version>";
|
|
21
|
+
return "usage: cueline <install|uninstall|doctor|routing|jobs|run status|run reconcile|run takeover|run reconcile-runtime|run cancel|run stop|job cancel|api path|config path|help|version>";
|
|
22
22
|
}
|
|
23
23
|
function help() {
|
|
24
24
|
return [
|
|
@@ -29,10 +29,13 @@ function help() {
|
|
|
29
29
|
"commands:",
|
|
30
30
|
" install link the bundled skill into Codex",
|
|
31
31
|
" uninstall remove only the skill link owned by this package",
|
|
32
|
-
" doctor report Node,
|
|
32
|
+
" doctor report Node, caller readiness, state home, and process lanes",
|
|
33
33
|
" routing list every lane and the candidate that would be selected",
|
|
34
34
|
" jobs list persisted local jobs with run, key, lane, mode, and PID",
|
|
35
35
|
" run status summarize one persisted run for safe cross-session handoff",
|
|
36
|
+
" run reconcile confirm one manually sent controller turn; never resends it",
|
|
37
|
+
" run takeover explicitly retire one exact stale runtime owner",
|
|
38
|
+
" run reconcile-runtime settle dead ownerless workers from persisted evidence",
|
|
36
39
|
" run cancel request safe cancellation; ownerless work becomes ambiguous",
|
|
37
40
|
" run stop alias for `run cancel`",
|
|
38
41
|
" job cancel request cancellation for one job in one run",
|
|
@@ -41,6 +44,24 @@ function help() {
|
|
|
41
44
|
" help print this text",
|
|
42
45
|
" version print the CueLine version",
|
|
43
46
|
"",
|
|
47
|
+
"command syntax:",
|
|
48
|
+
" cueline install",
|
|
49
|
+
" cueline uninstall",
|
|
50
|
+
" cueline doctor",
|
|
51
|
+
" cueline routing",
|
|
52
|
+
" cueline jobs [--json]",
|
|
53
|
+
" cueline run status <run-id> [--json]",
|
|
54
|
+
" cueline run reconcile <run-id> --request-id <request-id> --manual-send-confirmed [--conversation-url <url>] [--json]",
|
|
55
|
+
" cueline run takeover <run-id> [--json]",
|
|
56
|
+
" cueline run reconcile-runtime <run-id> [--json]",
|
|
57
|
+
" cueline run cancel <run-id> [--json]",
|
|
58
|
+
" cueline run stop <run-id> [--json]",
|
|
59
|
+
" cueline job cancel <run-id> <job-id> [--json]",
|
|
60
|
+
" cueline api path",
|
|
61
|
+
" cueline config path",
|
|
62
|
+
" cueline help",
|
|
63
|
+
" cueline version",
|
|
64
|
+
"",
|
|
44
65
|
"flags:",
|
|
45
66
|
" -h, --help same as `cueline help`",
|
|
46
67
|
" -v, --version same as `cueline version`",
|
|
@@ -54,8 +75,14 @@ function help() {
|
|
|
54
75
|
" 1 the command ran but the surface is degraded or unreadable",
|
|
55
76
|
" 2 the arguments were not understood",
|
|
56
77
|
"",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
78
|
+
"state effects:",
|
|
79
|
+
" Read-only: doctor, routing, jobs, run status, api path, config path, help, version.",
|
|
80
|
+
" Local setup: install and uninstall change only the package-owned skill link.",
|
|
81
|
+
" Durable state writes: run reconcile, takeover, reconcile-runtime, cancel/stop,",
|
|
82
|
+
" and job cancel append evidence or change local run/job state.",
|
|
83
|
+
"",
|
|
84
|
+
"No CLI command drives the browser. A live run is driven through the imported API",
|
|
85
|
+
"inside Codex, where the built-in Browser lives.",
|
|
59
86
|
].join("\n");
|
|
60
87
|
}
|
|
61
88
|
function errorMessage(error) {
|
|
@@ -83,7 +110,7 @@ async function routingCommand(environment, io) {
|
|
|
83
110
|
}
|
|
84
111
|
return available > 0 ? 0 : 1;
|
|
85
112
|
}
|
|
86
|
-
async function
|
|
113
|
+
async function readRunJobMetadata(home) {
|
|
87
114
|
const metadata = new Map();
|
|
88
115
|
let entries;
|
|
89
116
|
try {
|
|
@@ -97,33 +124,59 @@ async function readLegacyJobMetadata(home) {
|
|
|
97
124
|
for (const entry of entries) {
|
|
98
125
|
if (!entry.isDirectory())
|
|
99
126
|
continue;
|
|
100
|
-
const events = await
|
|
127
|
+
const events = await readAuthoritativeRunEvents(home, entry.name);
|
|
101
128
|
for (const event of events) {
|
|
102
|
-
if (event.
|
|
103
|
-
typeof event.payload !== "object" ||
|
|
129
|
+
if (typeof event.payload !== "object" ||
|
|
104
130
|
event.payload === null ||
|
|
105
|
-
Array.isArray(event.payload))
|
|
131
|
+
Array.isArray(event.payload))
|
|
106
132
|
continue;
|
|
133
|
+
const payload = event.payload;
|
|
134
|
+
if (event.type === "job_registered") {
|
|
135
|
+
const job = payload.job;
|
|
136
|
+
if (typeof job !== "object" || job === null || Array.isArray(job))
|
|
137
|
+
continue;
|
|
138
|
+
const record = job;
|
|
139
|
+
const spec = typeof record.spec === "object" && record.spec !== null && !Array.isArray(record.spec)
|
|
140
|
+
? record.spec
|
|
141
|
+
: {};
|
|
142
|
+
if (typeof record.jobId !== "string" ||
|
|
143
|
+
typeof record.jobKey !== "string" ||
|
|
144
|
+
typeof spec.lane !== "string" ||
|
|
145
|
+
typeof spec.task !== "string" ||
|
|
146
|
+
(spec.mode !== "advise" && spec.mode !== "work"))
|
|
147
|
+
continue;
|
|
148
|
+
metadata.set(record.jobId, {
|
|
149
|
+
jobId: record.jobId,
|
|
150
|
+
runId: entry.name,
|
|
151
|
+
jobKey: record.jobKey,
|
|
152
|
+
lane: spec.lane,
|
|
153
|
+
mode: spec.mode,
|
|
154
|
+
task: spec.task,
|
|
155
|
+
execution: "foreground",
|
|
156
|
+
status: "pending",
|
|
157
|
+
startedAt: event.timestamp,
|
|
158
|
+
});
|
|
107
159
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
else if (event.type === "job_status" && typeof payload.job_id === "string") {
|
|
161
|
+
const existing = metadata.get(payload.job_id);
|
|
162
|
+
const status = payload.status;
|
|
163
|
+
if (existing === undefined ||
|
|
164
|
+
(status !== "pending" &&
|
|
165
|
+
status !== "running" &&
|
|
166
|
+
status !== "succeeded" &&
|
|
167
|
+
status !== "failed" &&
|
|
168
|
+
status !== "timed_out" &&
|
|
169
|
+
status !== "cancelled" &&
|
|
170
|
+
status !== "ambiguous"))
|
|
171
|
+
continue;
|
|
172
|
+
metadata.set(payload.job_id, {
|
|
173
|
+
...existing,
|
|
174
|
+
status,
|
|
175
|
+
...(status === "pending" || status === "running"
|
|
176
|
+
? {}
|
|
177
|
+
: { finishedAt: event.timestamp }),
|
|
178
|
+
});
|
|
120
179
|
}
|
|
121
|
-
metadata.set(record.jobId, {
|
|
122
|
-
runId: entry.name,
|
|
123
|
-
jobKey: record.jobKey,
|
|
124
|
-
lane: spec.lane,
|
|
125
|
-
mode: spec.mode,
|
|
126
|
-
});
|
|
127
180
|
}
|
|
128
181
|
}
|
|
129
182
|
return metadata;
|
|
@@ -136,8 +189,9 @@ async function readJobs(home) {
|
|
|
136
189
|
}
|
|
137
190
|
catch (error) {
|
|
138
191
|
if (error.code === "ENOENT")
|
|
139
|
-
|
|
140
|
-
|
|
192
|
+
names = [];
|
|
193
|
+
else
|
|
194
|
+
throw error;
|
|
141
195
|
}
|
|
142
196
|
const statuses = [];
|
|
143
197
|
for (const name of names.filter((candidate) => candidate.endsWith(".json")).sort()) {
|
|
@@ -150,13 +204,31 @@ async function readJobs(home) {
|
|
|
150
204
|
});
|
|
151
205
|
}
|
|
152
206
|
}
|
|
153
|
-
const
|
|
207
|
+
const runMetadata = await readRunJobMetadata(home);
|
|
208
|
+
const persistedById = new Map(statuses.map((status) => [status.jobId, status]));
|
|
209
|
+
for (const [jobId, metadata] of runMetadata) {
|
|
210
|
+
if (!persistedById.has(jobId))
|
|
211
|
+
statuses.push(metadata);
|
|
212
|
+
}
|
|
154
213
|
const ownership = new Map();
|
|
155
214
|
const listed = [];
|
|
156
215
|
for (const persisted of statuses) {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
216
|
+
const authoritative = runMetadata.get(persisted.jobId);
|
|
217
|
+
const conflict = authoritative !== undefined && authoritative.status !== persisted.status;
|
|
218
|
+
const status = conflict
|
|
219
|
+
? {
|
|
220
|
+
...Object.fromEntries(Object.entries(persisted).filter(([key]) => key !== "result" && key !== "error" && key !== "finishedAt")),
|
|
221
|
+
...authoritative,
|
|
222
|
+
execution: persisted.execution,
|
|
223
|
+
startedAt: persisted.startedAt,
|
|
224
|
+
}
|
|
225
|
+
: {
|
|
226
|
+
...authoritative,
|
|
227
|
+
...persisted,
|
|
228
|
+
status: authoritative?.status ?? persisted.status,
|
|
229
|
+
};
|
|
230
|
+
let observedStatus = conflict ? "conflict" : status.status;
|
|
231
|
+
if (!conflict && status.status === "running") {
|
|
160
232
|
if (status.runId === undefined) {
|
|
161
233
|
observedStatus = "unverified";
|
|
162
234
|
}
|
|
@@ -169,7 +241,11 @@ async function readJobs(home) {
|
|
|
169
241
|
observedStatus = runtime.ownership === "active" ? "running" : "orphaned";
|
|
170
242
|
}
|
|
171
243
|
}
|
|
172
|
-
listed.push({
|
|
244
|
+
listed.push({
|
|
245
|
+
...status,
|
|
246
|
+
...(conflict ? { persistedStatus: persisted.status } : {}),
|
|
247
|
+
observedStatus,
|
|
248
|
+
});
|
|
173
249
|
}
|
|
174
250
|
return listed.sort((left, right) => left.startedAt.localeCompare(right.startedAt));
|
|
175
251
|
}
|
|
@@ -184,7 +260,7 @@ async function jobsCommand(json, environment, io) {
|
|
|
184
260
|
return 0;
|
|
185
261
|
}
|
|
186
262
|
for (const job of jobs) {
|
|
187
|
-
io.stdout(`${job.jobId}\t${job.runId ?? "-"}\t${job.jobKey ?? "-"}\t${job.lane ?? "-"}\t${job.mode ?? "-"}\t${job.pid ?? "-"}\t${job.execution}\t${job.observedStatus}\t${job.startedAt}`);
|
|
263
|
+
io.stdout(`${job.jobId}\t${job.runId ?? "-"}\t${job.jobKey ?? "-"}\t${job.lane ?? "-"}\t${job.mode ?? "-"}\t${job.pid ?? "-"}\t${job.execution}\t${job.observedStatus}\t${job.startedAt}\ttask=${JSON.stringify(job.task ?? "")}`);
|
|
188
264
|
}
|
|
189
265
|
return 0;
|
|
190
266
|
}
|
|
@@ -203,6 +279,7 @@ async function runStatusCommand(runId, json, environment, io) {
|
|
|
203
279
|
io.stdout(`run\t${status.runId}`);
|
|
204
280
|
io.stdout(`version\t${CUELINE_VERSION}`);
|
|
205
281
|
io.stdout(`status\t${status.status}`);
|
|
282
|
+
io.stdout(`executor\t${status.executor}`);
|
|
206
283
|
io.stdout(`phase\t${status.phase}`);
|
|
207
284
|
io.stdout(`runtime\t${status.runtime.ownership}${status.runtime.heartbeatAt === undefined
|
|
208
285
|
? ""
|
|
@@ -211,7 +288,7 @@ async function runStatusCommand(runId, json, environment, io) {
|
|
|
211
288
|
io.stdout(`controller\t${controller}\tpending=${status.controller.pendingTurns}\taccepted_commands=${status.controller.acceptedCommands}\tlast_action=${status.controller.lastAcceptedAction ?? "-"}\tlast_jobs=${status.controller.lastAcceptedJobKeys.length}`);
|
|
212
289
|
io.stdout(`jobs\ttotal=${status.jobs.total}\tpending=${counts.pending}\trunning=${counts.running}\tsucceeded=${counts.succeeded}\tfailed=${counts.failed}\ttimed_out=${counts.timed_out}\torphaned=${counts.orphaned}\tcancelled=${counts.cancelled}\tambiguous=${counts.ambiguous}`);
|
|
213
290
|
for (const job of status.jobs.items) {
|
|
214
|
-
io.stdout(`job\t${job.jobId}\t${job.jobKey}\t${job.status}\t${job.mode}\t${job.lane}\trequired=${job.required}\tpersisted=${job.persistedStatus}`);
|
|
291
|
+
io.stdout(`job\t${job.jobId}\t${job.jobKey}\t${job.status}\t${job.mode}\t${job.lane}\trequired=${job.required}\tpersisted=${job.persistedStatus}\ttask=${JSON.stringify(job.task)}`);
|
|
215
292
|
}
|
|
216
293
|
io.stdout(`cancellation\trun=${status.cancellation.runRequested ? "requested" : "none"}\tjobs=${status.cancellation.jobRequests.length}`);
|
|
217
294
|
io.stdout(`continue\t${status.continueAllowed ? "allowed" : "forbidden"}`);
|
|
@@ -225,26 +302,30 @@ async function doctorCommand(environment, io) {
|
|
|
225
302
|
const nodeOk = major >= 22;
|
|
226
303
|
const config = await loadRoutingConfig(configPath);
|
|
227
304
|
const availability = executableAvailability(environment);
|
|
228
|
-
let
|
|
305
|
+
let callerLanes = 0;
|
|
306
|
+
let processAvailableLanes = 0;
|
|
229
307
|
for (const [lane, laneConfig] of Object.entries(config.lanes)) {
|
|
230
308
|
if (!laneConfig.enabled)
|
|
231
309
|
continue;
|
|
310
|
+
callerLanes += 1;
|
|
232
311
|
try {
|
|
233
312
|
resolveRoute(lane, config, availability);
|
|
234
|
-
|
|
313
|
+
processAvailableLanes += 1;
|
|
235
314
|
}
|
|
236
315
|
catch {
|
|
237
316
|
// Doctor reports the aggregate below; `cueline routing` shows lane details.
|
|
238
317
|
}
|
|
239
318
|
}
|
|
240
|
-
const
|
|
319
|
+
const callerReady = nodeOk && callerLanes > 0;
|
|
241
320
|
io.stdout(`CueLine ${CUELINE_VERSION}`);
|
|
242
|
-
io.stdout(`status\t${
|
|
321
|
+
io.stdout(`status\t${callerReady ? "ok" : "degraded"}`);
|
|
243
322
|
io.stdout(`node\t${process.versions.node}\t${nodeOk ? "ok" : "requires >=22"}`);
|
|
244
323
|
io.stdout(`config\t${configPath}\tvalid`);
|
|
245
324
|
io.stdout(`home\t${home}`);
|
|
246
|
-
io.stdout(`
|
|
247
|
-
|
|
325
|
+
io.stdout(`caller_ready\t${callerReady ? "yes" : "no"}`);
|
|
326
|
+
io.stdout(`caller_lanes\t${callerLanes}`);
|
|
327
|
+
io.stdout(`process_available_lanes\t${processAvailableLanes}`);
|
|
328
|
+
return callerReady ? 0 : 1;
|
|
248
329
|
}
|
|
249
330
|
export async function main(args = process.argv.slice(2), environment = process.env, io = processIo) {
|
|
250
331
|
try {
|
|
@@ -256,6 +337,11 @@ export async function main(args = process.argv.slice(2), environment = process.e
|
|
|
256
337
|
io.stdout(help());
|
|
257
338
|
return 0;
|
|
258
339
|
}
|
|
340
|
+
if (args.length > 1 &&
|
|
341
|
+
args.some((argument) => argument === "--help" || argument === "-h")) {
|
|
342
|
+
io.stdout(help());
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
259
345
|
if (args.length === 1 && ["version", "--version", "-v"].includes(args[0])) {
|
|
260
346
|
io.stdout(CUELINE_VERSION);
|
|
261
347
|
return 0;
|
|
@@ -283,6 +369,75 @@ export async function main(args = process.argv.slice(2), environment = process.e
|
|
|
283
369
|
(args.length === 1 || (args.length === 2 && args[1] === "--json"))) {
|
|
284
370
|
return jobsCommand(args[1] === "--json", environment, io);
|
|
285
371
|
}
|
|
372
|
+
if (args[0] === "run" &&
|
|
373
|
+
args[1] === "takeover" &&
|
|
374
|
+
typeof args[2] === "string" &&
|
|
375
|
+
(args.length === 3 || (args.length === 4 && args[3] === "--json"))) {
|
|
376
|
+
const result = await takeoverCueLineRuntime(args[2], { environment });
|
|
377
|
+
if (args[3] === "--json")
|
|
378
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
379
|
+
else {
|
|
380
|
+
io.stdout(`${result.runId}\t${result.outcome}\tnext=${result.next}${result.previousOwnerId === undefined
|
|
381
|
+
? ""
|
|
382
|
+
: `\tprevious_owner=${result.previousOwnerId}`}`);
|
|
383
|
+
}
|
|
384
|
+
return 0;
|
|
385
|
+
}
|
|
386
|
+
if (args[0] === "run" &&
|
|
387
|
+
args[1] === "reconcile-runtime" &&
|
|
388
|
+
typeof args[2] === "string" &&
|
|
389
|
+
(args.length === 3 || (args.length === 4 && args[3] === "--json"))) {
|
|
390
|
+
const result = await reconcileCueLineRuntime(args[2], { environment });
|
|
391
|
+
if (args[3] === "--json")
|
|
392
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
393
|
+
else {
|
|
394
|
+
io.stdout(`${result.runId}\t${result.outcome}\taffected_jobs=${result.affectedJobs}\tsurviving_jobs=${result.survivingJobs.length}`);
|
|
395
|
+
}
|
|
396
|
+
return result.outcome === "owner_alive" || result.outcome === "processes_alive" ? 1 : 0;
|
|
397
|
+
}
|
|
398
|
+
if (args[0] === "run" &&
|
|
399
|
+
args[1] === "reconcile" &&
|
|
400
|
+
typeof args[2] === "string") {
|
|
401
|
+
let requestId;
|
|
402
|
+
let conversationUrl;
|
|
403
|
+
let manualConfirmed = false;
|
|
404
|
+
let json = false;
|
|
405
|
+
let valid = true;
|
|
406
|
+
for (let index = 3; index < args.length; index += 1) {
|
|
407
|
+
const argument = args[index];
|
|
408
|
+
if (argument === "--request-id" && typeof args[index + 1] === "string") {
|
|
409
|
+
requestId = args[index + 1];
|
|
410
|
+
index += 1;
|
|
411
|
+
}
|
|
412
|
+
else if (argument === "--conversation-url" &&
|
|
413
|
+
typeof args[index + 1] === "string") {
|
|
414
|
+
conversationUrl = args[index + 1];
|
|
415
|
+
index += 1;
|
|
416
|
+
}
|
|
417
|
+
else if (argument === "--manual-send-confirmed") {
|
|
418
|
+
manualConfirmed = true;
|
|
419
|
+
}
|
|
420
|
+
else if (argument === "--json") {
|
|
421
|
+
json = true;
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
valid = false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!valid || !requestId || !manualConfirmed) {
|
|
428
|
+
throw new CueLineError("CLI_ARGUMENTS_INVALID", "usage: cueline run reconcile <run-id> --request-id <request-id> --manual-send-confirmed [--conversation-url <url>] [--json]");
|
|
429
|
+
}
|
|
430
|
+
const result = await confirmManualControllerSubmission(args[2], {
|
|
431
|
+
environment,
|
|
432
|
+
requestId,
|
|
433
|
+
...(conversationUrl === undefined ? {} : { conversationUrl }),
|
|
434
|
+
});
|
|
435
|
+
if (json)
|
|
436
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
437
|
+
else
|
|
438
|
+
io.stdout(`${result.runId}\t${result.requestId}\t${result.outcome}\tno_resend`);
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
286
441
|
if (args[0] === "run" &&
|
|
287
442
|
args[1] === "status" &&
|
|
288
443
|
typeof args[2] === "string" &&
|
|
@@ -328,7 +483,7 @@ export async function main(args = process.argv.slice(2), environment = process.e
|
|
|
328
483
|
}
|
|
329
484
|
catch (error) {
|
|
330
485
|
io.stderr(`CueLine: ${errorMessage(error)}`);
|
|
331
|
-
return 1;
|
|
486
|
+
return error instanceof CueLineError && error.code === "CLI_ARGUMENTS_INVALID" ? 2 : 1;
|
|
332
487
|
}
|
|
333
488
|
}
|
|
334
489
|
if (process.argv[1] !== undefined &&
|