cueline 0.1.2 → 0.1.4
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 +38 -0
- package/README.ja.md +65 -21
- package/README.ko.md +65 -21
- package/README.md +70 -23
- package/README.zh-CN.md +65 -21
- package/README.zh-TW.md +68 -22
- 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 +253 -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 +568 -0
- package/dist/src/api-runtime-lifecycle.js.map +1 -0
- package/dist/src/api.d.ts +9 -27
- package/dist/src/api.js +138 -42
- package/dist/src/api.js.map +1 -1
- package/dist/src/browser/browser-adapter.d.ts +11 -1
- package/dist/src/browser/codex-iab/bootstrap.d.ts +16 -0
- package/dist/src/browser/codex-iab/bootstrap.js +49 -0
- package/dist/src/browser/codex-iab/bootstrap.js.map +1 -1
- package/dist/src/browser/codex-iab/chatgpt-client.js +346 -120
- 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 +53 -0
- package/dist/src/browser/codex-iab/submission-url.js.map +1 -0
- package/dist/src/cli/main.js +305 -20
- 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 -37
- package/dist/src/core/controller-loop.js +386 -291
- 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 +309 -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 +53 -0
- package/dist/src/core/run-status.js +224 -0
- package/dist/src/core/run-status.js.map +1 -0
- package/dist/src/core/state-machine.d.ts +17 -4
- package/dist/src/core/state-machine.js +170 -16
- package/dist/src/core/state-machine.js.map +1 -1
- package/dist/src/jobs/status.d.ts +7 -1
- package/dist/src/jobs/status.js.map +1 -1
- package/dist/src/jobs/supervisor.d.ts +2 -0
- package/dist/src/jobs/supervisor.js +47 -13
- 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.d.ts +2 -1
- package/dist/src/router/resolver.js +31 -13
- 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 +113 -9
- 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/atomic-write.js +17 -2
- package/dist/src/state/atomic-write.js.map +1 -1
- package/dist/src/state/cancellation.d.ts +42 -0
- package/dist/src/state/cancellation.js +168 -0
- package/dist/src/state/cancellation.js.map +1 -0
- 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 +4 -0
- package/dist/src/state/paths.js +4 -0
- package/dist/src/state/paths.js.map +1 -1
- package/dist/src/state/runtime-lease.d.ts +53 -0
- package/dist/src/state/runtime-lease.js +727 -0
- package/dist/src/state/runtime-lease.js.map +1 -0
- 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 +16 -8
- package/docs/controller-protocol.md +8 -4
- package/docs/runner-contract.md +26 -6
- package/docs/state-and-recovery.md +69 -16
- package/package.json +4 -3
- package/schemas/controller-observation.schema.json +1 -1
- package/scripts/validate-plugin.mjs +66 -0
- package/skills/cueline/SKILL.md +89 -13
|
@@ -9,6 +9,13 @@ ${CUELINE_HOME:-$HOME/.cueline}/
|
|
|
9
9
|
├── runs/
|
|
10
10
|
│ └── <run-id>/
|
|
11
11
|
│ ├── events.jsonl
|
|
12
|
+
│ ├── events.jsonl.segments/
|
|
13
|
+
│ ├── runtime.json.fence
|
|
14
|
+
│ ├── runtime.json.epochs/
|
|
15
|
+
│ ├── runtime.json.retired-owners/
|
|
16
|
+
│ ├── runtime.json.takeover-intents/
|
|
17
|
+
│ ├── cancel.json # only after run cancellation is requested
|
|
18
|
+
│ ├── job-cancellations/ # only after job cancellation is requested
|
|
12
19
|
│ └── snapshot.json
|
|
13
20
|
└── jobs/
|
|
14
21
|
└── <job-id>.json
|
|
@@ -18,7 +25,7 @@ ${CUELINE_HOME:-$HOME/.cueline}/
|
|
|
18
25
|
|
|
19
26
|
## Event log
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
The logical event log is append-only and authoritative. A legacy or atomically created first event may live in `events.jsonl`; subsequent events use one immutable file per sequence under `events.jsonl.segments/`. Each event contains a monotonically increasing sequence number, timestamp, type, payload, and an optional runtime owner ID. The writer fully writes and syncs a temporary segment, hard-links it to the sequence name with create-if-absent semantics, then syncs the containing directory before reporting success. A `legacy-fence.json` freezes the accepted byte prefix of an older JSONL writer, so a still-loaded pre-segment process may append a diagnostic suffix without creating duplicate logical sequence numbers. Concurrent or recovered writers that lose a sequence race reread and retry; no global event lock or partial segment becomes authoritative.
|
|
22
29
|
|
|
23
30
|
Important transitions include:
|
|
24
31
|
|
|
@@ -26,38 +33,71 @@ Important transitions include:
|
|
|
26
33
|
- controller turn intent, submission checkpoints, response, reconciliation, rejection, and accepted command
|
|
27
34
|
- job registration and status changes
|
|
28
35
|
- notices
|
|
29
|
-
- complete, blocked, and failed terminal records
|
|
36
|
+
- complete, blocked, cancelled, and failed terminal records
|
|
30
37
|
|
|
31
|
-
The controller turn is recorded before sending it through the browser. Immediately before
|
|
38
|
+
The controller turn is recorded before sending it through the browser. After the composer settles, CueLine records whether the prompt is `inline_ready` or `attachment_ready`. Immediately before its one allowed send attempt it records `controller_turn_submission_started` with `submission_state: submitting`; a verified post-click boundary becomes `submitted`, while an ambiguous click becomes `possibly_sent`. A verified response is recorded separately. A job is registered only after the entire dispatch has passed validation, and before either caller handoff or process start. This ordering leaves evidence when an interruption occurs between intent and side effect.
|
|
32
39
|
|
|
33
40
|
The absence of `controller_response_received` proves only that the local runtime did not record a response. It does **not** prove that ChatGPT did not answer. `run_failed` therefore retains the safe error message, stage, exact request ID, submission state, and known conversation URL. CueLine never treats a missing response event as permission to resend an ambiguous prompt.
|
|
34
41
|
|
|
35
42
|
## Snapshot
|
|
36
43
|
|
|
37
|
-
`snapshot.json` is a materialized view of the event stream. CueLine writes a temporary file in the same directory and renames it over the destination. A snapshot records its state protocol, run ID, and last applied sequence.
|
|
38
|
-
|
|
39
|
-
On load, CueLine uses a snapshot only when its identity and sequence are valid. A missing, malformed, or out-of-range snapshot is ignored and the state is replayed from event 1. Events newer than a valid snapshot are applied afterward.
|
|
44
|
+
`snapshot.json` is a disposable materialized view of the event stream. CueLine writes a temporary file in the same directory and renames it over the destination. A snapshot records its state protocol, run ID, and last applied physical sequence. Loads replay the authoritative event stream from event 1 instead of trusting a snapshot that a retired owner could have replaced.
|
|
40
45
|
|
|
41
46
|
The snapshot is therefore disposable. Do not edit the event log by hand; invalid JSON or a broken sequence makes the run unreplayable and is reported rather than silently skipped.
|
|
42
47
|
|
|
43
48
|
## Job status
|
|
44
49
|
|
|
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.
|
|
50
|
+
The supervisor atomically replaces one JSON status file per job. Status records include run ID, job key, lane, mode, and—when a process exists—its child PID. The task remains in the authoritative run's `job_registered` event; `cueline jobs` joins that event metadata with the status file so the task is still visible. Caller jobs are visible as `pending` before the current Codex submits a terminal result. PID is diagnostic evidence only; CueLine never treats a PID alone as authority to kill a process. Foreground process work returns its terminal status directly. Background process work first persists `running`; later `wait` reads the same in-process completion or the last persisted status.
|
|
51
|
+
|
|
52
|
+
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` includes run ID, job key, lane, mode, task, and PID when known. For process runs it adds a derived observed status: `running` requires active runtime ownership, `orphaned` means persisted process work says active but no owner is proven, `unverified` covers legacy records without a run ID, and `conflict` means a status file disagrees with authoritative run events. A conflict reports the authoritative `status`, exposes the file's value as `persistedStatus`, and omits the untrusted late result. Pending caller jobs remain pending during their intentional ownerless handoff.
|
|
53
|
+
|
|
54
|
+
## Runtime ownership and status
|
|
55
|
+
|
|
56
|
+
`runtime.json.fence` selects the authoritative owner-heartbeat record under `runtime.json.epochs/`. A live controller refreshes only its selected epoch and removes its own record on normal exit. If a mutation lock is abandoned, recovery rotates the fence before reuse; a paused old writer can then modify only its old epoch and cannot overwrite or release the new owner. An explicit takeover first records the operator's exact expected owner and heartbeat under `runtime.json.takeover-intents/`, including rejected race attempts. While holding the same mutation lock as runtime-authored event appends, a successful takeover then atomically replaces the exact stale lease and embeds the retired owner's last authoritative event sequence in the new lease. That lease replacement is the commit boundary: a failed replacement leaves the old owner authoritative. Before the replacement lease is removed, embedded cutoffs are also mirrored to immutable records under `runtime.json.retired-owners/`. Owner-tagged events after a committed cutoff remain on disk for audit but are excluded from state, status, reconciliation, and job metadata replay. Legacy `runtime.json` records are migrated on the first mutation. Creation and dead-owner retirement remain atomic and owner-checked so two sessions cannot both claim the run. `cueline run status <run-id> --json` combines event replay, lease ownership, cancellation requests, and job state.
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
- `phase: controller_response_pending`, `controller.pendingTurns === 1`, missing runtime ownership, and `safeNextAction: observe` mean the exact normally submitted request awaits one read-only observation. `controller.responseAccepted` is false even when `lastAcceptedAction` summarizes an earlier round; continue the same run without resend. `safeNextAction: reconcile` is reserved for ambiguous, manually submitted, or multiple pending turns.
|
|
59
|
+
- `controller.responseAccepted: true` means no newer turn is pending and a controller response was accepted. `lastAcceptedAction` and `lastAcceptedJobKeys` summarize what that accepted response ordered without dumping its full task text.
|
|
60
|
+
- `phase: jobs_running` plus `runtime.ownership: active` means local jobs are executing under the original loop.
|
|
61
|
+
- `executor: caller`, `phase: caller_jobs_pending`, `runtime.ownership: missing`, and `safeNextAction: execute_caller_jobs` is a healthy durable pause. The current Codex should execute the listed `advise` tasks and submit their results; it must not claim that ChatGPT used local tools.
|
|
62
|
+
- Caller handoff has no execution claim. Coordinate one session before doing the local advice; if two sessions execute it, the first terminal evidence submitted wins and the later submission returns `already_terminal`.
|
|
63
|
+
- `runtime_ownership_unknown` means persisted `running` is not proof of a live process. `runtime_stale` requires explicit `cueline run takeover <run-id>` confirmation before the exact stale heartbeat can be retired. Active-looking process jobs are reported as `orphaned`.
|
|
64
|
+
- `runtime_active` means a live owner is still settling a failed state; another session must observe rather than continue.
|
|
65
|
+
- `continueAllowed: false` is a hard stop. Another session must not send, resume, or claim completion.
|
|
48
66
|
|
|
49
67
|
## Continue behavior
|
|
50
68
|
|
|
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.
|
|
69
|
+
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
70
|
|
|
53
|
-
- `complete` and `
|
|
71
|
+
- `complete`, `blocked`, and `cancelled` runs are returned as-is; they are not dispatched again.
|
|
72
|
+
- an active owner is rejected with `RUN_ALREADY_ACTIVE`; a stale lease requires dead-owner inspection; missing ownership is healthy only for a pristine or caller-mode run
|
|
54
73
|
- a non-terminal or locally `failed` run with no pending controller turn can be marked resumed and driven for additional rounds
|
|
74
|
+
- `maxRounds` is a durable total-run contract, not a per-continuation allowance; continuation reuses the created value and cannot widen or shrink it
|
|
55
75
|
- 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
76
|
- a pending controller turn is reconciled read-only from the exact conversation before any new prompt is sent
|
|
57
|
-
-
|
|
77
|
+
- built-in caller/IAB submission returns `awaiting_controller` after one durable send and exact URL capture; each later continuation performs one non-blocking observation and returns the same status again if Pro is unfinished
|
|
78
|
+
- inline reconciliation requires the page's last user message to equal the persisted prompt, a completed assistant response, and both Pro model checks
|
|
79
|
+
- attachment reconciliation may omit the full prompt from the visible user message, but only after a formal manual-send confirmation and exact conversation plus protocol/run/round/request/Pro evidence; response and URL must come from the same DOM snapshot; this exact operator-confirmed path also supports legacy turns that predate the durable assistant-count baseline, while automatic attachment recovery still requires that baseline
|
|
80
|
+
- a specified recoverably abandoned turn can be restored through that same append-only confirmation path when no same/newer command was accepted; it is never resent
|
|
58
81
|
- when more than one legacy turn is pending, CueLine stops with `MULTIPLE_CONTROLLER_TURNS_PENDING` rather than guessing
|
|
59
82
|
- deterministic job IDs suppress a repeated dispatch already present in state
|
|
60
|
-
-
|
|
83
|
+
- caller jobs are returned as `awaiting_caller`; after local execution, `submitCueLineCallerJobResult` persists the full result and continuation sends bounded evidence to the same controller
|
|
84
|
+
- `complete` and `blocked` are rejected while any required or optional job is pending/running, so a terminal command cannot orphan background work
|
|
85
|
+
- any non-normal process-loop exit cancels and settles its owned active jobs before releasing the runtime lease, including round-limit and controller-validation failures
|
|
86
|
+
- process jobs can be observed or waited through their persisted status
|
|
87
|
+
|
|
88
|
+
Caller mode accepts `advise` only. Explicit process execution defaults to at most two concurrent jobs globally and two per lane; a dispatch containing any `work` job is serialized in command order to avoid overlapping mutations.
|
|
89
|
+
|
|
90
|
+
## Cancellation and deadlines
|
|
91
|
+
|
|
92
|
+
`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.
|
|
93
|
+
|
|
94
|
+
The CLI does not drive the browser. `doctor`, `routing`, `jobs`, `run status`, `api path`, and `config path` are read-only. `install`/`uninstall` change only the package-owned skill link. `run reconcile`, `run takeover`, `run reconcile-runtime`, `run cancel`/`run stop`, and `job cancel` append audit evidence or mutate durable local state; their complete positional syntax and options are listed by `cueline help`.
|
|
95
|
+
|
|
96
|
+
When a process run has no verifiable owner, runtime reconciliation checks both the recorded process and its POSIX process group. Surviving processes block settlement. Only dead `advise` work can become failed/cancelled; `work` remains `ambiguous` when partial side effects cannot be disproved. `cueline run reconcile-runtime <run-id>` exposes this transition without treating a stale PID as kill authority.
|
|
97
|
+
|
|
98
|
+
`cueline run takeover <run-id>` is narrower: it atomically replaces only the exact stale owner/heartbeat observed by the command and writes `runtime_stale_owner_takeover_requested` followed by `runtime_stale_owner_takeover_confirmed` while the replacement lease is held. A fresh active heartbeat is refused. The result says `next: continue` when the run may resume directly, or `next: reconcile_runtime` when ownerless process jobs must first pass process/group liveness reconciliation.
|
|
99
|
+
|
|
100
|
+
`runTimeoutMs` is an optional owned-advancement deadline. In explicit process mode it limits that controller-loop invocation and aborts owned jobs before returning `RUN_TIMEOUT`. Caller runs intentionally release ownership at controller/caller pauses, so the value limits each `runCueLine` or `continueCueLineRun` call in which it is supplied; ownerless Pro thinking and local handoff time between calls are not counted. `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.
|
|
61
101
|
|
|
62
102
|
For one unambiguous pending turn, supply the exact conversation URL when it was not captured before the failure:
|
|
63
103
|
|
|
@@ -81,6 +121,16 @@ await continueCueLineRun({
|
|
|
81
121
|
|
|
82
122
|
Do not set `abandonOtherPendingTurns` from sequence order alone. Use direct page evidence to match the visible user prompt to the persisted request. CueLine records every explicit abandonment.
|
|
83
123
|
|
|
124
|
+
When ChatGPT converted the prompt into an attachment and the operator manually performed the one send, record the confirmation formally instead of editing `events.jsonl`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
cueline run reconcile RUN_ID \
|
|
128
|
+
--request-id REQUEST_ID \
|
|
129
|
+
--manual-send-confirmed
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The command requires the exact persisted conversation and writes `controller_turn_manual_submission_confirmed`. Then continue with that request ID. CueLine accepts the response only if its Pro evidence and full envelope identity match; a mismatch is rejected without resend or dispatch.
|
|
133
|
+
|
|
84
134
|
Continuation cannot reconstruct an expired ChatGPT login, a deleted conversation, an unavailable registered executable, or an in-memory child process that disappeared with the host process. In those cases CueLine reports the concrete failure; it does not fabricate completion.
|
|
85
135
|
|
|
86
136
|
## Recovery procedure
|
|
@@ -89,9 +139,12 @@ Continuation cannot reconstruct an expired ChatGPT login, a deleted conversation
|
|
|
89
139
|
2. Record the `runId` from the earlier result or directory name.
|
|
90
140
|
3. Restore access to the same ChatGPT conversation in Codex's built-in Browser.
|
|
91
141
|
4. Restore any locally required executable/configuration without copying browser credentials.
|
|
92
|
-
5.
|
|
93
|
-
6.
|
|
94
|
-
7.
|
|
95
|
-
8.
|
|
142
|
+
5. Run `cueline run status <run-id> --json`. If the owner is active, observe or cancel; do not continue. If it is stale, use `cueline run takeover <run-id> --json` once and follow its exact `next` field. `controller_response_pending` means observe the same submitted turn; an accepted response plus jobs means do not call that accepted round “waiting for ChatGPT.”
|
|
143
|
+
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`.
|
|
144
|
+
7. For `controller_response_pending`, wait a bounded interval and call `continueCueLineRun` once; unfinished Pro output returns `awaiting_controller` without resend.
|
|
145
|
+
8. For `caller_jobs_pending`, execute only the listed `advise` jobs locally, submit each terminal result, then call `continueCueLineRun`; no browser resend is involved.
|
|
146
|
+
9. Otherwise call `continueCueLineRun({ runId, conversationUrl, ... })` only when continuation is allowed. Add `reconcileRequestId` and `abandonOtherPendingTurns: true` only for an explicitly resolved multi-pending case. For a manually sent attachment, first use the formal reconcile command above.
|
|
147
|
+
10. If reconciliation fails, do not resend. Preserve the page and report the exact `CONTROLLER_RECONCILIATION_*`, `IAB_RECONCILIATION_FAILED`, or `TAB_RECOVERY_UNSAFE` error. Post-creation errors expose `details.run_id` for this recovery.
|
|
148
|
+
11. Treat the new terminal result as valid only after its event and job evidence is present.
|
|
96
149
|
|
|
97
150
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueline",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A ChatGPT web conversation directs; CueLine validates each command and
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "A ChatGPT web conversation directs; CueLine validates each text command and hands durable local advice to the current Codex.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatgpt",
|
|
7
7
|
"codex",
|
|
@@ -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
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cueline
|
|
3
|
-
description: Use CueLine whenever the user wants a ChatGPT web Pro conversation to be the top-level controller that plans, delegates, reviews evidence, or decides completion while Codex performs
|
|
3
|
+
description: Use CueLine whenever the user wants a ChatGPT web Pro conversation to be the top-level text controller that plans, delegates, reviews evidence, or decides completion while the current Codex performs permitted caller-mode advice. Trigger for requests to let web ChatGPT lead Codex, combine browser judgment with local tools, run a durable controller loop, or continue an existing CueLine run. CueLine uses Codex's built-in in-app Browser.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# CueLine
|
|
7
7
|
|
|
8
|
-
Use CueLine to put the current ChatGPT web conversation in charge of planning and next-step decisions while Codex remains the local intermediary and executor. The web page has no direct machine access; it sends validated text commands
|
|
8
|
+
Use CueLine to put the current ChatGPT web conversation in charge of planning and next-step decisions while the current Codex remains the local intermediary and executor. The web page has no direct machine tools or filesystem access; it only sends validated text commands. Never describe a Pro response as having inspected the repository or used local tools.
|
|
9
9
|
|
|
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, and `caller_ready yes`. Run `cueline api path` and require the returned API module to exist. Do not reuse a module imported before a package update. `process_available_lanes` may be zero for caller mode; a usable process lane and `codex` CLI are required only when explicitly selecting the process executor.
|
|
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.
|
|
17
|
-
6. Keep v0.1 controller traffic text-only. Do not
|
|
17
|
+
6. Keep v0.1 controller traffic text-only. ChatGPT may automatically convert a long filled prompt into an attachment; CueLine recognizes that state. Do not deliberately upload files, images, use Deep Research, Projects, or Apps. CueLine may switch the composer from another model to `Pro`; no other model switching is allowed.
|
|
18
18
|
|
|
19
19
|
If live IAB, authentication, build output, or a required runner is missing, report that exact prerequisite. Do not claim a live run from fake or read-only evidence.
|
|
20
20
|
|
|
@@ -24,36 +24,91 @@ 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,
|
|
33
|
+
continueCueLineRun,
|
|
29
34
|
runCueLine,
|
|
30
35
|
startCueLineRun,
|
|
31
|
-
|
|
36
|
+
submitCueLineCallerJobResult,
|
|
37
|
+
} = await import(cuelineModuleUrl);
|
|
38
|
+
if (CUELINE_VERSION !== expectedCueLineVersion) throw new Error(`CUELINE_VERSION_MISMATCH: loaded ${CUELINE_VERSION}, expected ${expectedCueLineVersion}`);
|
|
32
39
|
|
|
33
40
|
const browser = createCodexIabAdapter({
|
|
34
41
|
// Optional: conversationUrl: "https://chatgpt.com/c/..."
|
|
35
42
|
});
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
let result = await startCueLineRun({
|
|
38
45
|
request: USER_REQUEST,
|
|
46
|
+
// executor defaults to "caller". Select "process" only explicitly.
|
|
47
|
+
// Optional here: runId, home, environment, now, maxRounds.
|
|
48
|
+
});
|
|
49
|
+
result = await continueCueLineRun({
|
|
50
|
+
runId: result.runId,
|
|
39
51
|
browser,
|
|
40
|
-
// Optional:
|
|
41
|
-
//
|
|
52
|
+
// Optional while advancing: routingConfig/routingConfigPath, cwd,
|
|
53
|
+
// defaultTimeoutMs, runTimeoutMs, signal, maxRepairAttempts.
|
|
42
54
|
});
|
|
43
55
|
```
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
`maxRounds` is fixed as a durable total-run limit at creation. Continuations should omit it and reuse the stored value; if supplied, it must exactly match or CueLine rejects the continuation without sending another controller turn.
|
|
58
|
+
|
|
59
|
+
Pass the user's request faithfully. Do not silently widen it, and do not turn an analysis-only request into `work`. Prefer `startCueLineRun` followed by `continueCueLineRun`, so the durable `runId` is known before any browser send. `runCueLine` remains a convenience API and post-creation failures include `details.run_id`. In caller mode, both APIs pause at `awaiting_controller` after one durable send and later at `awaiting_caller`; neither silently spawns a worker.
|
|
60
|
+
|
|
61
|
+
The web controller decides `dispatch`, `wait`, `inspect`, `complete`, or `blocked`. CueLine validates the exact pending identity and persists transitions. Under the default caller executor, a `dispatch` is only a durable text instruction: the current Codex must perform the listed local `advise` jobs and submit their results.
|
|
62
|
+
|
|
63
|
+
### Observe the controller turn
|
|
64
|
+
|
|
65
|
+
When the result is `awaiting_controller`, CueLine has submitted the exact request once, captured its exact conversation URL, released the runtime lease, and stopped holding the outer tool call open. Do not send or start another run. After a bounded backoff, call `continueCueLineRun` on the same `runId` and browser. It performs one read-only `observeTurn`; if Pro is still answering, it returns `awaiting_controller` immediately again. Repeat until the exact response is accepted or reconciliation reports a concrete error.
|
|
66
|
+
|
|
67
|
+
### Execute caller jobs
|
|
68
|
+
|
|
69
|
+
When the result is `awaiting_caller`, execute every `pendingJobs` task exactly as written using the current Codex's local tools. Caller mode accepts `advise` only. It has no execution claim: coordinate one session before starting local advice, because two sessions could perform the same task and only the first submitted terminal evidence wins. If the controller requested `work`, stop on `CALLER_WORK_REQUIRES_CLAIM`; do not perform the mutation. Submit terminal evidence, then continue the same run:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
for (const job of result.pendingJobs ?? []) {
|
|
73
|
+
const evidence = await EXECUTE_EXACT_ADVISE_TASK(job.spec.task);
|
|
74
|
+
await submitCueLineCallerJobResult(
|
|
75
|
+
result.runId,
|
|
76
|
+
job.jobId,
|
|
77
|
+
{ status: "succeeded", stdout: evidence },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const next = await continueCueLineRun({
|
|
82
|
+
runId: result.runId,
|
|
83
|
+
browser,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
46
86
|
|
|
47
|
-
|
|
87
|
+
Submit real terminal evidence only. A duplicate submit returns `already_terminal`; do not invent or replace the first result. Continue through controller-observation and caller pauses until the controller returns `complete`, `blocked`, or `cancelled`. CueLine rejects both `complete` and `blocked` while any required or optional job is still pending/running; settle, inspect, or cancel every job first.
|
|
48
88
|
|
|
49
89
|
## Continue a run
|
|
50
90
|
|
|
51
91
|
When an interrupted or locally failed run already has a `runId`, resume it instead of starting over:
|
|
52
92
|
|
|
93
|
+
```bash
|
|
94
|
+
cueline run status EXISTING_RUN_ID --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This check is mandatory before every continuation and after every outer tool/wait timeout. Read these fields literally:
|
|
98
|
+
|
|
99
|
+
- `phase: controller_response_pending`, `controller.pendingTurns === 1`, `runtime.ownership: missing`, and `safeNextAction: observe` means one normally submitted exact turn awaits a read-only observation. `controller.responseAccepted` is false even if `lastAcceptedAction` describes an earlier round. Continue the same run after a bounded backoff; never resend. `safeNextAction: reconcile` is reserved for ambiguous, manually submitted, or multiple pending turns and requires the exact recovery evidence below.
|
|
100
|
+
- `controller.responseAccepted: true` means no newer controller turn is pending and a response was accepted. Read `lastAcceptedAction` and `lastAcceptedJobKeys` before describing what CueLine is doing. Never call that accepted round “still waiting for the web response.”
|
|
101
|
+
- `phase: jobs_running` with `runtime.ownership: active` means the original local loop is running jobs. Observe it; do not call `continueCueLineRun`.
|
|
102
|
+
- `executor: caller`, `phase: caller_jobs_pending`, `runtime.ownership: missing`, and `safeNextAction: execute_caller_jobs` is a healthy handoff. Execute the listed local `advise` jobs and submit their results; do not call it orphaned or waiting for ChatGPT.
|
|
103
|
+
- `runtime_ownership_unknown` means persisted `running` is not a live-process claim. Jobs shown as `orphaned` need inspection or cancellation. `runtime_stale` requires explicit `cueline run takeover RUN_ID --json`; follow its `next` field and never retire a fresh active owner.
|
|
104
|
+
- `runtime_active` means a live owner is still settling a locally failed state. Observe that owner; do not continue from another session.
|
|
105
|
+
- `continueAllowed: false` is a hard stop. Do not resend, resume, or open another controller conversation.
|
|
106
|
+
|
|
53
107
|
```js
|
|
54
108
|
const {
|
|
55
109
|
continueCueLineRun,
|
|
56
|
-
|
|
110
|
+
loadCueLineRunStatus,
|
|
111
|
+
} = await import(cuelineModuleUrl);
|
|
57
112
|
|
|
58
113
|
const result = await continueCueLineRun({
|
|
59
114
|
runId: EXISTING_RUN_ID,
|
|
@@ -62,9 +117,19 @@ const result = await continueCueLineRun({
|
|
|
62
117
|
});
|
|
63
118
|
```
|
|
64
119
|
|
|
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 `
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
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, completed assistant response, exact envelope identity, and Pro evidence. Inline recovery also requires the exact visible last-user prompt. For a prompt automatically converted to an attachment and manually sent, use the formal operator-confirmation path below; visible text need not equal the full persisted prompt. 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.
|
|
123
|
+
|
|
124
|
+
For a manually submitted attachment, record confirmation without editing `events.jsonl`:
|
|
66
125
|
|
|
67
|
-
|
|
126
|
+
```bash
|
|
127
|
+
cueline run reconcile RUN_ID \
|
|
128
|
+
--request-id REQUEST_ID \
|
|
129
|
+
--manual-send-confirmed
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then continue with the same `reconcileRequestId`. The exact conversation URL, Pro selector/response slug, and protocol/run/round/request envelope must all match. This path may restore the specified abandoned turn only when no same/newer command was accepted. It never resends the prompt or dispatches a job twice.
|
|
68
133
|
|
|
69
134
|
When multiple legacy turns are pending, stop on `MULTIPLE_CONTROLLER_TURNS_PENDING`. Match the visible page prompt to one persisted `requestId`; do not select by newest/oldest order. Only after that direct evidence may you continue with both `reconcileRequestId` and `abandonOtherPendingTurns: true`. CueLine records the abandoned requests.
|
|
70
135
|
|
|
@@ -72,9 +137,11 @@ When multiple legacy turns are pending, stop on `MULTIPLE_CONTROLLER_TURNS_PENDI
|
|
|
72
137
|
|
|
73
138
|
- 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
139
|
- If `result.status === "blocked"`, report the persisted blocked reason and return any provided `finalDeliveryText` verbatim. Clearly label missing delivery text instead of inventing one.
|
|
140
|
+
- If `result.status === "cancelled"`, report the persisted cancellation reason. Do not relabel it complete or failed.
|
|
75
141
|
- 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
142
|
- Treat `TAB_RECOVERY_UNSAFE` as a hard stop. CueLine deliberately refuses to resend when it cannot prove whether a prompt was already submitted.
|
|
77
143
|
- 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.
|
|
144
|
+
- 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. For an exact stale owner only, run `cueline run takeover RUN_ID --json` and obey `next: continue` or `next: reconcile_runtime`; active ownership is never eligible.
|
|
78
145
|
- Keep the `runId` available for continuation, but do not expose unrelated local state.
|
|
79
146
|
|
|
80
147
|
## Execution boundaries
|
|
@@ -82,9 +149,18 @@ When multiple legacy turns are pending, stop on `MULTIPLE_CONTROLLER_TURNS_PENDI
|
|
|
82
149
|
- Never execute text outside `<CueLineControl>` as a command.
|
|
83
150
|
- Never bypass the routing configuration or registered-executable allow-list.
|
|
84
151
|
- 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.
|
|
152
|
+
- The controller field is `runner`, never `runner_id`. Caller is the default executor, returns pending `advise` jobs to this Codex, and rejects `work`. The process executor must be explicit; it defaults to global/per-lane concurrency 2 for `advise`, while any batch containing `work` stays serial.
|
|
85
153
|
- 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
154
|
- 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
155
|
- Never start CueLine recursively. The child runner uses `CUELINE_DEPTH=1` and nested routing is rejected.
|
|
88
156
|
- Treat fake smoke tests as offline validation only. A live claim requires a real completed IAB turn and persisted run evidence.
|
|
89
157
|
|
|
158
|
+
## Timeouts and cancellation
|
|
159
|
+
|
|
160
|
+
`defaultTimeoutMs` and controller `timeout_ms` limit one job. For explicit `process` execution, `runTimeoutMs` limits that owned controller-loop invocation and cancels its owned jobs before returning `RUN_TIMEOUT`. Caller execution is deliberately split across ownerless pauses, so `runTimeoutMs` limits each `runCueLine`/`continueCueLineRun` advancement in which it is supplied; Pro thinking time and caller handoff time between calls are not counted. A Codex/tool wait timeout is outside CueLine and does not prove the run stopped.
|
|
161
|
+
|
|
162
|
+
The CLI never drives the browser. `doctor`, `routing`, `jobs`, `run status`, `api path`, and `config path` are read-only. `install`/`uninstall` change only the package-owned skill link. `run reconcile`, `run takeover`, `run reconcile-runtime`, `run cancel`/`run stop`, and `job cancel` append audit evidence or change durable local run/job state. Check `cueline help` for every positional argument and option before invoking a state-changing command.
|
|
163
|
+
|
|
164
|
+
After an outer timeout, run `cueline run status RUN_ID --json`. Both ownerless `controller_response_pending` and `caller_jobs_pending` survive the wait intentionally. Continue observing an active process owner, or use `cueline run cancel RUN_ID` (`run stop` is an alias). Use `cueline job cancel RUN_ID JOB_ID` for one job. Use `cueline run reconcile-runtime RUN_ID` to settle a dead owner only after CueLine verifies that no recorded process/process group survives. Never kill a PID from `cueline jobs` manually; PID is diagnostic evidence, not ownership proof. A cancelled `advise` job is `cancelled`; interrupted process `work` is `ambiguous`. If `cueline jobs` reports `observedStatus: conflict`, the authoritative run event wins over a late status-file write; do not trust the late result.
|
|
165
|
+
|
|
90
166
|
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.
|