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.
Files changed (116) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +3 -3
  3. package/CHANGELOG.md +38 -0
  4. package/README.ja.md +65 -21
  5. package/README.ko.md +65 -21
  6. package/README.md +70 -23
  7. package/README.zh-CN.md +65 -21
  8. package/README.zh-TW.md +68 -22
  9. package/dist/src/api-contracts.d.ts +77 -0
  10. package/dist/src/api-contracts.js +2 -0
  11. package/dist/src/api-contracts.js.map +1 -0
  12. package/dist/src/api-controller-handoff.d.ts +6 -0
  13. package/dist/src/api-controller-handoff.js +253 -0
  14. package/dist/src/api-controller-handoff.js.map +1 -0
  15. package/dist/src/api-runtime-lifecycle.d.ts +22 -0
  16. package/dist/src/api-runtime-lifecycle.js +568 -0
  17. package/dist/src/api-runtime-lifecycle.js.map +1 -0
  18. package/dist/src/api.d.ts +9 -27
  19. package/dist/src/api.js +138 -42
  20. package/dist/src/api.js.map +1 -1
  21. package/dist/src/browser/browser-adapter.d.ts +11 -1
  22. package/dist/src/browser/codex-iab/bootstrap.d.ts +16 -0
  23. package/dist/src/browser/codex-iab/bootstrap.js +49 -0
  24. package/dist/src/browser/codex-iab/bootstrap.js.map +1 -1
  25. package/dist/src/browser/codex-iab/chatgpt-client.js +346 -120
  26. package/dist/src/browser/codex-iab/chatgpt-client.js.map +1 -1
  27. package/dist/src/browser/codex-iab/recovery-evidence.d.ts +6 -0
  28. package/dist/src/browser/codex-iab/recovery-evidence.js +37 -0
  29. package/dist/src/browser/codex-iab/recovery-evidence.js.map +1 -0
  30. package/dist/src/browser/codex-iab/submission-url.d.ts +2 -0
  31. package/dist/src/browser/codex-iab/submission-url.js +53 -0
  32. package/dist/src/browser/codex-iab/submission-url.js.map +1 -0
  33. package/dist/src/cli/main.js +305 -20
  34. package/dist/src/cli/main.js.map +1 -1
  35. package/dist/src/core/controller-abort.d.ts +1 -0
  36. package/dist/src/core/controller-abort.js +11 -0
  37. package/dist/src/core/controller-abort.js.map +1 -0
  38. package/dist/src/core/controller-command-execution.d.ts +9 -0
  39. package/dist/src/core/controller-command-execution.js +299 -0
  40. package/dist/src/core/controller-command-execution.js.map +1 -0
  41. package/dist/src/core/controller-loop.d.ts +3 -37
  42. package/dist/src/core/controller-loop.js +386 -291
  43. package/dist/src/core/controller-loop.js.map +1 -1
  44. package/dist/src/core/controller-turn.d.ts +16 -0
  45. package/dist/src/core/controller-turn.js +309 -0
  46. package/dist/src/core/controller-turn.js.map +1 -0
  47. package/dist/src/core/controller-types.d.ts +58 -0
  48. package/dist/src/core/controller-types.js +2 -0
  49. package/dist/src/core/controller-types.js.map +1 -0
  50. package/dist/src/core/persisted-run.d.ts +4 -0
  51. package/dist/src/core/persisted-run.js +19 -0
  52. package/dist/src/core/persisted-run.js.map +1 -0
  53. package/dist/src/core/process-liveness.d.ts +2 -0
  54. package/dist/src/core/process-liveness.js +44 -0
  55. package/dist/src/core/process-liveness.js.map +1 -0
  56. package/dist/src/core/run-status.d.ts +53 -0
  57. package/dist/src/core/run-status.js +224 -0
  58. package/dist/src/core/run-status.js.map +1 -0
  59. package/dist/src/core/state-machine.d.ts +17 -4
  60. package/dist/src/core/state-machine.js +170 -16
  61. package/dist/src/core/state-machine.js.map +1 -1
  62. package/dist/src/jobs/status.d.ts +7 -1
  63. package/dist/src/jobs/status.js.map +1 -1
  64. package/dist/src/jobs/supervisor.d.ts +2 -0
  65. package/dist/src/jobs/supervisor.js +47 -13
  66. package/dist/src/jobs/supervisor.js.map +1 -1
  67. package/dist/src/protocol/types.d.ts +1 -1
  68. package/dist/src/protocol/validate-command.js +18 -0
  69. package/dist/src/protocol/validate-command.js.map +1 -1
  70. package/dist/src/router/resolver.d.ts +2 -1
  71. package/dist/src/router/resolver.js +31 -13
  72. package/dist/src/router/resolver.js.map +1 -1
  73. package/dist/src/runners/process-runner.d.ts +2 -2
  74. package/dist/src/runners/process-runner.js +113 -9
  75. package/dist/src/runners/process-runner.js.map +1 -1
  76. package/dist/src/runners/runner-adapter.d.ts +9 -2
  77. package/dist/src/runners/runner-adapter.js.map +1 -1
  78. package/dist/src/state/atomic-write.js +17 -2
  79. package/dist/src/state/atomic-write.js.map +1 -1
  80. package/dist/src/state/cancellation.d.ts +42 -0
  81. package/dist/src/state/cancellation.js +168 -0
  82. package/dist/src/state/cancellation.js.map +1 -0
  83. package/dist/src/state/event-log.d.ts +17 -1
  84. package/dist/src/state/event-log.js +328 -18
  85. package/dist/src/state/event-log.js.map +1 -1
  86. package/dist/src/state/paths.d.ts +4 -0
  87. package/dist/src/state/paths.js +4 -0
  88. package/dist/src/state/paths.js.map +1 -1
  89. package/dist/src/state/runtime-lease.d.ts +53 -0
  90. package/dist/src/state/runtime-lease.js +727 -0
  91. package/dist/src/state/runtime-lease.js.map +1 -0
  92. package/dist/src/state/runtime-retirement.d.ts +16 -0
  93. package/dist/src/state/runtime-retirement.js +95 -0
  94. package/dist/src/state/runtime-retirement.js.map +1 -0
  95. package/dist/src/state/runtime-takeover-intent.d.ts +1 -0
  96. package/dist/src/state/runtime-takeover-intent.js +18 -0
  97. package/dist/src/state/runtime-takeover-intent.js.map +1 -0
  98. package/dist/src/state/store.d.ts +12 -1
  99. package/dist/src/state/store.js +246 -40
  100. package/dist/src/state/store.js.map +1 -1
  101. package/dist/src/version.d.ts +1 -1
  102. package/dist/src/version.js +1 -1
  103. package/docs/architecture.md +16 -11
  104. package/docs/assets/cueline-loop-en.svg +11 -10
  105. package/docs/assets/cueline-loop-ja.svg +11 -10
  106. package/docs/assets/cueline-loop-ko.svg +11 -10
  107. package/docs/assets/cueline-loop-zh-CN.svg +11 -10
  108. package/docs/assets/cueline-loop-zh-TW.svg +11 -10
  109. package/docs/compatibility.md +16 -8
  110. package/docs/controller-protocol.md +8 -4
  111. package/docs/runner-contract.md +26 -6
  112. package/docs/state-and-recovery.md +69 -16
  113. package/package.json +4 -3
  114. package/schemas/controller-observation.schema.json +1 -1
  115. package/scripts/validate-plugin.mjs +66 -0
  116. package/skills/cueline/SKILL.md +89 -13
@@ -0,0 +1,53 @@
1
+ import type { CueLineRunState, StoredJobStatus } from "./state-machine.js";
2
+ import type { RuntimeLeaseObservation } from "../state/runtime-lease.js";
3
+ import type { CancellationObservation } from "../state/cancellation.js";
4
+ import type { RunEvent } from "../state/event-log.js";
5
+ declare const OBSERVED_JOB_STATUSES: readonly ["pending", "running", "succeeded", "failed", "timed_out", "cancelled", "ambiguous", "orphaned"];
6
+ export type CueLineObservedJobStatus = (typeof OBSERVED_JOB_STATUSES)[number];
7
+ export type CueLineRunPhase = "starting" | "controller_response_pending" | "jobs_running" | "controller_decision_pending" | "caller_jobs_pending" | "runtime_active" | "runtime_stale" | "runtime_ownership_unknown" | "cancellation_pending" | "reconciliation_required" | "job_recovery_required" | "round_limit_reached" | "resume_ready" | "complete" | "blocked" | "cancelled";
8
+ export type CueLineSafeNextAction = "observe" | "reconcile" | "inspect_jobs_then_continue" | "inspect_runtime" | "continue" | "execute_caller_jobs" | "return_result";
9
+ export interface CueLineRunStatusSummary {
10
+ runId: string;
11
+ status: CueLineRunState["status"];
12
+ executor: CueLineRunState["executor"];
13
+ phase: CueLineRunPhase;
14
+ round: number;
15
+ maxRounds: number;
16
+ lastEventSequence: number;
17
+ runtime: RuntimeLeaseObservation;
18
+ cancellation: CancellationObservation;
19
+ controller: {
20
+ pendingTurns: number;
21
+ acceptedCommands: number;
22
+ responseAccepted: boolean;
23
+ lastAcceptedAction: "dispatch" | "wait" | "inspect" | "complete" | "blocked" | null;
24
+ lastAcceptedRequestId: string | null;
25
+ lastAcceptedJobKeys: string[];
26
+ };
27
+ jobs: {
28
+ total: number;
29
+ counts: Record<CueLineObservedJobStatus, number>;
30
+ items: Array<{
31
+ jobId: string;
32
+ jobKey: string;
33
+ required: boolean;
34
+ lane: string;
35
+ mode: string;
36
+ task: string;
37
+ status: CueLineObservedJobStatus;
38
+ persistedStatus: StoredJobStatus;
39
+ }>;
40
+ };
41
+ continueAllowed: boolean;
42
+ safeNextAction: CueLineSafeNextAction;
43
+ }
44
+ export interface AcceptedControllerCommandEvidence {
45
+ action: CueLineRunStatusSummary["controller"]["lastAcceptedAction"];
46
+ requestId: string | null;
47
+ jobKeys: string[];
48
+ }
49
+ export declare function acceptedControllerCommandEvidence(events: readonly RunEvent[]): AcceptedControllerCommandEvidence;
50
+ export declare function cueLineRunPhase(state: CueLineRunState, runtime: RuntimeLeaseObservation, cancellation?: CancellationObservation): CueLineRunPhase;
51
+ export declare function assertRunCanContinue(state: CueLineRunState, runtime: RuntimeLeaseObservation, cancellation?: CancellationObservation): void;
52
+ export declare function summarizeCueLineRunState(state: CueLineRunState, lastEventSequence: number, runtime: RuntimeLeaseObservation, cancellation?: CancellationObservation, acceptedCommand?: AcceptedControllerCommandEvidence): CueLineRunStatusSummary;
53
+ export {};
@@ -0,0 +1,224 @@
1
+ import { CueLineError } from "./errors.js";
2
+ const JOB_STATUSES = [
3
+ "pending",
4
+ "running",
5
+ "succeeded",
6
+ "failed",
7
+ "timed_out",
8
+ "cancelled",
9
+ "ambiguous",
10
+ ];
11
+ const OBSERVED_JOB_STATUSES = [...JOB_STATUSES, "orphaned"];
12
+ export function acceptedControllerCommandEvidence(events) {
13
+ for (let index = events.length - 1; index >= 0; index -= 1) {
14
+ const event = events[index];
15
+ if (event?.type !== "controller_command_accepted")
16
+ continue;
17
+ const payload = event.payload;
18
+ const command = payload.command;
19
+ if (typeof command !== "object" || command === null || Array.isArray(command))
20
+ continue;
21
+ const record = command;
22
+ const action = record.action === "dispatch" ||
23
+ record.action === "wait" ||
24
+ record.action === "inspect" ||
25
+ record.action === "complete" ||
26
+ record.action === "blocked"
27
+ ? record.action
28
+ : null;
29
+ if (action === null)
30
+ continue;
31
+ const jobs = Array.isArray(record.jobs) ? record.jobs : [];
32
+ return {
33
+ action,
34
+ requestId: typeof record.request_id === "string" ? record.request_id : null,
35
+ jobKeys: jobs.flatMap((job) => {
36
+ if (typeof job !== "object" || job === null || Array.isArray(job))
37
+ return [];
38
+ const jobKey = job.job_key;
39
+ return typeof jobKey === "string" ? [jobKey] : [];
40
+ }),
41
+ };
42
+ }
43
+ return { action: null, requestId: null, jobKeys: [] };
44
+ }
45
+ function activeJobCount(state) {
46
+ return Object.values(state.jobs).filter((job) => job.status === "pending" || job.status === "running").length;
47
+ }
48
+ function isPristineRun(state) {
49
+ return (state.round === 0 &&
50
+ state.pendingControllerTurns.length === 0 &&
51
+ state.commandHashes.length === 0 &&
52
+ Object.keys(state.jobs).length === 0);
53
+ }
54
+ function roundLimitReached(state) {
55
+ return (state.status === "failed" &&
56
+ state.lastFailure?.code === "MAX_ROUNDS_EXCEEDED" &&
57
+ state.pendingControllerTurns.length === 0 &&
58
+ activeJobCount(state) === 0 &&
59
+ state.round >= state.maxRounds);
60
+ }
61
+ function safeNextActionFor(state, runtime, cancellation) {
62
+ if (state.status === "complete" || state.status === "blocked" || state.status === "cancelled") {
63
+ return "return_result";
64
+ }
65
+ if (cancellation.runRequested) {
66
+ return runtime.ownership === "active" ? "observe" : "inspect_runtime";
67
+ }
68
+ if (runtime.ownership === "active")
69
+ return "observe";
70
+ if (state.executor === "caller" && activeJobCount(state) > 0) {
71
+ return "execute_caller_jobs";
72
+ }
73
+ if (runtime.ownership === "stale" || runtime.ownership === "invalid") {
74
+ return "inspect_runtime";
75
+ }
76
+ if (state.pendingControllerTurns.length > 0) {
77
+ const turn = state.pendingControllerTurns[0];
78
+ const normallySubmitted = state.pendingControllerTurns.length === 1 &&
79
+ turn?.submissionState === "submitted" &&
80
+ !turn.manualSendConfirmed;
81
+ return normallySubmitted ? "observe" : "reconcile";
82
+ }
83
+ if (roundLimitReached(state))
84
+ return "return_result";
85
+ if (isPristineRun(state) && state.status === "running")
86
+ return "continue";
87
+ if (state.executor === "caller" && state.status === "running")
88
+ return "continue";
89
+ if (state.status === "running")
90
+ return "inspect_runtime";
91
+ if (activeJobCount(state) > 0)
92
+ return "inspect_jobs_then_continue";
93
+ return "continue";
94
+ }
95
+ export function cueLineRunPhase(state, runtime, cancellation = { runRequested: false, jobRequests: [] }) {
96
+ if (state.status === "complete")
97
+ return "complete";
98
+ if (state.status === "blocked")
99
+ return "blocked";
100
+ if (state.status === "cancelled")
101
+ return "cancelled";
102
+ if (cancellation.runRequested)
103
+ return "cancellation_pending";
104
+ if (state.status === "failed" && runtime.ownership === "active")
105
+ return "runtime_active";
106
+ if (runtime.ownership === "stale")
107
+ return "runtime_stale";
108
+ if (isPristineRun(state) && runtime.ownership !== "active")
109
+ return "starting";
110
+ if (state.status === "running" && state.pendingControllerTurns.length > 0) {
111
+ return "controller_response_pending";
112
+ }
113
+ if (state.executor === "caller" &&
114
+ runtime.ownership !== "active" &&
115
+ activeJobCount(state) > 0) {
116
+ return "caller_jobs_pending";
117
+ }
118
+ if (state.status === "failed") {
119
+ if (state.pendingControllerTurns.length > 0)
120
+ return "reconciliation_required";
121
+ if (activeJobCount(state) > 0)
122
+ return "job_recovery_required";
123
+ if (roundLimitReached(state))
124
+ return "round_limit_reached";
125
+ return "resume_ready";
126
+ }
127
+ if (runtime.ownership !== "active") {
128
+ if (state.executor === "caller") {
129
+ return state.commandHashes.length > 0 ? "controller_decision_pending" : "starting";
130
+ }
131
+ return "runtime_ownership_unknown";
132
+ }
133
+ if (activeJobCount(state) > 0)
134
+ return "jobs_running";
135
+ if (state.commandHashes.length > 0)
136
+ return "controller_decision_pending";
137
+ return "starting";
138
+ }
139
+ export function assertRunCanContinue(state, runtime, cancellation = { runRequested: false, jobRequests: [] }) {
140
+ if (cancellation.runRequested) {
141
+ throw new CueLineError("RUN_CANCELLATION_PENDING", `CueLine run '${state.runId}' has a durable cancellation request; continuation is forbidden.`, { details: { run_id: state.runId, phase: "cancellation_pending" } });
142
+ }
143
+ const inspect = `Inspect it with 'cueline run status ${state.runId} --json'; do not resend it.`;
144
+ if (runtime.ownership === "active") {
145
+ throw new CueLineError("RUN_ALREADY_ACTIVE", `CueLine run '${state.runId}' still has an active controller loop. ${inspect}`, { details: { run_id: state.runId, phase: cueLineRunPhase(state, runtime) } });
146
+ }
147
+ if (runtime.ownership === "stale") {
148
+ throw new CueLineError("RUN_STALE_REQUIRES_TAKEOVER", `CueLine run '${state.runId}' stopped heartbeating; explicit recovery is required. ${inspect}`, { details: { run_id: state.runId, phase: cueLineRunPhase(state, runtime) } });
149
+ }
150
+ if (runtime.ownership === "invalid") {
151
+ throw new CueLineError("RUNTIME_LEASE_INVALID", `CueLine run '${state.runId}' has unreadable runtime ownership evidence. ${inspect}`, { details: { run_id: state.runId, phase: cueLineRunPhase(state, runtime) } });
152
+ }
153
+ if (state.status !== "running")
154
+ return;
155
+ if (isPristineRun(state) &&
156
+ (runtime.ownership === "missing" || runtime.ownership === "released")) {
157
+ return;
158
+ }
159
+ if (state.executor === "caller" &&
160
+ (runtime.ownership === "missing" || runtime.ownership === "released")) {
161
+ return;
162
+ }
163
+ throw new CueLineError("RUN_OWNERSHIP_UNVERIFIED", `CueLine run '${state.runId}' is marked running but has no verifiable active owner. ${inspect}`, { details: { run_id: state.runId, phase: cueLineRunPhase(state, runtime) } });
164
+ }
165
+ export function summarizeCueLineRunState(state, lastEventSequence, runtime, cancellation = { runRequested: false, jobRequests: [] }, acceptedCommand = {
166
+ action: null,
167
+ requestId: null,
168
+ jobKeys: [],
169
+ }) {
170
+ const counts = Object.fromEntries(OBSERVED_JOB_STATUSES.map((status) => [status, 0]));
171
+ const items = Object.values(state.jobs).map((job) => {
172
+ const status = (job.status === "pending" || job.status === "running") &&
173
+ state.executor !== "caller" &&
174
+ runtime.ownership !== "active"
175
+ ? "orphaned"
176
+ : job.status;
177
+ counts[status] += 1;
178
+ return {
179
+ jobId: job.jobId,
180
+ jobKey: job.jobKey,
181
+ required: job.required,
182
+ lane: job.spec.lane,
183
+ mode: job.spec.mode,
184
+ task: job.spec.task,
185
+ status,
186
+ persistedStatus: job.status,
187
+ };
188
+ });
189
+ const phase = cueLineRunPhase(state, runtime, cancellation);
190
+ const continueAllowed = !cancellation.runRequested &&
191
+ !roundLimitReached(state) &&
192
+ (runtime.ownership === "missing" || runtime.ownership === "released") &&
193
+ ((state.status === "failed") ||
194
+ (state.executor === "caller" && activeJobCount(state) === 0));
195
+ const safeNextAction = safeNextActionFor(state, runtime, cancellation);
196
+ return {
197
+ runId: state.runId,
198
+ status: state.status,
199
+ executor: state.executor,
200
+ phase,
201
+ round: state.round,
202
+ maxRounds: state.maxRounds,
203
+ lastEventSequence,
204
+ runtime,
205
+ cancellation,
206
+ controller: {
207
+ pendingTurns: state.pendingControllerTurns.length,
208
+ acceptedCommands: state.commandHashes.length,
209
+ responseAccepted: state.pendingControllerTurns.length === 0 &&
210
+ (acceptedCommand.action !== null || state.commandHashes.length > 0),
211
+ lastAcceptedAction: acceptedCommand.action,
212
+ lastAcceptedRequestId: acceptedCommand.requestId,
213
+ lastAcceptedJobKeys: acceptedCommand.jobKeys,
214
+ },
215
+ jobs: {
216
+ total: items.length,
217
+ counts,
218
+ items,
219
+ },
220
+ continueAllowed,
221
+ safeNextAction,
222
+ };
223
+ }
224
+ //# sourceMappingURL=run-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-status.js","sourceRoot":"","sources":["../../../src/core/run-status.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
- import type { ControllerJobSpec, JobObservation } from "../protocol/types.js";
1
+ import type { ControllerCommand, ControllerJobSpec, JobObservation } from "../protocol/types.js";
2
2
  import type { RunEvent } from "../state/event-log.js";
3
- export type CueLineRunStatus = "running" | "complete" | "blocked" | "failed";
3
+ export type CueLineRunStatus = "running" | "complete" | "blocked" | "cancelled" | "failed";
4
+ export type CueLineExecutor = "caller" | "process";
5
+ export declare const DEFAULT_MAX_ROUNDS = 12;
4
6
  export type StoredJobStatus = JobObservation["status"];
5
- export type ControllerSubmissionState = "requested" | "possibly_sent" | "submitted";
7
+ export type ControllerSubmissionState = "requested" | "submitting" | "possibly_sent" | "submitted";
6
8
  export interface PendingControllerTurn {
7
9
  round: number;
8
10
  requestId: string;
@@ -13,6 +15,8 @@ export interface PendingControllerTurn {
13
15
  conversationUrl: string | null;
14
16
  selectedModelLabel: string | null;
15
17
  baselineAssistantMessageCount: number | null;
18
+ composerPromptState: "inline_ready" | "attachment_ready" | null;
19
+ manualSendConfirmed: boolean;
16
20
  }
17
21
  export interface RunFailureEvidence {
18
22
  code: string;
@@ -31,20 +35,29 @@ export interface StoredJob {
31
35
  output: string | null;
32
36
  error: string | null;
33
37
  }
38
+ export interface PendingCommandExecution {
39
+ command: ControllerCommand;
40
+ commandHash: string;
41
+ }
34
42
  export interface CueLineRunState {
35
43
  runId: string;
36
44
  request: string;
45
+ executor: CueLineExecutor;
46
+ maxRounds: number;
37
47
  status: CueLineRunStatus;
38
48
  round: number;
39
49
  conversationUrl: string | null;
40
50
  pendingControllerTurns: PendingControllerTurn[];
51
+ abandonedControllerTurns: PendingControllerTurn[];
41
52
  lastFailure: RunFailureEvidence | null;
42
53
  jobs: Record<string, StoredJob>;
43
54
  notices: string[];
44
55
  commandHashes: string[];
56
+ pendingCommandExecution: PendingCommandExecution | null;
45
57
  finalDeliveryText: string | null;
46
58
  blockedReason: string | null;
59
+ cancelledReason: string | null;
47
60
  }
48
- export declare function initialRunState(runId: string, request: string): CueLineRunState;
61
+ export declare function initialRunState(runId: string, request: string, executor?: CueLineExecutor, maxRounds?: number): CueLineRunState;
49
62
  export declare function reduceRunState(state: CueLineRunState, event: RunEvent): CueLineRunState;
50
63
  export declare function jobObservations(state: CueLineRunState): JobObservation[];
@@ -1,29 +1,78 @@
1
+ export const DEFAULT_MAX_ROUNDS = 12;
2
+ const STORED_JOB_STATUSES = new Set([
3
+ "pending",
4
+ "running",
5
+ "succeeded",
6
+ "failed",
7
+ "timed_out",
8
+ "cancelled",
9
+ "ambiguous",
10
+ ]);
11
+ const TERMINAL_JOB_STATUSES = new Set([
12
+ "succeeded",
13
+ "failed",
14
+ "timed_out",
15
+ "cancelled",
16
+ "ambiguous",
17
+ ]);
1
18
  function recordPayload(event) {
2
19
  if (typeof event.payload !== "object" || event.payload === null || Array.isArray(event.payload)) {
3
20
  return {};
4
21
  }
5
22
  return event.payload;
6
23
  }
7
- export function initialRunState(runId, request) {
24
+ function normalizedConversationUrl(value) {
25
+ try {
26
+ const parsed = new URL(value);
27
+ return `${parsed.origin}${parsed.pathname}`;
28
+ }
29
+ catch {
30
+ return value;
31
+ }
32
+ }
33
+ function preserveCanonicalConversationUrl(canonical, candidate) {
34
+ if (typeof candidate !== "string" || candidate === "")
35
+ return canonical;
36
+ if (canonical !== null &&
37
+ normalizedConversationUrl(candidate) !== normalizedConversationUrl(canonical)) {
38
+ return canonical;
39
+ }
40
+ return canonical ?? candidate;
41
+ }
42
+ export function initialRunState(runId, request, executor = "process", maxRounds = DEFAULT_MAX_ROUNDS) {
8
43
  return {
9
44
  runId,
10
45
  request,
46
+ executor,
47
+ maxRounds,
11
48
  status: "running",
12
49
  round: 0,
13
50
  conversationUrl: null,
14
51
  pendingControllerTurns: [],
52
+ abandonedControllerTurns: [],
15
53
  lastFailure: null,
16
54
  jobs: {},
17
55
  notices: [],
18
56
  commandHashes: [],
57
+ pendingCommandExecution: null,
19
58
  finalDeliveryText: null,
20
59
  blockedReason: null,
60
+ cancelledReason: null,
21
61
  };
22
62
  }
23
63
  export function reduceRunState(state, event) {
24
64
  const payload = recordPayload(event);
25
65
  if (event.type === "run_created" && typeof payload.request === "string") {
26
- return { ...state, request: payload.request };
66
+ return {
67
+ ...state,
68
+ request: payload.request,
69
+ executor: payload.executor === "caller" ? "caller" : state.executor ?? "process",
70
+ maxRounds: typeof payload.max_rounds === "number" &&
71
+ Number.isSafeInteger(payload.max_rounds) &&
72
+ payload.max_rounds >= 1
73
+ ? payload.max_rounds
74
+ : state.maxRounds ?? DEFAULT_MAX_ROUNDS,
75
+ };
27
76
  }
28
77
  if (event.type === "run_resumed") {
29
78
  return { ...state, status: "running" };
@@ -48,18 +97,28 @@ export function reduceRunState(state, event) {
48
97
  conversationUrl: state.conversationUrl,
49
98
  selectedModelLabel: null,
50
99
  baselineAssistantMessageCount: null,
100
+ composerPromptState: null,
101
+ manualSendConfirmed: false,
51
102
  },
52
103
  ],
104
+ abandonedControllerTurns: (state.abandonedControllerTurns ?? []).filter((turn) => turn.requestId !== payload.request_id),
53
105
  };
54
106
  }
55
107
  if (event.type === "controller_conversation_bound" &&
56
108
  typeof payload.conversation_url === "string" &&
57
109
  payload.conversation_url !== "") {
110
+ const boundTurn = typeof payload.request_id === "string"
111
+ ? (state.pendingControllerTurns ?? []).find((turn) => turn.requestId === payload.request_id)
112
+ : undefined;
113
+ const conversationUrl = preserveCanonicalConversationUrl(state.conversationUrl ?? boundTurn?.conversationUrl ?? null, payload.conversation_url);
58
114
  return {
59
115
  ...state,
60
- conversationUrl: payload.conversation_url,
116
+ conversationUrl,
61
117
  pendingControllerTurns: (state.pendingControllerTurns ?? []).map((turn) => typeof payload.request_id === "string" && turn.requestId === payload.request_id
62
- ? { ...turn, conversationUrl: payload.conversation_url }
118
+ ? {
119
+ ...turn,
120
+ conversationUrl: preserveCanonicalConversationUrl(turn.conversationUrl ?? state.conversationUrl, payload.conversation_url),
121
+ }
63
122
  : turn),
64
123
  };
65
124
  }
@@ -69,9 +128,7 @@ export function reduceRunState(state, event) {
69
128
  const pending = (state.pendingControllerTurns ?? []).find((turn) => turn.requestId === payload.request_id);
70
129
  if (!pending)
71
130
  return state;
72
- const conversationUrl = typeof payload.conversation_url === "string" && payload.conversation_url !== ""
73
- ? payload.conversation_url
74
- : pending.conversationUrl;
131
+ const conversationUrl = preserveCanonicalConversationUrl(state.conversationUrl ?? pending.conversationUrl, payload.conversation_url);
75
132
  return {
76
133
  ...state,
77
134
  conversationUrl: conversationUrl ?? state.conversationUrl,
@@ -79,7 +136,11 @@ export function reduceRunState(state, event) {
79
136
  ? turn
80
137
  : {
81
138
  ...turn,
82
- submissionState: payload.submission_state === "submitted" ? "submitted" : "possibly_sent",
139
+ submissionState: payload.submission_state === "submitted"
140
+ ? "submitted"
141
+ : payload.submission_state === "possibly_sent"
142
+ ? "possibly_sent"
143
+ : "submitting",
83
144
  conversationUrl,
84
145
  selectedModelLabel: typeof payload.selected_model_label === "string"
85
146
  ? payload.selected_model_label
@@ -87,29 +148,90 @@ export function reduceRunState(state, event) {
87
148
  baselineAssistantMessageCount: typeof payload.baseline_assistant_message_count === "number"
88
149
  ? payload.baseline_assistant_message_count
89
150
  : turn.baselineAssistantMessageCount,
151
+ composerPromptState: payload.composer_prompt_state === "inline_ready" ||
152
+ payload.composer_prompt_state === "attachment_ready"
153
+ ? payload.composer_prompt_state
154
+ : turn.composerPromptState,
90
155
  }),
91
156
  };
92
157
  }
93
158
  if (event.type === "controller_response_received") {
94
159
  return {
95
160
  ...state,
96
- conversationUrl: typeof payload.conversation_url === "string"
97
- ? payload.conversation_url
98
- : state.conversationUrl,
99
- pendingControllerTurns: typeof payload.request_id === "string"
100
- ? (state.pendingControllerTurns ?? []).filter((turn) => turn.requestId !== payload.request_id)
101
- : state.pendingControllerTurns ?? [],
161
+ conversationUrl: preserveCanonicalConversationUrl(state.conversationUrl, payload.conversation_url),
162
+ // A received response is not yet an accepted command. Keep the exact
163
+ // pending turn recoverable until controller_command_accepted commits;
164
+ // otherwise a crash between these two events can resend the next round.
165
+ pendingControllerTurns: state.pendingControllerTurns ?? [],
102
166
  lastFailure: null,
103
167
  };
104
168
  }
105
169
  if (event.type === "controller_turn_abandoned" && typeof payload.request_id === "string") {
170
+ const abandoned = (state.pendingControllerTurns ?? []).find((turn) => turn.requestId === payload.request_id);
106
171
  return {
107
172
  ...state,
108
173
  pendingControllerTurns: (state.pendingControllerTurns ?? []).filter((turn) => turn.requestId !== payload.request_id),
174
+ abandonedControllerTurns: abandoned === undefined
175
+ ? state.abandonedControllerTurns ?? []
176
+ : [
177
+ ...(state.abandonedControllerTurns ?? []).filter((turn) => turn.requestId !== abandoned.requestId),
178
+ abandoned,
179
+ ],
180
+ };
181
+ }
182
+ if (event.type === "controller_turn_manual_submission_confirmed" &&
183
+ typeof payload.request_id === "string") {
184
+ const existing = (state.pendingControllerTurns ?? []).find((turn) => turn.requestId === payload.request_id) ??
185
+ (state.abandonedControllerTurns ?? []).find((turn) => turn.requestId === payload.request_id);
186
+ if (!existing)
187
+ return state;
188
+ const restored = {
189
+ ...existing,
190
+ submissionState: "submitted",
191
+ manualSendConfirmed: true,
192
+ conversationUrl: preserveCanonicalConversationUrl(state.conversationUrl ?? existing.conversationUrl, payload.conversation_url),
193
+ };
194
+ return {
195
+ ...state,
196
+ conversationUrl: restored.conversationUrl ?? state.conversationUrl,
197
+ pendingControllerTurns: [
198
+ ...(state.pendingControllerTurns ?? []).filter((turn) => turn.requestId !== restored.requestId),
199
+ restored,
200
+ ],
201
+ abandonedControllerTurns: (state.abandonedControllerTurns ?? []).filter((turn) => turn.requestId !== restored.requestId),
109
202
  };
110
203
  }
111
204
  if (event.type === "controller_command_accepted" && typeof payload.command_hash === "string") {
112
- return { ...state, commandHashes: [...state.commandHashes, payload.command_hash] };
205
+ const command = typeof payload.command === "object" &&
206
+ payload.command !== null &&
207
+ !Array.isArray(payload.command)
208
+ ? payload.command
209
+ : {};
210
+ const requestId = typeof command.request_id === "string" ? command.request_id : undefined;
211
+ return {
212
+ ...state,
213
+ commandHashes: [...state.commandHashes, payload.command_hash],
214
+ pendingCommandExecution: typeof payload.command === "object" &&
215
+ payload.command !== null &&
216
+ !Array.isArray(payload.command)
217
+ ? {
218
+ command: structuredClone(payload.command),
219
+ commandHash: payload.command_hash,
220
+ }
221
+ : state.pendingCommandExecution ?? null,
222
+ pendingControllerTurns: requestId === undefined
223
+ ? state.pendingControllerTurns ?? []
224
+ : (state.pendingControllerTurns ?? []).filter((turn) => turn.requestId !== requestId),
225
+ };
226
+ }
227
+ if (event.type === "controller_command_execution_completed" &&
228
+ typeof payload.command_hash === "string") {
229
+ return {
230
+ ...state,
231
+ pendingCommandExecution: state.pendingCommandExecution?.commandHash === payload.command_hash
232
+ ? null
233
+ : state.pendingCommandExecution ?? null,
234
+ };
113
235
  }
114
236
  if (event.type === "notice" && typeof payload.message === "string") {
115
237
  return { ...state, notices: [...state.notices, payload.message] };
@@ -122,9 +244,15 @@ export function reduceRunState(state, event) {
122
244
  }
123
245
  if (event.type === "job_status" && typeof payload.job_id === "string") {
124
246
  const existing = state.jobs[payload.job_id];
125
- if (!existing || typeof payload.status !== "string")
247
+ if (!existing ||
248
+ typeof payload.status !== "string" ||
249
+ !STORED_JOB_STATUSES.has(payload.status)) {
126
250
  return state;
251
+ }
127
252
  const status = payload.status;
253
+ if (TERMINAL_JOB_STATUSES.has(existing.status) && status !== existing.status) {
254
+ return state;
255
+ }
128
256
  return {
129
257
  ...state,
130
258
  jobs: {
@@ -143,6 +271,8 @@ export function reduceRunState(state, event) {
143
271
  ...state,
144
272
  status: "complete",
145
273
  pendingControllerTurns: [],
274
+ abandonedControllerTurns: [],
275
+ pendingCommandExecution: null,
146
276
  finalDeliveryText: payload.final_delivery_text,
147
277
  };
148
278
  }
@@ -151,14 +281,37 @@ export function reduceRunState(state, event) {
151
281
  ...state,
152
282
  status: "blocked",
153
283
  pendingControllerTurns: [],
284
+ abandonedControllerTurns: [],
285
+ pendingCommandExecution: null,
154
286
  blockedReason: payload.reason,
155
287
  finalDeliveryText: typeof payload.final_delivery_text === "string" ? payload.final_delivery_text : null,
156
288
  };
157
289
  }
290
+ if (event.type === "run_cancelled" && typeof payload.reason === "string") {
291
+ return {
292
+ ...state,
293
+ status: "cancelled",
294
+ pendingControllerTurns: [],
295
+ abandonedControllerTurns: [],
296
+ pendingCommandExecution: null,
297
+ cancelledReason: payload.reason,
298
+ };
299
+ }
158
300
  if (event.type === "run_failed") {
301
+ const failedRequestId = typeof payload.request_id === "string" ? payload.request_id : undefined;
302
+ const failedSubmissionState = payload.submission_state === "submitting" ||
303
+ payload.submission_state === "possibly_sent" ||
304
+ payload.submission_state === "submitted"
305
+ ? payload.submission_state
306
+ : undefined;
159
307
  return {
160
308
  ...state,
161
309
  status: "failed",
310
+ pendingControllerTurns: failedRequestId === undefined || failedSubmissionState === undefined
311
+ ? state.pendingControllerTurns ?? []
312
+ : (state.pendingControllerTurns ?? []).map((turn) => turn.requestId === failedRequestId
313
+ ? { ...turn, submissionState: failedSubmissionState }
314
+ : turn),
162
315
  lastFailure: {
163
316
  code: typeof payload.code === "string" ? payload.code : "CUELINE_INTERNAL",
164
317
  requestId: typeof payload.request_id === "string" ? payload.request_id : null,
@@ -166,6 +319,7 @@ export function reduceRunState(state, event) {
166
319
  stage: typeof payload.stage === "string" ? payload.stage : null,
167
320
  submissionState: payload.submission_state === "definitely_not_sent" ||
168
321
  payload.submission_state === "requested" ||
322
+ payload.submission_state === "submitting" ||
169
323
  payload.submission_state === "possibly_sent" ||
170
324
  payload.submission_state === "submitted"
171
325
  ? payload.submission_state