cueline 0.1.6 → 0.1.7

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 (162) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +27 -0
  4. package/README.ja.md +29 -7
  5. package/README.ko.md +29 -7
  6. package/README.md +78 -11
  7. package/README.zh-CN.md +29 -7
  8. package/README.zh-TW.md +36 -8
  9. package/config/routing.schema.json +3 -0
  10. package/dist/src/api-caller-work.js +58 -14
  11. package/dist/src/api-caller-work.js.map +1 -1
  12. package/dist/src/api-contracts.d.ts +49 -0
  13. package/dist/src/api-controller-handoff.js +30 -38
  14. package/dist/src/api-controller-handoff.js.map +1 -1
  15. package/dist/src/api-run-verification.d.ts +3 -0
  16. package/dist/src/api-run-verification.js +174 -0
  17. package/dist/src/api-run-verification.js.map +1 -0
  18. package/dist/src/api-runtime-lifecycle.d.ts +6 -1
  19. package/dist/src/api-runtime-lifecycle.js +97 -8
  20. package/dist/src/api-runtime-lifecycle.js.map +1 -1
  21. package/dist/src/api.d.ts +17 -3
  22. package/dist/src/api.js +105 -38
  23. package/dist/src/api.js.map +1 -1
  24. package/dist/src/browser/browser-adapter.d.ts +15 -0
  25. package/dist/src/browser/codex-iab/bootstrap.d.ts +20 -0
  26. package/dist/src/browser/codex-iab/bootstrap.js +173 -9
  27. package/dist/src/browser/codex-iab/bootstrap.js.map +1 -1
  28. package/dist/src/browser/codex-iab/chatgpt-client.d.ts +3 -0
  29. package/dist/src/browser/codex-iab/chatgpt-client.js +115 -57
  30. package/dist/src/browser/codex-iab/chatgpt-client.js.map +1 -1
  31. package/dist/src/browser/codex-iab/probe.d.ts +36 -0
  32. package/dist/src/browser/codex-iab/probe.js +217 -0
  33. package/dist/src/browser/codex-iab/probe.js.map +1 -0
  34. package/dist/src/browser/codex-iab/recovery-evidence.d.ts +1 -1
  35. package/dist/src/browser/codex-iab/recovery-evidence.js +7 -10
  36. package/dist/src/browser/codex-iab/recovery-evidence.js.map +1 -1
  37. package/dist/src/browser/codex-iab/selectors.d.ts +1 -0
  38. package/dist/src/browser/codex-iab/selectors.js +1 -0
  39. package/dist/src/browser/codex-iab/selectors.js.map +1 -1
  40. package/dist/src/browser/codex-iab/send-button.d.ts +5 -0
  41. package/dist/src/browser/codex-iab/send-button.js +62 -0
  42. package/dist/src/browser/codex-iab/send-button.js.map +1 -0
  43. package/dist/src/browser/codex-iab/submission-url.js +6 -15
  44. package/dist/src/browser/codex-iab/submission-url.js.map +1 -1
  45. package/dist/src/browser/codex-iab/tab-discovery.js +45 -12
  46. package/dist/src/browser/codex-iab/tab-discovery.js.map +1 -1
  47. package/dist/src/browser/codex-iab/timing-options.d.ts +1 -0
  48. package/dist/src/browser/codex-iab/timing-options.js +5 -0
  49. package/dist/src/browser/codex-iab/timing-options.js.map +1 -0
  50. package/dist/src/cli/health-commands.d.ts +2 -0
  51. package/dist/src/cli/health-commands.js +191 -0
  52. package/dist/src/cli/health-commands.js.map +1 -0
  53. package/dist/src/cli/io.d.ts +4 -0
  54. package/dist/src/cli/io.js +2 -0
  55. package/dist/src/cli/io.js.map +1 -0
  56. package/dist/src/cli/main.d.ts +1 -5
  57. package/dist/src/cli/main.js +159 -77
  58. package/dist/src/cli/main.js.map +1 -1
  59. package/dist/src/cli/observation-commands.d.ts +2 -0
  60. package/dist/src/cli/observation-commands.js +239 -0
  61. package/dist/src/cli/observation-commands.js.map +1 -0
  62. package/dist/src/cli/run-status-view.d.ts +79 -0
  63. package/dist/src/cli/run-status-view.js +112 -0
  64. package/dist/src/cli/run-status-view.js.map +1 -0
  65. package/dist/src/core/controller-command-execution.d.ts +2 -2
  66. package/dist/src/core/controller-command-execution.js +82 -16
  67. package/dist/src/core/controller-command-execution.js.map +1 -1
  68. package/dist/src/core/controller-conversation-archive.d.ts +11 -0
  69. package/dist/src/core/controller-conversation-archive.js +109 -0
  70. package/dist/src/core/controller-conversation-archive.js.map +1 -0
  71. package/dist/src/core/controller-loop.d.ts +7 -1
  72. package/dist/src/core/controller-loop.js +114 -23
  73. package/dist/src/core/controller-loop.js.map +1 -1
  74. package/dist/src/core/controller-turn.d.ts +14 -2
  75. package/dist/src/core/controller-turn.js +109 -40
  76. package/dist/src/core/controller-turn.js.map +1 -1
  77. package/dist/src/core/controller-types.d.ts +2 -0
  78. package/dist/src/core/conversation-url.d.ts +3 -0
  79. package/dist/src/core/conversation-url.js +34 -0
  80. package/dist/src/core/conversation-url.js.map +1 -0
  81. package/dist/src/core/run-status.d.ts +9 -2
  82. package/dist/src/core/run-status.js +38 -25
  83. package/dist/src/core/run-status.js.map +1 -1
  84. package/dist/src/core/state-machine.d.ts +21 -1
  85. package/dist/src/core/state-machine.js +138 -13
  86. package/dist/src/core/state-machine.js.map +1 -1
  87. package/dist/src/core/timing.d.ts +9 -0
  88. package/dist/src/core/timing.js +22 -0
  89. package/dist/src/core/timing.js.map +1 -0
  90. package/dist/src/diagnostics/run-doctor.d.ts +22 -0
  91. package/dist/src/diagnostics/run-doctor.js +197 -0
  92. package/dist/src/diagnostics/run-doctor.js.map +1 -0
  93. package/dist/src/jobs/status.d.ts +5 -2
  94. package/dist/src/jobs/status.js +172 -12
  95. package/dist/src/jobs/status.js.map +1 -1
  96. package/dist/src/jobs/supervisor.js +57 -8
  97. package/dist/src/jobs/supervisor.js.map +1 -1
  98. package/dist/src/observation/run-handoff.d.ts +86 -0
  99. package/dist/src/observation/run-handoff.js +309 -0
  100. package/dist/src/observation/run-handoff.js.map +1 -0
  101. package/dist/src/observation/run-timeline.d.ts +32 -0
  102. package/dist/src/observation/run-timeline.js +278 -0
  103. package/dist/src/observation/run-timeline.js.map +1 -0
  104. package/dist/src/observation/run-watch.d.ts +15 -0
  105. package/dist/src/observation/run-watch.js +82 -0
  106. package/dist/src/observation/run-watch.js.map +1 -0
  107. package/dist/src/protocol/limits.d.ts +3 -0
  108. package/dist/src/protocol/limits.js +4 -0
  109. package/dist/src/protocol/limits.js.map +1 -0
  110. package/dist/src/protocol/lint-command.d.ts +24 -0
  111. package/dist/src/protocol/lint-command.js +210 -0
  112. package/dist/src/protocol/lint-command.js.map +1 -0
  113. package/dist/src/protocol/parse-command.js +4 -0
  114. package/dist/src/protocol/parse-command.js.map +1 -1
  115. package/dist/src/protocol/types.d.ts +12 -0
  116. package/dist/src/protocol/validate-command.js +76 -3
  117. package/dist/src/protocol/validate-command.js.map +1 -1
  118. package/dist/src/router/config-loader.js +23 -2
  119. package/dist/src/router/config-loader.js.map +1 -1
  120. package/dist/src/router/resolver.js +8 -3
  121. package/dist/src/router/resolver.js.map +1 -1
  122. package/dist/src/runners/process-runner.js +55 -12
  123. package/dist/src/runners/process-runner.js.map +1 -1
  124. package/dist/src/runners/runner-adapter.d.ts +2 -0
  125. package/dist/src/runners/runner-adapter.js.map +1 -1
  126. package/dist/src/state/atomic-write.d.ts +6 -0
  127. package/dist/src/state/atomic-write.js +43 -2
  128. package/dist/src/state/atomic-write.js.map +1 -1
  129. package/dist/src/state/cancellation.js +38 -4
  130. package/dist/src/state/cancellation.js.map +1 -1
  131. package/dist/src/state/event-log.js +5 -4
  132. package/dist/src/state/event-log.js.map +1 -1
  133. package/dist/src/state/private-directory.d.ts +5 -0
  134. package/dist/src/state/private-directory.js +37 -0
  135. package/dist/src/state/private-directory.js.map +1 -0
  136. package/dist/src/state/runtime-lease.js +19 -39
  137. package/dist/src/state/runtime-lease.js.map +1 -1
  138. package/dist/src/state/runtime-record-validation.d.ts +6 -0
  139. package/dist/src/state/runtime-record-validation.js +44 -0
  140. package/dist/src/state/runtime-record-validation.js.map +1 -0
  141. package/dist/src/state/runtime-retirement.js +58 -18
  142. package/dist/src/state/runtime-retirement.js.map +1 -1
  143. package/dist/src/state/runtime-takeover-intent.js +2 -2
  144. package/dist/src/state/runtime-takeover-intent.js.map +1 -1
  145. package/dist/src/state/store.d.ts +1 -0
  146. package/dist/src/state/store.js +12 -6
  147. package/dist/src/state/store.js.map +1 -1
  148. package/dist/src/version.d.ts +1 -1
  149. package/dist/src/version.js +1 -1
  150. package/docs/architecture.md +4 -4
  151. package/docs/compatibility.md +1 -1
  152. package/docs/controller-protocol.md +38 -7
  153. package/docs/experiments/protocol-lint.md +40 -0
  154. package/docs/experiments/run-doctor.md +39 -0
  155. package/docs/experiments/run-handoff.md +39 -0
  156. package/docs/experiments/run-timeline.md +35 -0
  157. package/docs/experiments/run-watch.md +31 -0
  158. package/docs/runner-contract.md +4 -3
  159. package/docs/state-and-recovery.md +15 -5
  160. package/package.json +1 -1
  161. package/schemas/controller-command.schema.json +89 -4
  162. package/skills/cueline/SKILL.md +13 -7
@@ -0,0 +1,191 @@
1
+ import { routingConfigPath } from "../api.js";
2
+ import { CueLineError } from "../core/errors.js";
3
+ import { executableAvailability } from "../router/availability.js";
4
+ import { loadRoutingConfig } from "../router/config-loader.js";
5
+ import { resolveRoute } from "../router/resolver.js";
6
+ import { defaultCueLineHome } from "../state/paths.js";
7
+ import { CUELINE_VERSION } from "../version.js";
8
+ function routingErrorCode(error) {
9
+ return error instanceof CueLineError ? error.code : "ROUTE_UNAVAILABLE";
10
+ }
11
+ async function collectRoutingReport(environment) {
12
+ const configPath = routingConfigPath(environment);
13
+ let config;
14
+ try {
15
+ config = await loadRoutingConfig(configPath);
16
+ }
17
+ catch {
18
+ return {
19
+ version: CUELINE_VERSION,
20
+ config: {
21
+ path: configPath,
22
+ valid: false,
23
+ errorCode: "ROUTING_CONFIG_INVALID",
24
+ },
25
+ availableLanes: 0,
26
+ lanes: [],
27
+ findings: [
28
+ {
29
+ code: "ROUTING_CONFIG_INVALID",
30
+ message: "Routing configuration could not be loaded.",
31
+ },
32
+ ],
33
+ };
34
+ }
35
+ const availability = executableAvailability(environment);
36
+ const lanes = [];
37
+ for (const [lane, laneConfig] of Object.entries(config.lanes)) {
38
+ if (!laneConfig.enabled) {
39
+ lanes.push({
40
+ name: lane,
41
+ enabled: false,
42
+ status: "disabled",
43
+ selectedRunnerId: null,
44
+ });
45
+ continue;
46
+ }
47
+ try {
48
+ const route = resolveRoute(lane, config, availability);
49
+ lanes.push({
50
+ name: lane,
51
+ enabled: true,
52
+ status: "available",
53
+ selectedRunnerId: route.candidate.id,
54
+ });
55
+ }
56
+ catch (error) {
57
+ lanes.push({
58
+ name: lane,
59
+ enabled: true,
60
+ status: "unavailable",
61
+ selectedRunnerId: null,
62
+ errorCode: routingErrorCode(error),
63
+ });
64
+ }
65
+ }
66
+ return {
67
+ version: CUELINE_VERSION,
68
+ config: { path: configPath, valid: true },
69
+ availableLanes: lanes.filter((lane) => lane.status === "available").length,
70
+ lanes,
71
+ findings: [],
72
+ };
73
+ }
74
+ async function routingCommand(json, environment, io) {
75
+ const report = await collectRoutingReport(environment);
76
+ if (json) {
77
+ io.stdout(JSON.stringify(report, null, 2));
78
+ }
79
+ else if (!report.config.valid) {
80
+ io.stdout(`config\t${report.config.path}\tinvalid\t${report.config.errorCode}`);
81
+ }
82
+ else {
83
+ for (const lane of report.lanes) {
84
+ io.stdout(`${lane.name}\t${lane.selectedRunnerId ?? "-"}\t${lane.status}${lane.errorCode === undefined ? "" : ` (${lane.errorCode})`}`);
85
+ }
86
+ }
87
+ return report.availableLanes > 0 ? 0 : 1;
88
+ }
89
+ async function collectDoctorReport(environment) {
90
+ const configPath = routingConfigPath(environment);
91
+ const home = defaultCueLineHome(environment);
92
+ const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
93
+ const nodeOk = major >= 22;
94
+ const availability = executableAvailability(environment);
95
+ const findings = [];
96
+ let callerLanes = 0;
97
+ let processAvailableLanes = 0;
98
+ let config;
99
+ try {
100
+ config = await loadRoutingConfig(configPath);
101
+ }
102
+ catch {
103
+ findings.push({
104
+ code: "ROUTING_CONFIG_INVALID",
105
+ surface: "config",
106
+ message: "Routing configuration could not be loaded.",
107
+ });
108
+ }
109
+ if (config !== undefined) {
110
+ for (const [lane, laneConfig] of Object.entries(config.lanes)) {
111
+ if (!laneConfig.enabled)
112
+ continue;
113
+ callerLanes += 1;
114
+ try {
115
+ resolveRoute(lane, config, availability);
116
+ processAvailableLanes += 1;
117
+ }
118
+ catch {
119
+ // `cueline routing` reports per-lane details.
120
+ }
121
+ }
122
+ }
123
+ if (!nodeOk) {
124
+ findings.push({
125
+ code: "NODE_VERSION_UNSUPPORTED",
126
+ surface: "node",
127
+ message: "Node.js 22 or newer is required.",
128
+ });
129
+ }
130
+ if (config !== undefined && callerLanes === 0) {
131
+ findings.push({
132
+ code: "CALLER_LANES_UNAVAILABLE",
133
+ surface: "caller",
134
+ message: "No enabled caller lane is configured.",
135
+ });
136
+ }
137
+ const callerReady = nodeOk && config !== undefined && callerLanes > 0;
138
+ return {
139
+ version: CUELINE_VERSION,
140
+ status: callerReady ? "ok" : "degraded",
141
+ node: {
142
+ version: process.versions.node,
143
+ ok: nodeOk,
144
+ requirement: ">=22",
145
+ },
146
+ config: config === undefined
147
+ ? { path: configPath, valid: false, errorCode: "ROUTING_CONFIG_INVALID" }
148
+ : { path: configPath, valid: true },
149
+ home,
150
+ caller: {
151
+ ready: callerReady,
152
+ enabledLanes: callerLanes,
153
+ },
154
+ process: {
155
+ availableLanes: processAvailableLanes,
156
+ },
157
+ findings,
158
+ };
159
+ }
160
+ async function doctorCommand(json, environment, io) {
161
+ const report = await collectDoctorReport(environment);
162
+ if (json) {
163
+ io.stdout(JSON.stringify(report, null, 2));
164
+ }
165
+ else {
166
+ io.stdout(`CueLine ${report.version}`);
167
+ io.stdout(`status\t${report.status}`);
168
+ io.stdout(`node\t${report.node.version}\t${report.node.ok ? "ok" : `requires ${report.node.requirement}`}`);
169
+ io.stdout(`config\t${report.config.path}\t${report.config.valid ? "valid" : "invalid"}`);
170
+ io.stdout(`home\t${report.home}`);
171
+ io.stdout(`caller_ready\t${report.caller.ready ? "yes" : "no"}`);
172
+ io.stdout(`caller_lanes\t${report.caller.enabledLanes}`);
173
+ io.stdout(`process_available_lanes\t${report.process.availableLanes}`);
174
+ for (const item of report.findings) {
175
+ io.stdout(`finding\t${item.code}\t${item.surface}\t${item.message}`);
176
+ }
177
+ }
178
+ return report.status === "ok" ? 0 : 1;
179
+ }
180
+ export async function handleHealthCommand(args, environment, io) {
181
+ if (args[0] === "routing" &&
182
+ (args.length === 1 || (args.length === 2 && args[1] === "--json"))) {
183
+ return routingCommand(args[1] === "--json", environment, io);
184
+ }
185
+ if (args[0] === "doctor" &&
186
+ (args.length === 1 || (args.length === 2 && args[1] === "--json"))) {
187
+ return doctorCommand(args[1] === "--json", environment, io);
188
+ }
189
+ return undefined;
190
+ }
191
+ //# sourceMappingURL=health-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-commands.js","sourceRoot":"","sources":["../../../src/cli/health-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export interface CliIo {
2
+ stdout: (line: string) => void;
3
+ stderr: (line: string) => void;
4
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../../../src/cli/io.ts"],"names":[],"mappings":""}
@@ -1,7 +1,3 @@
1
1
  #!/usr/bin/env node
2
- interface CliIo {
3
- stdout: (line: string) => void;
4
- stderr: (line: string) => void;
5
- }
2
+ import type { CliIo } from "./io.js";
6
3
  export declare function main(args?: readonly string[], environment?: NodeJS.ProcessEnv, io?: CliIo): Promise<number>;
7
- export {};
@@ -3,22 +3,24 @@ import { readdir, readFile } from "node:fs/promises";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
- import { cancelCueLineJob, cancelCueLineRun, confirmManualControllerSubmission, loadCueLineRunStatus, reconcileCueLineRuntime, routingConfigPath, takeoverCueLineRuntime, } from "../api.js";
6
+ import { cancelCueLineJob, cancelCueLineRun, confirmManualControllerSubmission, loadCueLineRunStatus, lintControllerCommandText, reconcileCueLineRuntime, routingConfigPath, takeoverCueLineRuntime, } from "../api.js";
7
7
  import { CueLineError } from "../core/errors.js";
8
- import { executableAvailability } from "../router/availability.js";
8
+ import { JobStatusStore } from "../jobs/status.js";
9
9
  import { loadRoutingConfig } from "../router/config-loader.js";
10
- import { resolveRoute } from "../router/resolver.js";
11
10
  import { defaultCueLineHome } from "../state/paths.js";
12
11
  import { readRuntimeLease } from "../state/runtime-lease.js";
13
12
  import { readAuthoritativeRunEvents } from "../state/store.js";
14
13
  import { CUELINE_VERSION } from "../version.js";
14
+ import { handleHealthCommand } from "./health-commands.js";
15
+ import { handleObservationCommand } from "./observation-commands.js";
16
+ import { safeCueLineRunStatus } from "./run-status-view.js";
15
17
  import { installSkill, uninstallSkill } from "./skill-links.js";
16
18
  const processIo = {
17
19
  stdout: (line) => process.stdout.write(`${line}\n`),
18
20
  stderr: (line) => process.stderr.write(`${line}\n`),
19
21
  };
20
22
  function usage() {
21
- return "usage: cueline <install|uninstall|doctor|routing|jobs|run status|run reconcile|run takeover|run reconcile-runtime|run cancel|run stop|job cancel|api path|config path|help|version>";
23
+ return "usage: cueline <install|uninstall|doctor|routing|jobs|runs|protocol lint|run status|run doctor|run watch|run handoff|run timeline|run verify|run reconcile|run takeover|run reconcile-runtime|run cancel|run stop|job cancel|api path|config path|help|version>";
22
24
  }
23
25
  function help() {
24
26
  return [
@@ -32,7 +34,14 @@ function help() {
32
34
  " doctor report Node, caller readiness, state home, and process lanes",
33
35
  " routing list every lane and the candidate that would be selected",
34
36
  " jobs list persisted local jobs with run, key, lane, mode, and PID",
35
- " run status summarize one persisted run for safe cross-session handoff",
37
+ " protocol lint validate a Pro control envelope offline and explain corrections",
38
+ " runs list safe summaries of every persisted run",
39
+ " run status metadata-only summary for safe cross-session handoff",
40
+ " run doctor explain why a run is waiting or blocked and name the safe next action",
41
+ " run watch wait briefly for a newer durable event without owning the run",
42
+ " run handoff emit a restart packet with exact paths and safe next action",
43
+ " run timeline show a sanitized, cursor-paginated audit timeline",
44
+ " run verify verify durable run evidence without returning its content",
36
45
  " run reconcile confirm one manually sent controller turn; never resends it",
37
46
  " run takeover explicitly retire one exact stale runtime owner",
38
47
  " run reconcile-runtime settle dead ownerless workers from persisted evidence",
@@ -47,10 +56,17 @@ function help() {
47
56
  "command syntax:",
48
57
  " cueline install",
49
58
  " cueline uninstall",
50
- " cueline doctor",
51
- " cueline routing",
59
+ " cueline doctor [--json]",
60
+ " cueline routing [--json]",
52
61
  " cueline jobs [--json]",
62
+ " cueline protocol lint <file> --run-id <id> --round <n> --request-id <id> [--json]",
63
+ " cueline runs [--json]",
53
64
  " cueline run status <run-id> [--json]",
65
+ " cueline run doctor <run-id> [--json]",
66
+ " cueline run watch <run-id> --after <sequence> [--timeout-ms <0..30000>] [--json]",
67
+ " cueline run handoff <run-id> [--include-content] [--max-content-chars <16..10000>] [--json]",
68
+ " cueline run timeline <run-id> [--after <sequence>] [--limit <1..1000>] [--json]",
69
+ " cueline run verify <run-id> [--json]",
54
70
  " cueline run reconcile <run-id> --request-id <request-id> --manual-send-confirmed [--conversation-url <url>] [--json]",
55
71
  " cueline run takeover <run-id> [--json]",
56
72
  " cueline run reconcile-runtime <run-id> [--json]",
@@ -76,7 +92,7 @@ function help() {
76
92
  " 2 the arguments were not understood",
77
93
  "",
78
94
  "state effects:",
79
- " Read-only: doctor, routing, jobs, run status, api path, config path, help, version.",
95
+ " Read-only: doctor, routing, jobs, runs, protocol lint, run status, run doctor, run watch, run handoff, run timeline, run verify, api path, config path, help, version.",
80
96
  " Local setup: install and uninstall change only the package-owned skill link.",
81
97
  " Durable state writes: run reconcile, takeover, reconcile-runtime, cancel/stop,",
82
98
  " and job cancel append evidence or change local run/job state.",
@@ -90,25 +106,26 @@ function errorMessage(error) {
90
106
  return `${error.code}: ${error.message}`;
91
107
  return error instanceof Error ? error.message : String(error);
92
108
  }
93
- async function routingCommand(environment, io) {
94
- const config = await loadRoutingConfig(routingConfigPath(environment));
95
- const availability = executableAvailability(environment);
96
- let available = 0;
97
- for (const [lane, laneConfig] of Object.entries(config.lanes)) {
98
- if (!laneConfig.enabled) {
99
- io.stdout(`${lane}\t-\tdisabled`);
100
- continue;
101
- }
102
- try {
103
- const route = resolveRoute(lane, config, availability);
104
- available += 1;
105
- io.stdout(`${lane}\t${route.candidate.id}\tavailable`);
106
- }
107
- catch (error) {
108
- io.stdout(`${lane}\t-\tunavailable (${errorMessage(error)})`);
109
- }
110
- }
111
- return available > 0 ? 0 : 1;
109
+ function jobStatusMetadata(status) {
110
+ return {
111
+ jobId: status.jobId,
112
+ ...(status.runId === undefined ? {} : { runId: status.runId }),
113
+ ...(status.jobKey === undefined ? {} : { jobKey: status.jobKey }),
114
+ ...(status.lane === undefined ? {} : { lane: status.lane }),
115
+ ...(status.mode === undefined ? {} : { mode: status.mode }),
116
+ ...(status.runnerId === undefined ? {} : { runnerId: status.runnerId }),
117
+ ...(status.model === undefined ? {} : { model: status.model }),
118
+ ...(status.provider === undefined ? {} : { provider: status.provider }),
119
+ ...(status.pid === undefined ? {} : { pid: status.pid }),
120
+ ...(status.phase === undefined ? {} : { phase: status.phase }),
121
+ ...(status.lastProgressAt === undefined
122
+ ? {}
123
+ : { lastProgressAt: status.lastProgressAt }),
124
+ execution: status.execution,
125
+ status: status.status,
126
+ startedAt: status.startedAt,
127
+ ...(status.finishedAt === undefined ? {} : { finishedAt: status.finishedAt }),
128
+ };
112
129
  }
113
130
  async function readRunJobMetadata(home) {
114
131
  const metadata = new Map();
@@ -151,7 +168,6 @@ async function readRunJobMetadata(home) {
151
168
  jobKey: record.jobKey,
152
169
  lane: spec.lane,
153
170
  mode: spec.mode,
154
- task: spec.task,
155
171
  execution: "foreground",
156
172
  status: "pending",
157
173
  startedAt: event.timestamp,
@@ -203,13 +219,23 @@ async function readJobs(home) {
203
219
  else
204
220
  throw error;
205
221
  }
222
+ const statusStore = new JobStatusStore(home);
206
223
  const statuses = [];
207
- for (const name of names.filter((candidate) => candidate.endsWith(".json")).sort()) {
224
+ const jobIds = new Set();
225
+ for (const name of names) {
226
+ if (name.endsWith(".json"))
227
+ jobIds.add(name.slice(0, -".json".length));
228
+ if (name.endsWith(".terminal"))
229
+ jobIds.add(name.slice(0, -".terminal".length));
230
+ }
231
+ for (const jobId of [...jobIds].sort()) {
208
232
  try {
209
- statuses.push(JSON.parse(await readFile(path.join(directory, name), "utf8")));
233
+ const status = await statusStore.read(jobId);
234
+ if (status !== undefined)
235
+ statuses.push(status);
210
236
  }
211
237
  catch (error) {
212
- throw new CueLineError("JOB_STATUS_INVALID", `unable to parse job status: ${name}`, {
238
+ throw new CueLineError("JOB_STATUS_INVALID", `unable to parse job status: ${jobId}`, {
213
239
  cause: error,
214
240
  });
215
241
  }
@@ -225,16 +251,22 @@ async function readJobs(home) {
225
251
  for (const persisted of statuses) {
226
252
  const authoritative = runMetadata.get(persisted.jobId);
227
253
  const conflict = authoritative !== undefined && authoritative.status !== persisted.status;
254
+ const persistedMetadata = jobStatusMetadata(persisted);
255
+ const { finishedAt: _persistedFinishedAt, ...activePersistedMetadata } = persistedMetadata;
228
256
  const status = conflict
229
257
  ? {
230
- ...Object.fromEntries(Object.entries(persisted).filter(([key]) => key !== "result" && key !== "error" && key !== "finishedAt")),
258
+ ...activePersistedMetadata,
231
259
  ...authoritative,
232
260
  execution: persisted.execution,
233
261
  startedAt: persisted.startedAt,
234
262
  }
235
263
  : {
264
+ ...(authoritative?.status === "pending" || authoritative?.status === "running"
265
+ ? activePersistedMetadata
266
+ : persistedMetadata),
236
267
  ...authoritative,
237
- ...persisted,
268
+ execution: persisted.execution,
269
+ startedAt: persisted.startedAt,
238
270
  status: authoritative?.status ?? persisted.status,
239
271
  };
240
272
  let observedStatus = conflict ? "conflict" : status.status;
@@ -252,7 +284,7 @@ async function readJobs(home) {
252
284
  }
253
285
  }
254
286
  listed.push({
255
- ...status,
287
+ ...jobStatusMetadata(status),
256
288
  ...(conflict ? { persistedStatus: persisted.status } : {}),
257
289
  observedStatus,
258
290
  });
@@ -270,14 +302,50 @@ async function jobsCommand(json, environment, io) {
270
302
  return 0;
271
303
  }
272
304
  for (const job of jobs) {
273
- io.stdout(`${job.jobId}\t${job.runId ?? "-"}\t${job.jobKey ?? "-"}\t${job.lane ?? "-"}\t${job.mode ?? "-"}\t${job.pid ?? "-"}\t${job.execution}\t${job.observedStatus}\t${job.startedAt}\trunner=${job.runnerId ?? "-"}\tmodel=${job.model ?? "-"}\tprovider=${job.provider ?? "-"}\tphase=${job.phase ?? "-"}\tprogress=${job.lastProgressAt ?? "-"}\ttask=${JSON.stringify(job.task ?? "")}`);
305
+ io.stdout(`${job.jobId}\t${job.runId ?? "-"}\t${job.jobKey ?? "-"}\t${job.lane ?? "-"}\t${job.mode ?? "-"}\t${job.pid ?? "-"}\t${job.execution}\t${job.observedStatus}\t${job.startedAt}\trunner=${job.runnerId ?? "-"}\tmodel=${job.model ?? "-"}\tprovider=${job.provider ?? "-"}\tphase=${job.phase ?? "-"}\tprogress=${job.lastProgressAt ?? "-"}`);
274
306
  }
275
307
  return 0;
276
308
  }
309
+ async function protocolLintCommand(file, expected, json, environment, io) {
310
+ const config = await loadRoutingConfig(routingConfigPath(environment));
311
+ const runnerLanes = Object.create(null);
312
+ for (const [lane, laneConfig] of Object.entries(config.lanes)) {
313
+ if (!laneConfig.enabled)
314
+ continue;
315
+ for (const candidate of laneConfig.candidates) {
316
+ if (candidate.enabled === false || runnerLanes[candidate.id] !== undefined)
317
+ continue;
318
+ runnerLanes[candidate.id] = lane;
319
+ }
320
+ }
321
+ const text = await readFile(file, "utf8");
322
+ const report = lintControllerCommandText(text, {
323
+ expected,
324
+ routing: {
325
+ lanes: Object.entries(config.lanes)
326
+ .filter(([, lane]) => lane.enabled)
327
+ .map(([lane]) => lane),
328
+ runnerLanes,
329
+ },
330
+ });
331
+ if (json) {
332
+ io.stdout(JSON.stringify({ version: CUELINE_VERSION, ...report }, null, 2));
333
+ }
334
+ else {
335
+ io.stdout(`valid\t${report.valid ? "yes" : "no"}`);
336
+ io.stdout(`format\t${report.format}`);
337
+ for (const issue of report.issues) {
338
+ io.stdout(`issue\t${issue.severity}\t${issue.code}\t${issue.path ?? "-"}\t${issue.message}\tsuggestion=${issue.suggestion ?? "-"}`);
339
+ }
340
+ if (report.command !== undefined)
341
+ io.stdout(`action\t${report.command.action}`);
342
+ }
343
+ return report.valid ? 0 : 1;
344
+ }
277
345
  async function runStatusCommand(runId, json, environment, io) {
278
346
  const status = await loadCueLineRunStatus(runId, { environment });
279
347
  if (json) {
280
- io.stdout(JSON.stringify({ version: CUELINE_VERSION, ...status }, null, 2));
348
+ io.stdout(JSON.stringify({ version: CUELINE_VERSION, ...safeCueLineRunStatus(status) }, null, 2));
281
349
  return 0;
282
350
  }
283
351
  const controller = status.controller.responseAccepted
@@ -297,47 +365,18 @@ async function runStatusCommand(runId, json, environment, io) {
297
365
  : `\theartbeat=${status.runtime.heartbeatAt}\tage_ms=${status.runtime.ageMs ?? "-"}`}`);
298
366
  io.stdout(`sequence\t${status.lastEventSequence}`);
299
367
  io.stdout(`controller\t${controller}\tpending=${status.controller.pendingTurns}\taccepted_commands=${status.controller.acceptedCommands}\tlast_action=${status.controller.lastAcceptedAction ?? "-"}\tlast_jobs=${status.controller.lastAcceptedJobKeys.length}`);
368
+ if (status.controller.archive.enabled) {
369
+ io.stdout(`controller_archive\tenabled=yes\tstatus=${status.controller.archive.status}\tcode=${status.controller.archive.code ?? "-"}\tproof=${status.controller.archive.proof ?? "-"}`);
370
+ }
300
371
  io.stdout(`jobs\ttotal=${status.jobs.total}\tpending=${counts.pending}\trunning=${counts.running}\tsucceeded=${counts.succeeded}\tfailed=${counts.failed}\ttimed_out=${counts.timed_out}\torphaned=${counts.orphaned}\tcancelled=${counts.cancelled}\tambiguous=${counts.ambiguous}`);
301
372
  for (const job of status.jobs.items) {
302
- io.stdout(`job\t${job.jobId}\t${job.jobKey}\t${job.status}\t${job.mode}\t${job.lane}\trequired=${job.required}\tpersisted=${job.persistedStatus}\trunner=${job.execution?.runnerId ?? "-"}\tpid=${job.execution?.pid ?? "-"}\tmodel=${job.execution?.model ?? "-"}\tprovider=${job.execution?.provider ?? "-"}\tphase=${job.execution?.phase ?? "-"}\tprogress=${job.execution?.lastProgressAt ?? "-"}\ttask=${JSON.stringify(job.task)}`);
373
+ io.stdout(`job\t${job.jobId}\t${job.jobKey}\t${job.status}\t${job.mode}\t${job.lane}\trequired=${job.required}\tpersisted=${job.persistedStatus}\trunner=${job.execution?.runnerId ?? "-"}\tpid=${job.execution?.pid ?? "-"}\tmodel=${job.execution?.model ?? "-"}\tprovider=${job.execution?.provider ?? "-"}\tphase=${job.execution?.phase ?? "-"}\tprogress=${job.execution?.lastProgressAt ?? "-"}`);
303
374
  }
304
375
  io.stdout(`cancellation\trun=${status.cancellation.runRequested ? "requested" : "none"}\tjobs=${status.cancellation.jobRequests.length}`);
305
376
  io.stdout(`continue\t${status.continueAllowed ? "allowed" : "forbidden"}`);
306
377
  io.stdout(`next\t${status.safeNextAction}`);
307
378
  return 0;
308
379
  }
309
- async function doctorCommand(environment, io) {
310
- const configPath = routingConfigPath(environment);
311
- const home = defaultCueLineHome(environment);
312
- const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
313
- const nodeOk = major >= 22;
314
- const config = await loadRoutingConfig(configPath);
315
- const availability = executableAvailability(environment);
316
- let callerLanes = 0;
317
- let processAvailableLanes = 0;
318
- for (const [lane, laneConfig] of Object.entries(config.lanes)) {
319
- if (!laneConfig.enabled)
320
- continue;
321
- callerLanes += 1;
322
- try {
323
- resolveRoute(lane, config, availability);
324
- processAvailableLanes += 1;
325
- }
326
- catch {
327
- // Doctor reports the aggregate below; `cueline routing` shows lane details.
328
- }
329
- }
330
- const callerReady = nodeOk && callerLanes > 0;
331
- io.stdout(`CueLine ${CUELINE_VERSION}`);
332
- io.stdout(`status\t${callerReady ? "ok" : "degraded"}`);
333
- io.stdout(`node\t${process.versions.node}\t${nodeOk ? "ok" : "requires >=22"}`);
334
- io.stdout(`config\t${configPath}\tvalid`);
335
- io.stdout(`home\t${home}`);
336
- io.stdout(`caller_ready\t${callerReady ? "yes" : "no"}`);
337
- io.stdout(`caller_lanes\t${callerLanes}`);
338
- io.stdout(`process_available_lanes\t${processAvailableLanes}`);
339
- return callerReady ? 0 : 1;
340
- }
341
380
  export async function main(args = process.argv.slice(2), environment = process.env, io = processIo) {
342
381
  try {
343
382
  if (args.length === 0) {
@@ -373,13 +412,59 @@ export async function main(args = process.argv.slice(2), environment = process.e
373
412
  io.stdout(await uninstallSkill(environment));
374
413
  return 0;
375
414
  }
376
- if (args[0] === "routing" && args.length === 1) {
377
- return routingCommand(environment, io);
378
- }
415
+ const healthResult = await handleHealthCommand(args, environment, io);
416
+ if (healthResult !== undefined)
417
+ return healthResult;
379
418
  if (args[0] === "jobs" &&
380
419
  (args.length === 1 || (args.length === 2 && args[1] === "--json"))) {
381
- return jobsCommand(args[1] === "--json", environment, io);
420
+ return await jobsCommand(args[1] === "--json", environment, io);
382
421
  }
422
+ if (args[0] === "protocol" &&
423
+ args[1] === "lint" &&
424
+ typeof args[2] === "string") {
425
+ let runId;
426
+ let round;
427
+ let requestId;
428
+ let json = false;
429
+ let valid = true;
430
+ for (let index = 3; index < args.length; index += 1) {
431
+ const argument = args[index];
432
+ if (argument === "--run-id" && runId === undefined && typeof args[index + 1] === "string") {
433
+ runId = args[index + 1];
434
+ index += 1;
435
+ }
436
+ else if (argument === "--round" &&
437
+ round === undefined &&
438
+ typeof args[index + 1] === "string") {
439
+ round = Number(args[index + 1]);
440
+ index += 1;
441
+ }
442
+ else if (argument === "--request-id" &&
443
+ requestId === undefined &&
444
+ typeof args[index + 1] === "string") {
445
+ requestId = args[index + 1];
446
+ index += 1;
447
+ }
448
+ else if (argument === "--json" && !json) {
449
+ json = true;
450
+ }
451
+ else {
452
+ valid = false;
453
+ }
454
+ }
455
+ if (!valid ||
456
+ !runId ||
457
+ !requestId ||
458
+ round === undefined ||
459
+ !Number.isSafeInteger(round) ||
460
+ round < 1) {
461
+ throw new CueLineError("CLI_ARGUMENTS_INVALID", "usage: cueline protocol lint <file> --run-id <id> --round <n> --request-id <id> [--json]");
462
+ }
463
+ return await protocolLintCommand(args[2], { runId, round, requestId }, json, environment, io);
464
+ }
465
+ const observationResult = await handleObservationCommand(args, environment, io);
466
+ if (observationResult !== undefined)
467
+ return observationResult;
383
468
  if (args[0] === "run" &&
384
469
  args[1] === "takeover" &&
385
470
  typeof args[2] === "string" &&
@@ -453,7 +538,7 @@ export async function main(args = process.argv.slice(2), environment = process.e
453
538
  args[1] === "status" &&
454
539
  typeof args[2] === "string" &&
455
540
  (args.length === 3 || (args.length === 4 && args[3] === "--json"))) {
456
- return runStatusCommand(args[2], args[3] === "--json", environment, io);
541
+ return await runStatusCommand(args[2], args[3] === "--json", environment, io);
457
542
  }
458
543
  if (args[0] === "run" &&
459
544
  (args[1] === "cancel" || args[1] === "stop") &&
@@ -484,9 +569,6 @@ export async function main(args = process.argv.slice(2), environment = process.e
484
569
  io.stdout(`${result.runId}\t${result.jobId}\t${result.outcome}`);
485
570
  return 0;
486
571
  }
487
- if (args[0] === "doctor" && args.length === 1) {
488
- return doctorCommand(environment, io);
489
- }
490
572
  io.stderr(`cueline: unrecognized command: ${args.join(" ")}`);
491
573
  io.stderr(usage());
492
574
  io.stderr("try `cueline help`");