cueline 0.1.2 → 0.1.3
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.ja.md +15 -6
- package/README.ko.md +15 -6
- package/README.md +15 -6
- package/README.zh-CN.md +15 -6
- package/README.zh-TW.md +15 -6
- package/dist/src/api.d.ts +23 -1
- package/dist/src/api.js +110 -6
- package/dist/src/api.js.map +1 -1
- package/dist/src/browser/browser-adapter.d.ts +1 -0
- package/dist/src/browser/codex-iab/bootstrap.d.ts +6 -0
- package/dist/src/browser/codex-iab/bootstrap.js.map +1 -1
- package/dist/src/browser/codex-iab/chatgpt-client.js +138 -41
- package/dist/src/browser/codex-iab/chatgpt-client.js.map +1 -1
- package/dist/src/cli/main.js +150 -8
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/core/controller-loop.d.ts +6 -0
- package/dist/src/core/controller-loop.js +240 -74
- package/dist/src/core/controller-loop.js.map +1 -1
- package/dist/src/core/run-status.d.ts +50 -0
- package/dist/src/core/run-status.js +169 -0
- package/dist/src/core/run-status.js.map +1 -0
- package/dist/src/core/state-machine.d.ts +2 -1
- package/dist/src/core/state-machine.js +22 -1
- package/dist/src/core/state-machine.js.map +1 -1
- package/dist/src/jobs/status.d.ts +6 -0
- package/dist/src/jobs/status.js.map +1 -1
- package/dist/src/jobs/supervisor.d.ts +2 -0
- package/dist/src/jobs/supervisor.js +44 -12
- package/dist/src/jobs/supervisor.js.map +1 -1
- package/dist/src/protocol/types.d.ts +1 -1
- package/dist/src/protocol/validate-command.js +18 -0
- package/dist/src/protocol/validate-command.js.map +1 -1
- package/dist/src/router/resolver.js +7 -1
- package/dist/src/router/resolver.js.map +1 -1
- package/dist/src/runners/process-runner.d.ts +2 -2
- package/dist/src/runners/process-runner.js +56 -5
- package/dist/src/runners/process-runner.js.map +1 -1
- package/dist/src/runners/runner-adapter.d.ts +9 -2
- package/dist/src/runners/runner-adapter.js.map +1 -1
- package/dist/src/state/cancellation.d.ts +42 -0
- package/dist/src/state/cancellation.js +160 -0
- package/dist/src/state/cancellation.js.map +1 -0
- package/dist/src/state/paths.d.ts +3 -0
- package/dist/src/state/paths.js +3 -0
- package/dist/src/state/paths.js.map +1 -1
- package/dist/src/state/runtime-lease.d.ts +28 -0
- package/dist/src/state/runtime-lease.js +179 -0
- package/dist/src/state/runtime-lease.js.map +1 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/compatibility.md +3 -1
- package/docs/controller-protocol.md +4 -2
- package/docs/runner-contract.md +7 -1
- package/docs/state-and-recovery.md +34 -9
- package/package.json +3 -2
- package/schemas/controller-observation.schema.json +1 -1
- package/scripts/validate-plugin.mjs +66 -0
- package/skills/cueline/SKILL.md +33 -5
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const CUELINE_VERSION = "0.1.
|
|
1
|
+
export const CUELINE_VERSION = "0.1.3";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/docs/compatibility.md
CHANGED
|
@@ -26,10 +26,12 @@ The v0.1 adapter relies on accessible textbox/button roles and assistant-message
|
|
|
26
26
|
- text controller observations and commands
|
|
27
27
|
- `dispatch`, `wait`, `inspect`, `complete`, and `blocked`
|
|
28
28
|
- foreground and background local jobs
|
|
29
|
+
- concurrent all-`advise` batches and serialized batches containing `work`
|
|
29
30
|
- deterministic routing before spawn
|
|
30
31
|
- append-only run recovery and atomic snapshots
|
|
31
32
|
- continuation by run ID and stored conversation URL
|
|
32
33
|
- read-only persisted-state loading through `loadCueLineRunState`
|
|
34
|
+
- cross-session run status, runtime ownership, run/job cancellation, and optional run deadlines
|
|
33
35
|
- injected fake browser/runner for offline tests
|
|
34
36
|
|
|
35
37
|
## Not supported in v0.1
|
|
@@ -46,7 +48,7 @@ The v0.1 adapter relies on accessible textbox/button roles and assistant-message
|
|
|
46
48
|
|
|
47
49
|
## CLI boundary
|
|
48
50
|
|
|
49
|
-
`cueline doctor`, `routing`, `jobs`, and `config path` diagnose the local
|
|
51
|
+
`cueline doctor`, `routing`, `jobs`, `run status`, `run cancel` / `run stop`, `job cancel`, and `config path` diagnose or control the local runtime state. They do not drive the ChatGPT page. Live orchestration is an imported API run inside Codex so that the IAB browser object can be injected or resolved.
|
|
50
52
|
|
|
51
53
|
## Live readiness checklist
|
|
52
54
|
|
|
@@ -35,7 +35,7 @@ A stale or mismatched value is rejected. CueLine parses only the **last complete
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Job states are `pending`, `running`, `succeeded`, `failed`, `timed_out`, or `ambiguous`. Before browser submission, a single output/error field is bounded to 40,000 characters and only the most recent 20 notices are sent. The local event log remains the recovery record.
|
|
38
|
+
Job states are `pending`, `running`, `succeeded`, `failed`, `timed_out`, `cancelled`, or `ambiguous`. Before browser submission, a single output/error field is bounded to 40,000 characters and only the most recent 20 notices are sent. The local event log remains the recovery record.
|
|
39
39
|
|
|
40
40
|
## Command envelope
|
|
41
41
|
|
|
@@ -81,7 +81,9 @@ Schedules one or more local jobs.
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
`job_key` must be unique inside the command and match the supported identifier form. `mode` is `advise` or `work`. Optional fields are `required`, `timeout_ms`, `runner`, `workdir`, and `background`. The local runtime—not ChatGPT—resolves the configured executable. `lane` must be a listed available lane; a runner ID is not a lane name. When `runner` is supplied, it must name an enabled, available candidate in the selected lane. CueLine validates every new route in the dispatch before registering or starting any job. One invalid route rejects the whole command and requests a corrected envelope with the same pending identity. Repeating an already persisted deterministic job is ignored rather than spawned again.
|
|
84
|
+
`job_key` must be unique inside the command and match the supported identifier form. `mode` is `advise` or `work`. Optional fields are `required`, `timeout_ms`, `runner`, `workdir`, and `background`. `runner_id` is invalid and produces an explicit correction to use `runner`. The local runtime—not ChatGPT—resolves the configured executable. `lane` must be a listed available lane; a runner ID is not a lane name. When `runner` is supplied, it must name an enabled, available candidate in the selected lane. CueLine validates every new route in the dispatch before registering or starting any job. One invalid route rejects the whole command and requests a corrected envelope with the same pending identity. Repeating an already persisted deterministic job is ignored rather than spawned again.
|
|
85
|
+
|
|
86
|
+
If every job in one dispatch is `advise`, CueLine starts them concurrently and then reports all results. If any job is `work`, the entire dispatch remains serial in command order.
|
|
85
87
|
|
|
86
88
|
### `wait`
|
|
87
89
|
|
package/docs/runner-contract.md
CHANGED
|
@@ -13,6 +13,8 @@ Every process uses a `RunnerSpec`:
|
|
|
13
13
|
```ts
|
|
14
14
|
interface RunnerSpec {
|
|
15
15
|
jobId: string;
|
|
16
|
+
runId?: string;
|
|
17
|
+
jobKey?: string;
|
|
16
18
|
argv: readonly string[];
|
|
17
19
|
stdin?: string;
|
|
18
20
|
mode: "advise" | "work";
|
|
@@ -22,6 +24,7 @@ interface RunnerSpec {
|
|
|
22
24
|
task?: string;
|
|
23
25
|
cwd?: string;
|
|
24
26
|
env?: NodeJS.ProcessEnv;
|
|
27
|
+
signal?: AbortSignal;
|
|
25
28
|
}
|
|
26
29
|
```
|
|
27
30
|
|
|
@@ -35,6 +38,7 @@ interface RunnerSpec {
|
|
|
35
38
|
- The effective output is stdout, stderr, or both in that order.
|
|
36
39
|
- Exit code 0 is `succeeded`; a non-zero exit or spawn error is `failed`.
|
|
37
40
|
- At timeout CueLine sends `SIGTERM`, then schedules `SIGKILL` after 250 ms if needed; the result is `timed_out`.
|
|
41
|
+
- Cancellation uses the same owned-process termination path. `advise` becomes `cancelled`; started `work` becomes `ambiguous`.
|
|
38
42
|
- Empty output is explicitly recorded instead of being replaced with invented content.
|
|
39
43
|
- Results are never marked retryable by the process runner.
|
|
40
44
|
|
|
@@ -42,7 +46,9 @@ For an unsuccessful `work` job, `ambiguousSideEffects` is true because CueLine c
|
|
|
42
46
|
|
|
43
47
|
## Foreground and background
|
|
44
48
|
|
|
45
|
-
A foreground `start` waits for the single execution and persists its terminal status. A background `start` persists and returns `running` immediately while the same completion promise continues. `waitForCompletion(jobId)` returns that completion or the last persisted status.
|
|
49
|
+
A foreground `start` waits for the single execution and persists its terminal status. A background `start` persists and returns `running` immediately while the same completion promise continues. `waitForCompletion(jobId)` returns that completion or the last persisted status. The supervisor persists run ID, job key, lane, mode, and child PID after spawn. PID is observability, not stand-alone cancellation authority.
|
|
50
|
+
|
|
51
|
+
`cancel(jobId)` and `cancelAll()` operate only on executions owned by the current supervisor. Cross-session CLI cancellation is a durable request consumed by that owner. CueLine does not kill an unverified process merely because a stale status file contains a PID.
|
|
46
52
|
|
|
47
53
|
The controller loop derives deterministic job IDs from the run, `job_key`, and job specification. A duplicate dispatch already present in run state is recorded as a notice and skipped. This is the run-level at-most-once gate; it is not a distributed transaction across hosts.
|
|
48
54
|
|
|
@@ -9,6 +9,9 @@ ${CUELINE_HOME:-$HOME/.cueline}/
|
|
|
9
9
|
├── runs/
|
|
10
10
|
│ └── <run-id>/
|
|
11
11
|
│ ├── events.jsonl
|
|
12
|
+
│ ├── runtime.json
|
|
13
|
+
│ ├── cancel.json # only after run cancellation is requested
|
|
14
|
+
│ ├── job-cancellations/ # only after job cancellation is requested
|
|
12
15
|
│ └── snapshot.json
|
|
13
16
|
└── jobs/
|
|
14
17
|
└── <job-id>.json
|
|
@@ -26,7 +29,7 @@ Important transitions include:
|
|
|
26
29
|
- controller turn intent, submission checkpoints, response, reconciliation, rejection, and accepted command
|
|
27
30
|
- job registration and status changes
|
|
28
31
|
- notices
|
|
29
|
-
- complete, blocked, and failed terminal records
|
|
32
|
+
- complete, blocked, cancelled, and failed terminal records
|
|
30
33
|
|
|
31
34
|
The controller turn is recorded before sending it through the browser. Immediately before the send click, CueLine records `controller_turn_submission_started` with `submission_state: possibly_sent`; after the click it records `controller_turn_submitted`. A verified response is recorded separately. A job is registered only after the entire dispatch has passed pre-spawn route validation, and before its process starts. This ordering leaves evidence when an interruption occurs between intent and side effect.
|
|
32
35
|
|
|
@@ -42,15 +45,26 @@ The snapshot is therefore disposable. Do not edit the event log by hand; invalid
|
|
|
42
45
|
|
|
43
46
|
## Job status
|
|
44
47
|
|
|
45
|
-
The supervisor atomically replaces one JSON file per job. Foreground work returns its terminal status directly. Background work first persists `running`; later `wait` reads the same in-process completion or the last persisted status.
|
|
48
|
+
The supervisor atomically replaces one JSON file per job. New records include run ID, job key, lane, mode, and spawned child PID. PID is diagnostic evidence only; CueLine never treats a PID alone as authority to kill a process. Foreground work returns its terminal status directly. Background work first persists `running`; later `wait` reads the same in-process completion or the last persisted status.
|
|
46
49
|
|
|
47
|
-
The run event log still records the controller-visible job transitions. A status file is execution evidence, not a substitute for the run history.
|
|
50
|
+
The run event log still records the controller-visible job transitions. A status file is execution evidence, not a substitute for the run history. `cueline jobs --json` adds a derived observed status: `running` requires active runtime ownership, `orphaned` means the persisted job says running but the owner is not active, and `unverified` covers legacy records without a run ID.
|
|
51
|
+
|
|
52
|
+
## Runtime ownership and status
|
|
53
|
+
|
|
54
|
+
`runtime.json` is an owner heartbeat lease. A live controller refreshes it while it owns the run and marks it released on a normal exit. `cueline run status <run-id> --json` combines event replay, lease ownership, cancellation requests, and job state.
|
|
55
|
+
|
|
56
|
+
- `controller.responseAccepted: true` means a controller response was accepted. `lastAcceptedAction` and `lastAcceptedJobKeys` summarize what that response ordered without dumping its full task text. Do not describe this phase as waiting for ChatGPT.
|
|
57
|
+
- `phase: jobs_running` plus `runtime.ownership: active` means local jobs are executing under the original loop.
|
|
58
|
+
- `runtime_ownership_unknown` or `runtime_stale` means persisted `running` is not proof of a live process. Active-looking jobs are reported as `orphaned`.
|
|
59
|
+
- `runtime_active` means a live owner is still settling a failed state; another session must observe rather than continue.
|
|
60
|
+
- `continueAllowed: false` is a hard stop. Another session must not send, resume, or claim completion.
|
|
48
61
|
|
|
49
62
|
## Continue behavior
|
|
50
63
|
|
|
51
|
-
`continueCueLineRun` loads the exact `runId`, replays state as needed, and resumes the same persisted run. The public runtime also reuses the stored ChatGPT conversation URL unless an explicit compatible adapter is supplied.
|
|
64
|
+
Always run `cueline run status <run-id> --json` before continuation. `continueCueLineRun` loads the exact `runId`, replays state as needed, and resumes the same persisted run. The public runtime also reuses the stored ChatGPT conversation URL unless an explicit compatible adapter is supplied.
|
|
52
65
|
|
|
53
|
-
- `complete` and `
|
|
66
|
+
- `complete`, `blocked`, and `cancelled` runs are returned as-is; they are not dispatched again.
|
|
67
|
+
- an active owner is rejected with `RUN_ALREADY_ACTIVE`; missing ownership is rejected with `RUN_OWNERSHIP_UNVERIFIED`; a stale lease requires explicit recovery
|
|
54
68
|
- a non-terminal or locally `failed` run with no pending controller turn can be marked resumed and driven for additional rounds
|
|
55
69
|
- when a failure proves `definitely_not_sent` for the exact sole pending request, CueLine records that turn as abandoned and safely starts a new round
|
|
56
70
|
- a pending controller turn is reconciled read-only from the exact conversation before any new prompt is sent
|
|
@@ -59,6 +73,16 @@ The run event log still records the controller-visible job transitions. A status
|
|
|
59
73
|
- deterministic job IDs suppress a repeated dispatch already present in state
|
|
60
74
|
- running jobs can be observed or waited through their persisted status
|
|
61
75
|
|
|
76
|
+
All jobs in an accepted dispatch start concurrently only when every job is `advise`. A dispatch containing any `work` job is serialized in command order to avoid overlapping mutations.
|
|
77
|
+
|
|
78
|
+
## Cancellation and deadlines
|
|
79
|
+
|
|
80
|
+
`cueline run cancel <run-id>` and its `run stop` alias write a durable request. `cueline job cancel <run-id> <job-id>` targets one job. An active owner observes the request, sends `SIGTERM` to its owned child, escalates to `SIGKILL` after the grace interval, and persists the terminal transition. Cancelled `advise` is `cancelled`; started `work` is `ambiguous` because partial side effects cannot be disproved.
|
|
81
|
+
|
|
82
|
+
When a legacy run has no verifiable owner, run cancellation closes the run and changes active-looking jobs to `ambiguous`; it does not claim an unknown process was killed. A stale current-generation lease records the request but requires inspection rather than unsafe PID-based killing.
|
|
83
|
+
|
|
84
|
+
`runTimeoutMs` is an optional run-level deadline. It aborts the controller and owned jobs before returning `RUN_TIMEOUT`. `defaultTimeoutMs` and job `timeout_ms` remain per-job limits. A caller-side or tool-side wait timeout is not a cancellation signal; after one fires, inspect `run status`, then explicitly cancel if required. API callers may pass `signal` for direct cancellation propagation.
|
|
85
|
+
|
|
62
86
|
For one unambiguous pending turn, supply the exact conversation URL when it was not captured before the failure:
|
|
63
87
|
|
|
64
88
|
```js
|
|
@@ -89,9 +113,10 @@ Continuation cannot reconstruct an expired ChatGPT login, a deleted conversation
|
|
|
89
113
|
2. Record the `runId` from the earlier result or directory name.
|
|
90
114
|
3. Restore access to the same ChatGPT conversation in Codex's built-in Browser.
|
|
91
115
|
4. Restore any locally required executable/configuration without copying browser credentials.
|
|
92
|
-
5.
|
|
93
|
-
6.
|
|
94
|
-
7.
|
|
95
|
-
8.
|
|
116
|
+
5. Run `cueline run status <run-id> --json`. If the owner is active, observe or cancel; do not continue. If the response is accepted and jobs exist, do not call that state “waiting for ChatGPT.”
|
|
117
|
+
6. Inspect `loadCueLineRunState(runId, ...)` only when status says recovery is needed. If multiple `pendingControllerTurns` exist, match the visible page prompt and select its exact `requestId`.
|
|
118
|
+
7. Call `continueCueLineRun({ runId, conversationUrl, ... })` only when continuation is allowed. Add `reconcileRequestId` and `abandonOtherPendingTurns: true` only for an explicitly resolved multi-pending case.
|
|
119
|
+
8. If reconciliation fails, do not resend manually. Preserve the page and report the exact `CONTROLLER_RECONCILIATION_*`, `IAB_RECONCILIATION_FAILED`, or `TAB_RECOVERY_UNSAFE` error.
|
|
120
|
+
9. Treat the new terminal result as valid only after its event and job evidence is present.
|
|
96
121
|
|
|
97
122
|
For manual diagnosis, use `cueline jobs` and inspect the run's JSONL as read-only evidence. Do not copy `CUELINE_HOME` between machines as a replacement for local process or browser session state.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A ChatGPT web conversation directs; CueLine validates each command and runs the work on your machine, with a durable record of both.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatgpt",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"!docs/superpowers",
|
|
47
47
|
"evals",
|
|
48
48
|
"schemas",
|
|
49
|
+
"scripts",
|
|
49
50
|
"skills",
|
|
50
51
|
"install.sh",
|
|
51
52
|
"CHANGELOG.md",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"test:unit": "npm run build && node --test dist/test/unit/*.test.js",
|
|
66
67
|
"test:integration": "npm run build && node --test dist/test/integration/*.test.js",
|
|
67
68
|
"smoke:fake": "npm run build && node --test dist/test/smoke/fake-smoke.test.js",
|
|
68
|
-
"validate:plugin": "
|
|
69
|
+
"validate:plugin": "node scripts/validate-plugin.mjs"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@types/node": "^22.20.1",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"job_id": { "type": "string" },
|
|
21
21
|
"job_key": { "type": "string" },
|
|
22
22
|
"required": { "type": "boolean" },
|
|
23
|
-
"status": { "enum": ["pending", "running", "succeeded", "failed", "timed_out", "ambiguous"] },
|
|
23
|
+
"status": { "enum": ["pending", "running", "succeeded", "failed", "timed_out", "cancelled", "ambiguous"] },
|
|
24
24
|
"output": { "type": "string" },
|
|
25
25
|
"error": { "type": "string" }
|
|
26
26
|
},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
|
|
7
|
+
async function readJson(relativePath) {
|
|
8
|
+
const absolutePath = path.join(root, relativePath);
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(await readFile(absolutePath, "utf8"));
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new Error(`${relativePath} must exist and contain valid JSON: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function requireNonEmptyString(value, field) {
|
|
17
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
18
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
23
|
+
const packageJson = await readJson("package.json");
|
|
24
|
+
const codexManifest = await readJson(".codex-plugin/plugin.json");
|
|
25
|
+
const claudeManifest = await readJson(".claude-plugin/plugin.json");
|
|
26
|
+
|
|
27
|
+
for (const [name, manifest] of [
|
|
28
|
+
[".codex-plugin/plugin.json", codexManifest],
|
|
29
|
+
[".claude-plugin/plugin.json", claudeManifest],
|
|
30
|
+
]) {
|
|
31
|
+
requireNonEmptyString(manifest.name, `${name}: name`);
|
|
32
|
+
requireNonEmptyString(manifest.version, `${name}: version`);
|
|
33
|
+
requireNonEmptyString(manifest.description, `${name}: description`);
|
|
34
|
+
requireNonEmptyString(manifest.author?.name, `${name}: author.name`);
|
|
35
|
+
if (!semver.test(manifest.version)) {
|
|
36
|
+
throw new Error(`${name}: version must be strict semver`);
|
|
37
|
+
}
|
|
38
|
+
if (manifest.name !== packageJson.name || manifest.version !== packageJson.version) {
|
|
39
|
+
throw new Error(`${name}: name and version must match package.json`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pluginInterface = codexManifest.interface;
|
|
44
|
+
for (const field of [
|
|
45
|
+
"displayName",
|
|
46
|
+
"shortDescription",
|
|
47
|
+
"longDescription",
|
|
48
|
+
"developerName",
|
|
49
|
+
"category",
|
|
50
|
+
"defaultPrompt",
|
|
51
|
+
]) {
|
|
52
|
+
requireNonEmptyString(pluginInterface?.[field], `.codex-plugin/plugin.json: interface.${field}`);
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(pluginInterface.capabilities) || pluginInterface.capabilities.some((item) => typeof item !== "string")) {
|
|
55
|
+
throw new Error(".codex-plugin/plugin.json: interface.capabilities must be an array of strings");
|
|
56
|
+
}
|
|
57
|
+
if (codexManifest.skills !== "./skills/") {
|
|
58
|
+
throw new Error(".codex-plugin/plugin.json: skills must be ./skills/");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const skill = await readFile(path.join(root, "skills/cueline/SKILL.md"), "utf8");
|
|
62
|
+
if (!skill.startsWith("---\n") || !/^name: cueline$/m.test(skill) || !/^description: .+$/m.test(skill)) {
|
|
63
|
+
throw new Error("skills/cueline/SKILL.md must contain cueline name and description frontmatter");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`Plugin validation passed: ${root} (${packageJson.version})`);
|
package/skills/cueline/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ Use CueLine to put the current ChatGPT web conversation in charge of planning an
|
|
|
10
10
|
## Preconditions
|
|
11
11
|
|
|
12
12
|
1. Confirm the current host, operator, and work directory before local work.
|
|
13
|
-
2. Run `cueline
|
|
13
|
+
2. Run `cueline version`, then `cueline doctor`, and require the same expected package version, Node.js 22 or newer, plus at least one usable lane. Run `cueline api path` and require the returned API module to exist. Do not reuse a module imported before a package update. The bundled default lane needs the `codex` CLI on `PATH`.
|
|
14
14
|
3. Use Codex's persistent Node REPL/runtime together with the built-in in-app Browser (IAB), not a separate plain `node` child, Chrome automation, or GPT Relay. Claim the logged-in `chatgpt.com` tab intended for this run.
|
|
15
15
|
4. CueLine requires the composer model selector to show `Pro` before every controller turn and requires the completed assistant message's `data-message-model-slug` to identify a Pro model. The account label (for example, a profile name ending in `Pro`) is subscription evidence only and never model evidence. Do not bypass `MODEL_SELECTOR_MISSING`, `PRO_MODEL_UNAVAILABLE`, `PRO_MODEL_SELECTION_FAILED`, or `PRO_MODEL_MISMATCH`.
|
|
16
16
|
5. Do not request, read, copy, or print cookies, access tokens, browser session material, or private environment values.
|
|
@@ -24,11 +24,16 @@ Drive CueLine from Codex's Node runtime so the injected IAB object remains avail
|
|
|
24
24
|
|
|
25
25
|
```js
|
|
26
26
|
var cuelineApiPath = "/exact/output/from/cueline/api/path";
|
|
27
|
+
var expectedCueLineVersion = "exact output from cueline version";
|
|
28
|
+
const { pathToFileURL } = await import("node:url");
|
|
29
|
+
var cuelineModuleUrl = `${pathToFileURL(cuelineApiPath).href}?v=${encodeURIComponent(expectedCueLineVersion)}`;
|
|
27
30
|
const {
|
|
31
|
+
CUELINE_VERSION,
|
|
28
32
|
createCodexIabAdapter,
|
|
29
33
|
runCueLine,
|
|
30
34
|
startCueLineRun,
|
|
31
|
-
} = await import(
|
|
35
|
+
} = await import(cuelineModuleUrl);
|
|
36
|
+
if (CUELINE_VERSION !== expectedCueLineVersion) throw new Error(`CUELINE_VERSION_MISMATCH: loaded ${CUELINE_VERSION}, expected ${expectedCueLineVersion}`);
|
|
32
37
|
|
|
33
38
|
const browser = createCodexIabAdapter({
|
|
34
39
|
// Optional: conversationUrl: "https://chatgpt.com/c/..."
|
|
@@ -38,7 +43,8 @@ const result = await runCueLine({
|
|
|
38
43
|
request: USER_REQUEST,
|
|
39
44
|
browser,
|
|
40
45
|
// Optional: conversationUrl, routingConfig/routingConfigPath, home, cwd,
|
|
41
|
-
// environment, defaultTimeoutMs, maxRounds,
|
|
46
|
+
// environment, defaultTimeoutMs, runTimeoutMs, signal, maxRounds,
|
|
47
|
+
// maxRepairAttempts.
|
|
42
48
|
});
|
|
43
49
|
```
|
|
44
50
|
|
|
@@ -50,10 +56,23 @@ The web controller decides `dispatch`, `wait`, `inspect`, `complete`, or `blocke
|
|
|
50
56
|
|
|
51
57
|
When an interrupted or locally failed run already has a `runId`, resume it instead of starting over:
|
|
52
58
|
|
|
59
|
+
```bash
|
|
60
|
+
cueline run status EXISTING_RUN_ID --json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This check is mandatory before every continuation and after every outer tool/wait timeout. Read these fields literally:
|
|
64
|
+
|
|
65
|
+
- `controller.responseAccepted: true` means the web response was already received and accepted. Read `lastAcceptedAction` and `lastAcceptedJobKeys` before describing what CueLine is doing. Never say CueLine is waiting for the web response.
|
|
66
|
+
- `phase: jobs_running` with `runtime.ownership: active` means the original local loop is running jobs. Observe it; do not call `continueCueLineRun`.
|
|
67
|
+
- `runtime_ownership_unknown` or `runtime_stale` means persisted `running` is not a live-process claim. Jobs shown as `orphaned` need inspection or cancellation.
|
|
68
|
+
- `runtime_active` means a live owner is still settling a locally failed state. Observe that owner; do not continue from another session.
|
|
69
|
+
- `continueAllowed: false` is a hard stop. Do not resend, resume, or open another controller conversation.
|
|
70
|
+
|
|
53
71
|
```js
|
|
54
72
|
const {
|
|
55
73
|
continueCueLineRun,
|
|
56
|
-
|
|
74
|
+
loadCueLineRunStatus,
|
|
75
|
+
} = await import(cuelineModuleUrl);
|
|
57
76
|
|
|
58
77
|
const result = await continueCueLineRun({
|
|
59
78
|
runId: EXISTING_RUN_ID,
|
|
@@ -62,7 +81,7 @@ const result = await continueCueLineRun({
|
|
|
62
81
|
});
|
|
63
82
|
```
|
|
64
83
|
|
|
65
|
-
Preserve the same `CUELINE_HOME` and browser conversation. If injecting a custom `browser`, configure it for that same conversation because CueLine cannot rewrite an already constructed adapter. Do not copy credentials or runtime state from another host. A terminal `complete` or `
|
|
84
|
+
Preserve the same `CUELINE_HOME` and browser conversation. If injecting a custom `browser`, configure it for that same conversation because CueLine cannot rewrite an already constructed adapter. Do not copy credentials or runtime state from another host. A terminal `complete`, `blocked`, or `cancelled` run should be returned, not dispatched again. Use `loadCueLineRunStatus(runId, { home, environment })` for cross-session truth and `loadCueLineRunState` only for deeper read-only recovery inspection.
|
|
66
85
|
|
|
67
86
|
If `pendingControllerTurns` is non-empty, CueLine must recover the existing page response before any new send. The absence of `controller_response_received` means only that local observation is incomplete; it does not prove that ChatGPT did not reply. Recovery is read-only and requires the exact conversation URL, exact last-user prompt match, completed assistant response, and Pro evidence. Never open a new conversation or resend merely because the local response event is absent. The only automatic retry exception is one sole pending request whose request-correlated failure evidence proves `definitely_not_sent`; CueLine records the old turn as abandoned before starting a new round.
|
|
68
87
|
|
|
@@ -72,9 +91,11 @@ When multiple legacy turns are pending, stop on `MULTIPLE_CONTROLLER_TURNS_PENDI
|
|
|
72
91
|
|
|
73
92
|
- If `result.status === "complete"`, return `result.finalDeliveryText` **verbatim** as the user-facing answer. Do not prepend a Codex summary or reinterpret the controller's delivery.
|
|
74
93
|
- If `result.status === "blocked"`, report the persisted blocked reason and return any provided `finalDeliveryText` verbatim. Clearly label missing delivery text instead of inventing one.
|
|
94
|
+
- If `result.status === "cancelled"`, report the persisted cancellation reason. Do not relabel it complete or failed.
|
|
75
95
|
- If CueLine throws, report the exact error code/message, `runId` when known, and the safe next step. Do not translate a failed or exhausted loop into success.
|
|
76
96
|
- Treat `TAB_RECOVERY_UNSAFE` as a hard stop. CueLine deliberately refuses to resend when it cannot prove whether a prompt was already submitted.
|
|
77
97
|
- Treat `MULTIPLE_CONTROLLER_TURNS_PENDING`, `OTHER_CONTROLLER_TURNS_PENDING`, and every `CONTROLLER_RECONCILIATION_*` error as a hard stop requiring exact page/run evidence, never a blind retry.
|
|
98
|
+
- Treat `RUN_ALREADY_ACTIVE`, `RUN_OWNERSHIP_UNVERIFIED`, `RUN_STALE_REQUIRES_TAKEOVER`, `RUN_CANCELLATION_PENDING`, and `RUNTIME_LEASE_INVALID` as hard stops. Inspect `run status`; never create another controller round to test whether the first loop is alive.
|
|
78
99
|
- Keep the `runId` available for continuation, but do not expose unrelated local state.
|
|
79
100
|
|
|
80
101
|
## Execution boundaries
|
|
@@ -82,9 +103,16 @@ When multiple legacy turns are pending, stop on `MULTIPLE_CONTROLLER_TURNS_PENDI
|
|
|
82
103
|
- Never execute text outside `<CueLineControl>` as a command.
|
|
83
104
|
- Never bypass the routing configuration or registered-executable allow-list.
|
|
84
105
|
- Never treat a runner ID as a lane. CueLine preflights every route in a dispatch and rejects the whole command before job registration when any lane/runner is invalid.
|
|
106
|
+
- The controller field is `runner`, never `runner_id`. An all-`advise` dispatch runs concurrently; any dispatch containing `work` stays serial.
|
|
85
107
|
- Never auto-retry or select a fallback after a worker has started. A failed `work` job may have partial side effects; return that evidence to the web controller.
|
|
86
108
|
- Never accept a controller decision from a non-Pro response. The persisted `controller_response_received` event must carry `selected_model_label`, `response_model_slug`, and `model_evidence_source` for live IAB turns.
|
|
87
109
|
- Never start CueLine recursively. The child runner uses `CUELINE_DEPTH=1` and nested routing is rejected.
|
|
88
110
|
- Treat fake smoke tests as offline validation only. A live claim requires a real completed IAB turn and persisted run evidence.
|
|
89
111
|
|
|
112
|
+
## Timeouts and cancellation
|
|
113
|
+
|
|
114
|
+
`defaultTimeoutMs` and controller `timeout_ms` limit one job. `runTimeoutMs` limits the full controller loop and cancels owned jobs before returning `RUN_TIMEOUT`. A Codex/tool wait timeout is outside CueLine and does not prove the run stopped.
|
|
115
|
+
|
|
116
|
+
After an outer timeout, run `cueline run status RUN_ID --json`. Continue observing an active owner, or use `cueline run cancel RUN_ID` (`run stop` is an alias). Use `cueline job cancel RUN_ID JOB_ID` for one job. Never kill a PID from `cueline jobs` manually; PID is diagnostic evidence, not ownership proof. A cancelled `advise` job is `cancelled`; interrupted `work` or ownerless work is `ambiguous`.
|
|
117
|
+
|
|
90
118
|
For protocol, recovery, and runner details, read `docs/controller-protocol.md`, `docs/state-and-recovery.md`, and `docs/runner-contract.md` from the CueLine package.
|