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.
Files changed (61) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +17 -0
  4. package/README.ja.md +15 -6
  5. package/README.ko.md +15 -6
  6. package/README.md +15 -6
  7. package/README.zh-CN.md +15 -6
  8. package/README.zh-TW.md +15 -6
  9. package/dist/src/api.d.ts +23 -1
  10. package/dist/src/api.js +110 -6
  11. package/dist/src/api.js.map +1 -1
  12. package/dist/src/browser/browser-adapter.d.ts +1 -0
  13. package/dist/src/browser/codex-iab/bootstrap.d.ts +6 -0
  14. package/dist/src/browser/codex-iab/bootstrap.js.map +1 -1
  15. package/dist/src/browser/codex-iab/chatgpt-client.js +138 -41
  16. package/dist/src/browser/codex-iab/chatgpt-client.js.map +1 -1
  17. package/dist/src/cli/main.js +150 -8
  18. package/dist/src/cli/main.js.map +1 -1
  19. package/dist/src/core/controller-loop.d.ts +6 -0
  20. package/dist/src/core/controller-loop.js +240 -74
  21. package/dist/src/core/controller-loop.js.map +1 -1
  22. package/dist/src/core/run-status.d.ts +50 -0
  23. package/dist/src/core/run-status.js +169 -0
  24. package/dist/src/core/run-status.js.map +1 -0
  25. package/dist/src/core/state-machine.d.ts +2 -1
  26. package/dist/src/core/state-machine.js +22 -1
  27. package/dist/src/core/state-machine.js.map +1 -1
  28. package/dist/src/jobs/status.d.ts +6 -0
  29. package/dist/src/jobs/status.js.map +1 -1
  30. package/dist/src/jobs/supervisor.d.ts +2 -0
  31. package/dist/src/jobs/supervisor.js +44 -12
  32. package/dist/src/jobs/supervisor.js.map +1 -1
  33. package/dist/src/protocol/types.d.ts +1 -1
  34. package/dist/src/protocol/validate-command.js +18 -0
  35. package/dist/src/protocol/validate-command.js.map +1 -1
  36. package/dist/src/router/resolver.js +7 -1
  37. package/dist/src/router/resolver.js.map +1 -1
  38. package/dist/src/runners/process-runner.d.ts +2 -2
  39. package/dist/src/runners/process-runner.js +56 -5
  40. package/dist/src/runners/process-runner.js.map +1 -1
  41. package/dist/src/runners/runner-adapter.d.ts +9 -2
  42. package/dist/src/runners/runner-adapter.js.map +1 -1
  43. package/dist/src/state/cancellation.d.ts +42 -0
  44. package/dist/src/state/cancellation.js +160 -0
  45. package/dist/src/state/cancellation.js.map +1 -0
  46. package/dist/src/state/paths.d.ts +3 -0
  47. package/dist/src/state/paths.js +3 -0
  48. package/dist/src/state/paths.js.map +1 -1
  49. package/dist/src/state/runtime-lease.d.ts +28 -0
  50. package/dist/src/state/runtime-lease.js +179 -0
  51. package/dist/src/state/runtime-lease.js.map +1 -0
  52. package/dist/src/version.d.ts +1 -1
  53. package/dist/src/version.js +1 -1
  54. package/docs/compatibility.md +3 -1
  55. package/docs/controller-protocol.md +4 -2
  56. package/docs/runner-contract.md +7 -1
  57. package/docs/state-and-recovery.md +34 -9
  58. package/package.json +3 -2
  59. package/schemas/controller-observation.schema.json +1 -1
  60. package/scripts/validate-plugin.mjs +66 -0
  61. package/skills/cueline/SKILL.md +33 -5
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueline",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Use a ChatGPT web conversation as the controller for durable local agent work.",
5
5
  "author": {
6
6
  "name": "CueLine contributors"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueline",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Use a ChatGPT web conversation as the controller for durable local agent work.",
5
5
  "author": {
6
6
  "name": "CueLine contributors"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3 - 2026-07-15
4
+
5
+ ### Fixed
6
+
7
+ - Add `cueline run status <run-id> --json` with accepted-controller-response evidence, the last accepted action and job keys, runtime ownership, safe next action, cancellation requests, and derived `orphaned` jobs. A new session no longer calls a persisted `running` value active when no live owner is proven.
8
+ - Add an owner heartbeat lease. A second session refuses to continue an active run, and a missing or stale owner requires inspection instead of another controller send.
9
+ - Run independent all-`advise` dispatches concurrently. Any dispatch containing `work` remains serial to avoid overlapping mutations.
10
+ - Add durable `run cancel` / `run stop` and `job cancel` requests, API `AbortSignal` support, and `runTimeoutMs`. Active `advise` cancellation becomes `cancelled`; interrupted `work` or ownerless legacy work becomes `ambiguous`.
11
+ - Persist job run ID, job key, lane, mode, and child PID for diagnosis. `cueline jobs --json` reports an observed status and never uses PID alone as kill authorization.
12
+ - Reject unsupported `runner_id` explicitly, require `runner`, and explain when a runner ID such as `codex-default` was placed in `lane`.
13
+ - Preserve built-in-browser attach failures instead of converting them to an empty tab list. Retry safe pre-submit attachment once, recheck an empty discovery result once, cache a new tab only after it loads, and use at most one CUA coordinate fallback after a transient send-locator failure.
14
+
15
+ ### Documentation
16
+
17
+ - Require `run status` before continuation and distinguish controller response acceptance, local job execution, runtime ownership, and outer waiter timeout.
18
+ - Document cancellation, run-level deadlines, `advise` concurrency, `work` serialization, and stale-job recovery semantics.
19
+
3
20
  ## 0.1.2 - 2026-07-15
4
21
 
5
22
  ### Fixed
package/README.ja.md CHANGED
@@ -44,15 +44,15 @@ ChatGPT Pro のサブスクリプションと、選択された Pro モデルは
44
44
  npm レジストリからインストールします。
45
45
 
46
46
  ```bash
47
- npm install -g cueline@0.1.2
47
+ npm install -g cueline@0.1.3
48
48
  cueline install
49
49
  cueline doctor
50
50
  ```
51
51
 
52
- フォールバックとして、[v0.1.2 リリース](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.2) のパッケージ済み tarball をインストールすることもできます。同じリリースに `.sha256` チェックサムも置いてあります。
52
+ フォールバックとして、[v0.1.3 リリース](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.3) のパッケージ済み tarball をインストールすることもできます。同じリリースに `.sha256` チェックサムも置いてあります。
53
53
 
54
54
  ```bash
55
- npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.2/cueline-0.1.2.tgz
55
+ npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.3/cueline-0.1.3.tgz
56
56
  cueline install
57
57
  cueline doctor
58
58
  ```
@@ -89,7 +89,8 @@ import { createCodexIabAdapter, runCueLine } from "cueline";
89
89
  const result = await runCueLine({
90
90
  request: "Inspect the repository, delegate an implementation plan, and report the evidence.",
91
91
  browser: createCodexIabAdapter(),
92
- // 任意:conversationUrl、routingConfig / routingConfigPath、home、cwd、limits。
92
+ // 任意:conversationUrl、routingConfig / routingConfigPath、home、cwd、
93
+ // runTimeoutMs、signal、ジョブごと/既定の期限。
93
94
  });
94
95
 
95
96
  if (result.status === "complete") {
@@ -99,7 +100,7 @@ if (result.status === "complete") {
99
100
 
100
101
  Codex のランタイムでは、`cueline api path` が出力する絶対パスのモジュールを import します。それがインストールしたパッケージのビルド済み API です。
101
102
 
102
- `startCueLineRun` が明示的な開始点です(`runCueLine` はその別名)。`continueCueLineRun({ runId })` は中断した実行を同じ会話で再開し、新しいアダプターを渡さないかぎり保存済みの会話 URL を再利用します。`loadCueLineRunState(runId)` は永続化された状態を読むだけで、何も駆動しません。すでに `complete` または `blocked` に達した実行はそのまま返され、二度とディスパッチされません。
103
+ `startCueLineRun` が明示的な開始点です(`runCueLine` はその別名)。`continueCueLineRun({ runId })` は中断した実行を同じ会話で再開し、新しいアダプターを渡さないかぎり保存済みの会話 URL を再利用します。`loadCueLineRunState(runId)` は永続化された状態を読むだけで、何も駆動しません。すでに `complete`、`blocked`、または `cancelled` に達した実行はそのまま返され、二度とディスパッチされません。再開前に `cueline run status <run-id> --json` を実行してください。応答が受理済みで phase が `jobs_running` なら、ChatGPT の応答後にローカルジョブを実行中です。
103
104
 
104
105
  ## CLI
105
106
 
@@ -110,7 +111,7 @@ $ cueline install
110
111
  CueLine skill installed: /Users/you/.codex/skills/cueline
111
112
 
112
113
  $ cueline doctor
113
- CueLine 0.1.2
114
+ CueLine 0.1.3
114
115
  status ok
115
116
  node 22.14.0 ok
116
117
  config /usr/local/lib/node_modules/cueline/config/routing.default.json valid
@@ -126,6 +127,12 @@ default codex-default available
126
127
  $ cueline jobs
127
128
  No jobs.
128
129
 
130
+ $ cueline run status run_... --json
131
+ {"status":"running","phase":"jobs_running","runtime":{"ownership":"active"},...}
132
+
133
+ $ cueline run cancel run_...
134
+ run_... requested affected_jobs=0
135
+
129
136
  $ cueline config path
130
137
  /usr/local/lib/node_modules/cueline/config/routing.default.json
131
138
 
@@ -145,6 +152,8 @@ Node が古すぎる場合、あるいは解決できるレーンが一つもな
145
152
 
146
153
  ```text
147
154
  runs/<run-id>/events.jsonl 追記のみ、正本
155
+ runs/<run-id>/runtime.json ライブ owner の heartbeat 証拠
156
+ runs/<run-id>/cancel.json 存在する場合は永続キャンセル要求
148
157
  runs/<run-id>/snapshot.json リプレイの最適化、破棄可能
149
158
  jobs/<job-id>.json ジョブごとの実行証拠
150
159
  ```
package/README.ko.md CHANGED
@@ -44,15 +44,15 @@ ChatGPT Pro 구독과 선택된 Pro 모델은 서로 다른 것입니다. 계정
44
44
  npm 레지스트리에서 설치합니다:
45
45
 
46
46
  ```bash
47
- npm install -g cueline@0.1.2
47
+ npm install -g cueline@0.1.3
48
48
  cueline install
49
49
  cueline doctor
50
50
  ```
51
51
 
52
- 대안으로, [v0.1.2 릴리스](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.2)의 패키지 tarball을 설치할 수도 있습니다. 같은 릴리스에 `.sha256` 체크섬도 함께 있습니다.
52
+ 대안으로, [v0.1.3 릴리스](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.3)의 패키지 tarball을 설치할 수도 있습니다. 같은 릴리스에 `.sha256` 체크섬도 함께 있습니다.
53
53
 
54
54
  ```bash
55
- npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.2/cueline-0.1.2.tgz
55
+ npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.3/cueline-0.1.3.tgz
56
56
  cueline install
57
57
  cueline doctor
58
58
  ```
@@ -89,7 +89,8 @@ import { createCodexIabAdapter, runCueLine } from "cueline";
89
89
  const result = await runCueLine({
90
90
  request: "Inspect the repository, delegate an implementation plan, and report the evidence.",
91
91
  browser: createCodexIabAdapter(),
92
- // 선택: conversationUrl, routingConfig / routingConfigPath, home, cwd, limits.
92
+ // 선택: conversationUrl, routingConfig / routingConfigPath, home, cwd,
93
+ // runTimeoutMs, signal, 작업별/기본 제한 시간.
93
94
  });
94
95
 
95
96
  if (result.status === "complete") {
@@ -99,7 +100,7 @@ if (result.status === "complete") {
99
100
 
100
101
  Codex 런타임에서는 `cueline api path`가 출력하는 절대 경로 모듈을 import하세요. 그것이 설치한 패키지의 빌드된 API입니다.
101
102
 
102
- `startCueLineRun`이 명시적인 시작점입니다(`runCueLine`은 그 별칭). `continueCueLineRun({ runId })`은 중단된 실행을 같은 대화에서 재개하며, 새 어댑터를 넘기지 않는 한 저장된 대화 URL을 재사용합니다. `loadCueLineRunState(runId)`는 저장된 상태를 읽기만 하고 아무것도 구동하지 않습니다. 이미 `complete`나 `blocked`에 도달한 실행은 그대로 반환되며, 두 번 디스패치되지 않습니다.
103
+ `startCueLineRun`이 명시적인 시작점입니다(`runCueLine`은 그 별칭). `continueCueLineRun({ runId })`은 중단된 실행을 같은 대화에서 재개하며, 새 어댑터를 넘기지 않는 한 저장된 대화 URL을 재사용합니다. `loadCueLineRunState(runId)`는 저장된 상태를 읽기만 하고 아무것도 구동하지 않습니다. 이미 `complete`, `blocked`, `cancelled`에 도달한 실행은 그대로 반환되며, 두 번 디스패치되지 않습니다. 재개 전 `cueline run status <run-id> --json`을 실행하십시오. 응답이 수락되었고 phase가 `jobs_running`이면 ChatGPT 응답은 끝났고 로컬 작업이 실행 중입니다.
103
104
 
104
105
  ## CLI
105
106
 
@@ -110,7 +111,7 @@ $ cueline install
110
111
  CueLine skill installed: /Users/you/.codex/skills/cueline
111
112
 
112
113
  $ cueline doctor
113
- CueLine 0.1.2
114
+ CueLine 0.1.3
114
115
  status ok
115
116
  node 22.14.0 ok
116
117
  config /usr/local/lib/node_modules/cueline/config/routing.default.json valid
@@ -126,6 +127,12 @@ default codex-default available
126
127
  $ cueline jobs
127
128
  No jobs.
128
129
 
130
+ $ cueline run status run_... --json
131
+ {"status":"running","phase":"jobs_running","runtime":{"ownership":"active"},...}
132
+
133
+ $ cueline run cancel run_...
134
+ run_... requested affected_jobs=0
135
+
129
136
  $ cueline config path
130
137
  /usr/local/lib/node_modules/cueline/config/routing.default.json
131
138
 
@@ -145,6 +152,8 @@ Node 버전이 너무 낮거나 해석 가능한 레인이 하나도 없으면 `
145
152
 
146
153
  ```text
147
154
  runs/<run-id>/events.jsonl 추가 전용, 정본
155
+ runs/<run-id>/runtime.json 활성 owner의 heartbeat 증거
156
+ runs/<run-id>/cancel.json 존재할 때 지속 취소 요청
148
157
  runs/<run-id>/snapshot.json 재생 최적화용, 버려도 무방
149
158
  jobs/<job-id>.json 작업별 실행 증거
150
159
  ```
package/README.md CHANGED
@@ -44,15 +44,15 @@ You need Node.js 22+, Codex with its built-in Browser, and — for the bundled d
44
44
  Install from the npm registry:
45
45
 
46
46
  ```bash
47
- npm install -g cueline@0.1.2
47
+ npm install -g cueline@0.1.3
48
48
  cueline install
49
49
  cueline doctor
50
50
  ```
51
51
 
52
- As a fallback, install the packaged tarball from the [v0.1.2 release](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.2), which also carries its `.sha256` checksum:
52
+ As a fallback, install the packaged tarball from the [v0.1.3 release](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.3), which also carries its `.sha256` checksum:
53
53
 
54
54
  ```bash
55
- npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.2/cueline-0.1.2.tgz
55
+ npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.3/cueline-0.1.3.tgz
56
56
  cueline install
57
57
  cueline doctor
58
58
  ```
@@ -89,7 +89,8 @@ import { createCodexIabAdapter, runCueLine } from "cueline";
89
89
  const result = await runCueLine({
90
90
  request: "Inspect the repository, delegate an implementation plan, and report the evidence.",
91
91
  browser: createCodexIabAdapter(),
92
- // Optional: conversationUrl, routingConfig / routingConfigPath, home, cwd, limits.
92
+ // Optional: conversationUrl, routingConfig / routingConfigPath, home, cwd,
93
+ // runTimeoutMs, signal, and per-job/default limits.
93
94
  });
94
95
 
95
96
  if (result.status === "complete") {
@@ -97,7 +98,7 @@ if (result.status === "complete") {
97
98
  }
98
99
  ```
99
100
 
100
- `startCueLineRun` is the explicit start (`runCueLine` is its alias). `continueCueLineRun({ runId })` resumes an interrupted run in the same conversation, and reuses the stored conversation URL unless you hand it a new adapter. `loadCueLineRunState(runId)` reads persisted state without driving anything. A run that already reached `complete` or `blocked` is returned as-is, never dispatched twice.
101
+ `startCueLineRun` is the explicit start (`runCueLine` is its alias). `continueCueLineRun({ runId })` resumes an interrupted run in the same conversation, and reuses the stored conversation URL unless you hand it a new adapter. `loadCueLineRunState(runId)` reads persisted state without driving anything. A run that already reached `complete`, `blocked`, or `cancelled` is returned as-is, never dispatched twice. Before continuation, run `cueline run status <run-id> --json`: an accepted response plus `jobs_running` means ChatGPT already answered and local jobs are executing.
101
102
 
102
103
  Inside Codex's runtime, import the absolute module that `cueline api path` prints — that is the built API of the package you installed.
103
104
 
@@ -110,7 +111,7 @@ $ cueline install
110
111
  CueLine skill installed: /Users/you/.codex/skills/cueline
111
112
 
112
113
  $ cueline doctor
113
- CueLine 0.1.2
114
+ CueLine 0.1.3
114
115
  status ok
115
116
  node 22.14.0 ok
116
117
  config /usr/local/lib/node_modules/cueline/config/routing.default.json valid
@@ -126,6 +127,12 @@ default codex-default available
126
127
  $ cueline jobs
127
128
  No jobs.
128
129
 
130
+ $ cueline run status run_... --json
131
+ {"status":"running","phase":"jobs_running","runtime":{"ownership":"active"},...}
132
+
133
+ $ cueline run cancel run_...
134
+ run_... requested affected_jobs=0
135
+
129
136
  $ cueline config path
130
137
  /usr/local/lib/node_modules/cueline/config/routing.default.json
131
138
 
@@ -145,6 +152,8 @@ State lives under `CUELINE_HOME`:
145
152
 
146
153
  ```text
147
154
  runs/<run-id>/events.jsonl append-only, authoritative
155
+ runs/<run-id>/runtime.json live-owner heartbeat evidence
156
+ runs/<run-id>/cancel.json durable cancellation request, when present
148
157
  runs/<run-id>/snapshot.json a replay optimization, disposable
149
158
  jobs/<job-id>.json per-job execution evidence
150
159
  ```
package/README.zh-CN.md CHANGED
@@ -44,15 +44,15 @@ ChatGPT Pro 订阅套餐与“选定的 Pro 模型”是两回事。账号或个
44
44
  从 npm registry 安装:
45
45
 
46
46
  ```bash
47
- npm install -g cueline@0.1.2
47
+ npm install -g cueline@0.1.3
48
48
  cueline install
49
49
  cueline doctor
50
50
  ```
51
51
 
52
- 作为后备,也可以安装 [v0.1.2 release](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.2) 上的打包 tarball,该 release 同时附带它的 `.sha256` 校验值:
52
+ 作为后备,也可以安装 [v0.1.3 release](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.3) 上的打包 tarball,该 release 同时附带它的 `.sha256` 校验值:
53
53
 
54
54
  ```bash
55
- npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.2/cueline-0.1.2.tgz
55
+ npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.3/cueline-0.1.3.tgz
56
56
  cueline install
57
57
  cueline doctor
58
58
  ```
@@ -89,7 +89,8 @@ import { createCodexIabAdapter, runCueLine } from "cueline";
89
89
  const result = await runCueLine({
90
90
  request: "Inspect the repository, delegate an implementation plan, and report the evidence.",
91
91
  browser: createCodexIabAdapter(),
92
- // 可选:conversationUrl、routingConfig / routingConfigPath、home、cwd、limits。
92
+ // 可选:conversationUrl、routingConfig / routingConfigPath、home、cwd、
93
+ // runTimeoutMs、signal,以及作业/默认期限。
93
94
  });
94
95
 
95
96
  if (result.status === "complete") {
@@ -99,7 +100,7 @@ if (result.status === "complete") {
99
100
 
100
101
  在 Codex 的 runtime 里,import `cueline api path` 打印出的那个绝对路径模块——那就是你安装的那份包构建出来的 API。
101
102
 
102
- `startCueLineRun` 是显式的启动入口(`runCueLine` 是它的别名)。`continueCueLineRun({ runId })` 会在同一个会话中续跑被中断的运行,并复用已保存的会话链接,除非你传入新的 adapter。`loadCueLineRunState(runId)` 只读取已持久化的状态,不驱动任何东西。已经到达 `complete` 或 `blocked` 的运行会原样返回,绝不会被再次派发。
103
+ `startCueLineRun` 是显式的启动入口(`runCueLine` 是它的别名)。`continueCueLineRun({ runId })` 会在同一个会话中续跑被中断的运行,并复用已保存的会话链接,除非你传入新的 adapter。`loadCueLineRunState(runId)` 只读取已持久化的状态,不驱动任何东西。已经到达 `complete`、`blocked` 或 `cancelled` 的运行会原样返回,绝不会被再次派发。续跑前先执行 `cueline run status <run-id> --json`;已接受回复且 phase 为 `jobs_running` 表示 ChatGPT 已回复,本地作业正在执行。
103
104
 
104
105
  ## CLI
105
106
 
@@ -110,7 +111,7 @@ $ cueline install
110
111
  CueLine skill installed: /Users/you/.codex/skills/cueline
111
112
 
112
113
  $ cueline doctor
113
- CueLine 0.1.2
114
+ CueLine 0.1.3
114
115
  status ok
115
116
  node 22.14.0 ok
116
117
  config /usr/local/lib/node_modules/cueline/config/routing.default.json valid
@@ -126,6 +127,12 @@ default codex-default available
126
127
  $ cueline jobs
127
128
  No jobs.
128
129
 
130
+ $ cueline run status run_... --json
131
+ {"status":"running","phase":"jobs_running","runtime":{"ownership":"active"},...}
132
+
133
+ $ cueline run cancel run_...
134
+ run_... requested affected_jobs=0
135
+
129
136
  $ cueline config path
130
137
  /usr/local/lib/node_modules/cueline/config/routing.default.json
131
138
 
@@ -145,6 +152,8 @@ CueLine skill removed: /Users/you/.codex/skills/cueline
145
152
 
146
153
  ```text
147
154
  runs/<run-id>/events.jsonl 仅追加、具权威性
155
+ runs/<run-id>/runtime.json 活跃 owner 的 heartbeat 证据
156
+ runs/<run-id>/cancel.json 存在时表示持久取消请求
148
157
  runs/<run-id>/snapshot.json 重放优化产物,可丢弃
149
158
  jobs/<job-id>.json 每个作业的执行证据
150
159
  ```
package/README.zh-TW.md CHANGED
@@ -44,15 +44,15 @@ ChatGPT Pro 訂閱方案與「選定的 Pro 模型」是兩回事。帳號或個
44
44
  從 npm registry 安裝:
45
45
 
46
46
  ```bash
47
- npm install -g cueline@0.1.2
47
+ npm install -g cueline@0.1.3
48
48
  cueline install
49
49
  cueline doctor
50
50
  ```
51
51
 
52
- 作為備援,也可以安裝 [v0.1.2 release](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.2) 上的打包 tarball,該 release 同時附上它的 `.sha256` 校驗碼:
52
+ 作為備援,也可以安裝 [v0.1.3 release](https://github.com/Seraphim0916/cueline/releases/tag/v0.1.3) 上的打包 tarball,該 release 同時附上它的 `.sha256` 校驗碼:
53
53
 
54
54
  ```bash
55
- npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.2/cueline-0.1.2.tgz
55
+ npm install -g https://github.com/Seraphim0916/cueline/releases/download/v0.1.3/cueline-0.1.3.tgz
56
56
  cueline install
57
57
  cueline doctor
58
58
  ```
@@ -89,7 +89,8 @@ import { createCodexIabAdapter, runCueLine } from "cueline";
89
89
  const result = await runCueLine({
90
90
  request: "Inspect the repository, delegate an implementation plan, and report the evidence.",
91
91
  browser: createCodexIabAdapter(),
92
- // 選填:conversationUrl、routingConfig / routingConfigPath、home、cwd、limits。
92
+ // 選填:conversationUrl、routingConfig / routingConfigPath、home、cwd、
93
+ // runTimeoutMs、signal,以及各工作/預設期限。
93
94
  });
94
95
 
95
96
  if (result.status === "complete") {
@@ -99,7 +100,7 @@ if (result.status === "complete") {
99
100
 
100
101
  在 Codex 的 runtime 裡,import `cueline api path` 印出的那個絕對路徑模組——那就是你安裝的那份套件建置出來的 API。
101
102
 
102
- `startCueLineRun` 是明確的啟動入口(`runCueLine` 是它的別名)。`continueCueLineRun({ runId })` 會在同一個對話裡續跑被中斷的執行,並沿用已保存的對話網址,除非你另外傳入新的 adapter。`loadCueLineRunState(runId)` 只讀取已保存的狀態,不驅動任何東西。已經走到 `complete` 或 `blocked` 的執行會原樣回傳,絕不會被再派工一次。
103
+ `startCueLineRun` 是明確的啟動入口(`runCueLine` 是它的別名)。`continueCueLineRun({ runId })` 會在同一個對話裡續跑被中斷的執行,並沿用已保存的對話網址,除非你另外傳入新的 adapter。`loadCueLineRunState(runId)` 只讀取已保存的狀態,不驅動任何東西。已經走到 `complete`、`blocked` 或 `cancelled` 的執行會原樣回傳,絕不會被再派工一次。續跑前先執行 `cueline run status <run-id> --json`;若回覆已採用且 phase 是 `jobs_running`,代表 ChatGPT 已回覆,現在跑的是本機工作。
103
104
 
104
105
  ## CLI
105
106
 
@@ -110,7 +111,7 @@ $ cueline install
110
111
  CueLine skill installed: /Users/you/.codex/skills/cueline
111
112
 
112
113
  $ cueline doctor
113
- CueLine 0.1.2
114
+ CueLine 0.1.3
114
115
  status ok
115
116
  node 22.14.0 ok
116
117
  config /usr/local/lib/node_modules/cueline/config/routing.default.json valid
@@ -126,6 +127,12 @@ default codex-default available
126
127
  $ cueline jobs
127
128
  No jobs.
128
129
 
130
+ $ cueline run status run_... --json
131
+ {"status":"running","phase":"jobs_running","runtime":{"ownership":"active"},...}
132
+
133
+ $ cueline run cancel run_...
134
+ run_... requested affected_jobs=0
135
+
129
136
  $ cueline config path
130
137
  /usr/local/lib/node_modules/cueline/config/routing.default.json
131
138
 
@@ -145,6 +152,8 @@ Node 版本太舊、或沒有任何通道解析得出來時,`cueline doctor`
145
152
 
146
153
  ```text
147
154
  runs/<run-id>/events.jsonl 只追加、具權威性
155
+ runs/<run-id>/runtime.json 活 owner 的 heartbeat 證據
156
+ runs/<run-id>/cancel.json 存在時代表持久取消要求
148
157
  runs/<run-id>/snapshot.json 重播的最佳化產物,可丟棄
149
158
  jobs/<job-id>.json 每個工作的執行證據
150
159
  ```
package/dist/src/api.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { BrowserAdapter } from "./browser/browser-adapter.js";
2
2
  import { createCodexIabAdapter, type CodexIabAdapterOptions } from "./browser/codex-iab/chatgpt-client.js";
3
3
  import { type CueLineResult } from "./core/controller-loop.js";
4
+ import { type CueLineRunStatusSummary } from "./core/run-status.js";
4
5
  import { type CueLineRunState } from "./core/state-machine.js";
5
6
  import type { RoutingConfig } from "./router/types.js";
6
7
  export interface CueLineRuntimeOptions {
@@ -16,6 +17,9 @@ export interface CueLineRuntimeOptions {
16
17
  maxRounds?: number;
17
18
  maxRepairAttempts?: number;
18
19
  now?: () => Date;
20
+ signal?: AbortSignal;
21
+ cancellationPollIntervalMs?: number;
22
+ runTimeoutMs?: number;
19
23
  }
20
24
  export interface StartCueLineRunOptions extends CueLineRuntimeOptions {
21
25
  request: string;
@@ -26,11 +30,29 @@ export interface ContinueCueLineRunOptions extends CueLineRuntimeOptions {
26
30
  reconcileRequestId?: string;
27
31
  abandonOtherPendingTurns?: boolean;
28
32
  }
33
+ export interface CueLineRunCancellationResult {
34
+ runId: string;
35
+ outcome: "requested" | "cancelled" | "already_terminal";
36
+ affectedJobs: number;
37
+ }
38
+ export interface CueLineJobCancellationResult {
39
+ runId: string;
40
+ jobId: string;
41
+ outcome: "requested" | "ambiguous" | "already_terminal";
42
+ }
29
43
  export declare function defaultRoutingConfigPath(): string;
30
44
  export declare function routingConfigPath(environment?: NodeJS.ProcessEnv, explicitPath?: string): string;
31
45
  export declare function loadCueLineRunState(runId: string, options?: Pick<CueLineRuntimeOptions, "home" | "environment">): Promise<CueLineRunState>;
46
+ export declare function loadCueLineRunStatus(runId: string, options?: Pick<CueLineRuntimeOptions, "home" | "environment" | "now">): Promise<CueLineRunStatusSummary>;
47
+ export declare function cancelCueLineRun(runId: string, options?: Pick<CueLineRuntimeOptions, "home" | "environment" | "now"> & {
48
+ reason?: string;
49
+ }): Promise<CueLineRunCancellationResult>;
50
+ export declare function cancelCueLineJob(runId: string, jobId: string, options?: Pick<CueLineRuntimeOptions, "home" | "environment" | "now"> & {
51
+ reason?: string;
52
+ }): Promise<CueLineJobCancellationResult>;
32
53
  export declare function startCueLineRun(options: StartCueLineRunOptions): Promise<CueLineResult>;
33
54
  export declare function runCueLine(options: StartCueLineRunOptions): Promise<CueLineResult>;
34
55
  export declare function continueCueLineRun(options: ContinueCueLineRunOptions): Promise<CueLineResult>;
35
56
  export { createCodexIabAdapter };
36
- export type { BrowserAdapter, CueLineResult, CueLineRunState, RoutingConfig };
57
+ export { CUELINE_VERSION } from "./version.js";
58
+ export type { BrowserAdapter, CueLineResult, CueLineRunState, CueLineRunStatusSummary, RoutingConfig, };
package/dist/src/api.js CHANGED
@@ -3,6 +3,7 @@ import { createCodexIabAdapter, } from "./browser/codex-iab/chatgpt-client.js";
3
3
  import { continueControllerLoop, runControllerLoop, } from "./core/controller-loop.js";
4
4
  import { CueLineError } from "./core/errors.js";
5
5
  import { runtimeCwd, runtimeEnvironment } from "./core/runtime.js";
6
+ import { acceptedControllerCommandEvidence, assertRunCanContinue, summarizeCueLineRunState, } from "./core/run-status.js";
6
7
  import { initialRunState, reduceRunState, } from "./core/state-machine.js";
7
8
  import { JobStatusStore } from "./jobs/status.js";
8
9
  import { JobSupervisor } from "./jobs/supervisor.js";
@@ -12,7 +13,10 @@ import { materializeRunnerSpec } from "./router/materialize.js";
12
13
  import { resolveRoute } from "./router/resolver.js";
13
14
  import { ProcessRunner } from "./runners/process-runner.js";
14
15
  import { RunnerRegistry } from "./runners/registry.js";
15
- import { defaultCueLineHome } from "./state/paths.js";
16
+ import { readCancellationObservation, requestJobCancellation, requestRunCancellation, } from "./state/cancellation.js";
17
+ import { readEvents } from "./state/event-log.js";
18
+ import { defaultCueLineHome, runPaths } from "./state/paths.js";
19
+ import { readRuntimeLease } from "./state/runtime-lease.js";
16
20
  import { RunStore } from "./state/store.js";
17
21
  function assertNotNested(environment) {
18
22
  if (environment.CUELINE_DEPTH !== undefined) {
@@ -25,7 +29,7 @@ export function defaultRoutingConfigPath() {
25
29
  export function routingConfigPath(environment = runtimeEnvironment(), explicitPath) {
26
30
  return explicitPath ?? environment.CUELINE_CONFIG ?? defaultRoutingConfigPath();
27
31
  }
28
- async function persistedRunState(home, runId) {
32
+ async function persistedRunStore(home, runId) {
29
33
  const store = await RunStore.load({
30
34
  home,
31
35
  runId,
@@ -36,10 +40,15 @@ async function persistedRunState(home, runId) {
36
40
  if (state.request === "") {
37
41
  throw new CueLineError("RUN_NOT_FOUND", `No persisted CueLine run '${runId}' was found.`);
38
42
  }
39
- return state;
43
+ return store;
44
+ }
45
+ async function persistedRunState(home, runId) {
46
+ return (await persistedRunStore(home, runId)).state;
40
47
  }
41
48
  function terminalResult(state) {
42
- if (state.status !== "complete" && state.status !== "blocked") {
49
+ if (state.status !== "complete" &&
50
+ state.status !== "blocked" &&
51
+ state.status !== "cancelled") {
43
52
  throw new CueLineError("RUN_NOT_TERMINAL", "CueLine result requested before a terminal state.");
44
53
  }
45
54
  return {
@@ -47,6 +56,7 @@ function terminalResult(state) {
47
56
  status: state.status,
48
57
  ...(state.finalDeliveryText === null ? {} : { finalDeliveryText: state.finalDeliveryText }),
49
58
  ...(state.conversationUrl === null ? {} : { conversationUrl: state.conversationUrl }),
59
+ ...(state.cancelledReason === null ? {} : { cancelledReason: state.cancelledReason }),
50
60
  state,
51
61
  };
52
62
  }
@@ -54,6 +64,86 @@ export async function loadCueLineRunState(runId, options = {}) {
54
64
  const environment = options.environment ?? runtimeEnvironment();
55
65
  return persistedRunState(options.home ?? defaultCueLineHome(environment), runId);
56
66
  }
67
+ export async function loadCueLineRunStatus(runId, options = {}) {
68
+ const environment = options.environment ?? runtimeEnvironment();
69
+ const home = options.home ?? defaultCueLineHome(environment);
70
+ const store = await persistedRunStore(home, runId);
71
+ const runtime = await readRuntimeLease(home, runId, {
72
+ ...(options.now === undefined ? {} : { now: options.now }),
73
+ });
74
+ const cancellation = await readCancellationObservation(home, runId);
75
+ const acceptedCommand = acceptedControllerCommandEvidence(await readEvents(runPaths(home, runId).events));
76
+ return summarizeCueLineRunState(store.state, store.lastSequence, runtime, cancellation, acceptedCommand);
77
+ }
78
+ function isTerminalRun(state) {
79
+ return (state.status === "complete" || state.status === "blocked" || state.status === "cancelled");
80
+ }
81
+ export async function cancelCueLineRun(runId, options = {}) {
82
+ const environment = options.environment ?? runtimeEnvironment();
83
+ const home = options.home ?? defaultCueLineHome(environment);
84
+ const store = await persistedRunStore(home, runId);
85
+ if (isTerminalRun(store.state)) {
86
+ return { runId, outcome: "already_terminal", affectedJobs: 0 };
87
+ }
88
+ const reason = options.reason ?? "operator requested cancellation";
89
+ await requestRunCancellation(home, runId, reason, options.now);
90
+ const runtime = await readRuntimeLease(home, runId, {
91
+ ...(options.now === undefined ? {} : { now: options.now }),
92
+ });
93
+ if (runtime.ownership === "active" ||
94
+ runtime.ownership === "stale" ||
95
+ runtime.ownership === "invalid") {
96
+ return { runId, outcome: "requested", affectedJobs: 0 };
97
+ }
98
+ const active = Object.values(store.state.jobs).filter((job) => job.status === "pending" || job.status === "running");
99
+ for (const job of active) {
100
+ await store.append("job_status", {
101
+ job_id: job.jobId,
102
+ status: "ambiguous",
103
+ error: "Run cancelled without a verifiable active runtime; process outcome is unknown.",
104
+ });
105
+ }
106
+ await store.append("run_cancelled", { reason });
107
+ await store.snapshot();
108
+ return { runId, outcome: "cancelled", affectedJobs: active.length };
109
+ }
110
+ export async function cancelCueLineJob(runId, jobId, options = {}) {
111
+ const environment = options.environment ?? runtimeEnvironment();
112
+ const home = options.home ?? defaultCueLineHome(environment);
113
+ const store = await persistedRunStore(home, runId);
114
+ const job = store.state.jobs[jobId];
115
+ if (job === undefined) {
116
+ throw new CueLineError("JOB_NOT_FOUND", `No job '${jobId}' exists in run '${runId}'.`);
117
+ }
118
+ if (isTerminalRun(store.state) ||
119
+ (job.status !== "pending" && job.status !== "running")) {
120
+ return { runId, jobId, outcome: "already_terminal" };
121
+ }
122
+ const reason = options.reason ?? "operator requested job cancellation";
123
+ await requestJobCancellation(home, runId, jobId, reason, options.now);
124
+ const runtime = await readRuntimeLease(home, runId, {
125
+ ...(options.now === undefined ? {} : { now: options.now }),
126
+ });
127
+ if (runtime.ownership === "active" ||
128
+ runtime.ownership === "stale" ||
129
+ runtime.ownership === "invalid") {
130
+ return { runId, jobId, outcome: "requested" };
131
+ }
132
+ await store.append("job_status", {
133
+ job_id: jobId,
134
+ status: "ambiguous",
135
+ error: "Job cancelled without a verifiable active runtime; process outcome is unknown.",
136
+ });
137
+ if (store.state.status === "running") {
138
+ await store.append("run_failed", {
139
+ code: "JOB_CANCELLED_WITHOUT_ACTIVE_RUNTIME",
140
+ message: `Job '${jobId}' was marked ambiguous because no active owner could confirm termination.`,
141
+ stage: "job_cancellation",
142
+ });
143
+ }
144
+ await store.snapshot();
145
+ return { runId, jobId, outcome: "ambiguous" };
146
+ }
57
147
  async function resolvedRoutingConfig(options, environment) {
58
148
  return (options.routingConfig ??
59
149
  (await loadRoutingConfig(routingConfigPath(environment, options.routingConfigPath))));
@@ -78,7 +168,7 @@ function routingInstruction(config, availability) {
78
168
  .map((candidate) => candidate.id);
79
169
  return `${name} [${candidates.length > 0 ? candidates.join(", ") : "unavailable"}]`;
80
170
  });
81
- return `Available routing lanes: ${lanes.join("; ")}. Use only a listed available lane and optional runner id.`;
171
+ return `Available routing lanes: ${lanes.join("; ")}. Use only a listed lane. Select an optional candidate with the field runner; never use runner_id or place a runner ID in lane.`;
82
172
  }
83
173
  async function prepareRuntime(options, persistedConversationUrl) {
84
174
  const environment = options.environment ?? runtimeEnvironment();
@@ -126,6 +216,11 @@ export async function startCueLineRun(options) {
126
216
  ? {}
127
217
  : { maxRepairAttempts: options.maxRepairAttempts }),
128
218
  ...(options.now === undefined ? {} : { now: options.now }),
219
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
220
+ ...(options.cancellationPollIntervalMs === undefined
221
+ ? {}
222
+ : { cancellationPollIntervalMs: options.cancellationPollIntervalMs }),
223
+ ...(options.runTimeoutMs === undefined ? {} : { runTimeoutMs: options.runTimeoutMs }),
129
224
  });
130
225
  }
131
226
  export async function runCueLine(options) {
@@ -135,9 +230,12 @@ export async function continueCueLineRun(options) {
135
230
  const environment = options.environment ?? runtimeEnvironment();
136
231
  const home = options.home ?? defaultCueLineHome(environment);
137
232
  const state = await persistedRunState(home, options.runId);
138
- if (state.status === "complete" || state.status === "blocked") {
233
+ if (isTerminalRun(state)) {
139
234
  return terminalResult(state);
140
235
  }
236
+ assertRunCanContinue(state, await readRuntimeLease(home, options.runId, {
237
+ ...(options.now === undefined ? {} : { now: options.now }),
238
+ }), await readCancellationObservation(home, options.runId));
141
239
  assertNotNested(environment);
142
240
  const runtime = await prepareRuntime(options, state.conversationUrl ?? undefined);
143
241
  return continueControllerLoop({
@@ -154,7 +252,13 @@ export async function continueCueLineRun(options) {
154
252
  ? {}
155
253
  : { maxRepairAttempts: options.maxRepairAttempts }),
156
254
  ...(options.now === undefined ? {} : { now: options.now }),
255
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
256
+ ...(options.cancellationPollIntervalMs === undefined
257
+ ? {}
258
+ : { cancellationPollIntervalMs: options.cancellationPollIntervalMs }),
259
+ ...(options.runTimeoutMs === undefined ? {} : { runTimeoutMs: options.runTimeoutMs }),
157
260
  });
158
261
  }
159
262
  export { createCodexIabAdapter };
263
+ export { CUELINE_VERSION } from "./version.js";
160
264
  //# sourceMappingURL=api.js.map