@ellipsis-dev/sdk 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-RMPAMUAE.js +208 -0
- package/dist/index.d.ts +2 -2
- package/dist/store/index.d.ts +45 -2
- package/dist/store/index.js +318 -4
- package/dist/stream/index.d.ts +1 -1
- package/dist/stream/index.js +18 -189
- package/dist/{types-D9g-eZui.d.ts → types-DB1hOyQE.d.ts} +106 -98
- package/package.json +1 -1
- package/schema/frames.schema.json +172 -127
- package/schema/openapi.v1.json +167 -135
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// src/stream/index.ts
|
|
2
|
+
var SESSION_STREAM_PROTOCOL_VERSION = 3;
|
|
3
|
+
var WS_CLOSE_NORMAL = 1e3;
|
|
4
|
+
var WS_CLOSE_GOING_AWAY = 1001;
|
|
5
|
+
var WS_CLOSE_UNSUPPORTED_PROTOCOL_VERSION = 1002;
|
|
6
|
+
var WS_CLOSE_NO_PROTOCOL = 1003;
|
|
7
|
+
var WS_CLOSE_AUTH_FAILED = 1008;
|
|
8
|
+
var WS_CLOSE_SERVER_ERROR = 1011;
|
|
9
|
+
var WS_CLOSE_OVER_CAPACITY = 1013;
|
|
10
|
+
function sessionStatusWord(session) {
|
|
11
|
+
return session.surface?.status ?? session.status;
|
|
12
|
+
}
|
|
13
|
+
var StreamUnavailableError = class extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "StreamUnavailableError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var StreamAuthError = class extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "StreamAuthError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function streamQuery(afterSeq) {
|
|
26
|
+
const base = `protocol=${SESSION_STREAM_PROTOCOL_VERSION}`;
|
|
27
|
+
return afterSeq > 0 ? `${base}&after_seq=${afterSeq}` : base;
|
|
28
|
+
}
|
|
29
|
+
var HEARTBEAT_TIMEOUT_MS = 45e3;
|
|
30
|
+
var DEFAULT_MAX_RECONNECTS = 5;
|
|
31
|
+
function classifyCloseCode(code) {
|
|
32
|
+
switch (code) {
|
|
33
|
+
case WS_CLOSE_NORMAL:
|
|
34
|
+
return "normal";
|
|
35
|
+
case WS_CLOSE_AUTH_FAILED:
|
|
36
|
+
case 4401:
|
|
37
|
+
// dashboard ticket door: bad/expired ticket
|
|
38
|
+
case 4403:
|
|
39
|
+
return "auth";
|
|
40
|
+
case WS_CLOSE_UNSUPPORTED_PROTOCOL_VERSION:
|
|
41
|
+
case WS_CLOSE_NO_PROTOCOL:
|
|
42
|
+
return "unsupported";
|
|
43
|
+
default:
|
|
44
|
+
return "retry";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function nextReconnectDelayMs(attempt) {
|
|
48
|
+
const base = 500;
|
|
49
|
+
const max = 8e3;
|
|
50
|
+
return Math.min(max, base * 2 ** Math.max(0, attempt - 1));
|
|
51
|
+
}
|
|
52
|
+
function decideReconnect(params) {
|
|
53
|
+
const { closeKind, everReceivedFrame, attempt, maxReconnects } = params;
|
|
54
|
+
if (closeKind === "auth") return { action: "fail-auth" };
|
|
55
|
+
if (closeKind === "unsupported") return { action: "fallback" };
|
|
56
|
+
const cap = everReceivedFrame ? maxReconnects : Math.min(2, maxReconnects);
|
|
57
|
+
if (attempt >= cap) return { action: "fallback" };
|
|
58
|
+
return { action: "reconnect", delayMs: nextReconnectDelayMs(attempt) };
|
|
59
|
+
}
|
|
60
|
+
function connectOnce(sock, emit, signal) {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
let settled = false;
|
|
63
|
+
let heartbeat;
|
|
64
|
+
const finish = (result) => {
|
|
65
|
+
if (settled) return;
|
|
66
|
+
settled = true;
|
|
67
|
+
if (heartbeat) clearTimeout(heartbeat);
|
|
68
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
69
|
+
sock.close();
|
|
70
|
+
resolve(result);
|
|
71
|
+
};
|
|
72
|
+
const onAbort = () => finish({ kind: "aborted" });
|
|
73
|
+
const bumpHeartbeat = () => {
|
|
74
|
+
if (heartbeat) clearTimeout(heartbeat);
|
|
75
|
+
heartbeat = setTimeout(
|
|
76
|
+
() => finish({ kind: "error", err: new Error("heartbeat timeout") }),
|
|
77
|
+
HEARTBEAT_TIMEOUT_MS
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
if (signal) {
|
|
81
|
+
if (signal.aborted) {
|
|
82
|
+
finish({ kind: "aborted" });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
signal.addEventListener("abort", onAbort);
|
|
86
|
+
}
|
|
87
|
+
sock.onOpen(() => bumpHeartbeat());
|
|
88
|
+
sock.onMessage((data) => {
|
|
89
|
+
bumpHeartbeat();
|
|
90
|
+
let frame;
|
|
91
|
+
try {
|
|
92
|
+
frame = JSON.parse(data);
|
|
93
|
+
} catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
emit(frame);
|
|
97
|
+
if (frame.type === "done") {
|
|
98
|
+
finish({ kind: "done" });
|
|
99
|
+
} else if (frame.type === "error") {
|
|
100
|
+
finish({
|
|
101
|
+
kind: "frameError",
|
|
102
|
+
message: frame.message ?? "stream error"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
sock.onClose((code) => finish({ kind: "closed", code }));
|
|
107
|
+
sock.onError((err) => finish({ kind: "error", err }));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function sleep(ms, signal) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const timer = setTimeout(resolve, ms);
|
|
113
|
+
signal?.addEventListener(
|
|
114
|
+
"abort",
|
|
115
|
+
() => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
resolve();
|
|
118
|
+
},
|
|
119
|
+
{ once: true }
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function streamSession(opts) {
|
|
124
|
+
const maxReconnects = opts.maxReconnects ?? DEFAULT_MAX_RECONNECTS;
|
|
125
|
+
let afterSeq = opts.afterSeq ?? 0;
|
|
126
|
+
let everReceivedFrame = false;
|
|
127
|
+
let attempt = 0;
|
|
128
|
+
let lastStatusWord = "";
|
|
129
|
+
let lastExitStatus = null;
|
|
130
|
+
const emit = (frame) => {
|
|
131
|
+
everReceivedFrame = true;
|
|
132
|
+
attempt = 0;
|
|
133
|
+
if (frame.type === "records_append") {
|
|
134
|
+
const records = frame.records;
|
|
135
|
+
for (const record of records) {
|
|
136
|
+
if (typeof record.feed_seq === "number") {
|
|
137
|
+
afterSeq = Math.max(afterSeq, record.feed_seq);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} else if (frame.type === "snapshot" || frame.type === "session") {
|
|
141
|
+
const session = frame.session;
|
|
142
|
+
lastStatusWord = sessionStatusWord(session);
|
|
143
|
+
lastExitStatus = session.exit_status ?? null;
|
|
144
|
+
}
|
|
145
|
+
opts.onFrame(frame);
|
|
146
|
+
};
|
|
147
|
+
for (; ; ) {
|
|
148
|
+
if (opts.signal?.aborted) return { type: "aborted" };
|
|
149
|
+
let res;
|
|
150
|
+
try {
|
|
151
|
+
const sock = await opts.openSocket({
|
|
152
|
+
sessionId: opts.sessionId,
|
|
153
|
+
afterSeq,
|
|
154
|
+
query: streamQuery(afterSeq)
|
|
155
|
+
});
|
|
156
|
+
res = await connectOnce(sock, emit, opts.signal);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
res = {
|
|
159
|
+
kind: "error",
|
|
160
|
+
err: err instanceof Error ? err : new Error(String(err))
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (res.kind === "done") {
|
|
164
|
+
return {
|
|
165
|
+
type: "done",
|
|
166
|
+
status: lastStatusWord,
|
|
167
|
+
exitStatus: lastExitStatus
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (res.kind === "frameError")
|
|
171
|
+
return { type: "error", message: res.message };
|
|
172
|
+
if (res.kind === "aborted") return { type: "aborted" };
|
|
173
|
+
attempt++;
|
|
174
|
+
const decision = decideReconnect({
|
|
175
|
+
closeKind: res.kind === "closed" ? classifyCloseCode(res.code) : void 0,
|
|
176
|
+
everReceivedFrame,
|
|
177
|
+
attempt,
|
|
178
|
+
maxReconnects
|
|
179
|
+
});
|
|
180
|
+
if (decision.action === "fail-auth") {
|
|
181
|
+
throw new StreamAuthError("not authorized to stream this session");
|
|
182
|
+
}
|
|
183
|
+
if (decision.action === "fallback") {
|
|
184
|
+
const why = res.kind === "error" ? res.err.message : `stream closed (code ${res.code})`;
|
|
185
|
+
throw new StreamUnavailableError(why);
|
|
186
|
+
}
|
|
187
|
+
await sleep(decision.delayMs ?? 0, opts.signal);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
SESSION_STREAM_PROTOCOL_VERSION,
|
|
193
|
+
WS_CLOSE_NORMAL,
|
|
194
|
+
WS_CLOSE_GOING_AWAY,
|
|
195
|
+
WS_CLOSE_UNSUPPORTED_PROTOCOL_VERSION,
|
|
196
|
+
WS_CLOSE_NO_PROTOCOL,
|
|
197
|
+
WS_CLOSE_AUTH_FAILED,
|
|
198
|
+
WS_CLOSE_SERVER_ERROR,
|
|
199
|
+
WS_CLOSE_OVER_CAPACITY,
|
|
200
|
+
sessionStatusWord,
|
|
201
|
+
StreamUnavailableError,
|
|
202
|
+
StreamAuthError,
|
|
203
|
+
streamQuery,
|
|
204
|
+
classifyCloseCode,
|
|
205
|
+
nextReconnectDelayMs,
|
|
206
|
+
decideReconnect,
|
|
207
|
+
streamSession
|
|
208
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as components } from './types-
|
|
2
|
-
export { A as AgentConfigSource, a as AttributionType, B as BudgetSource, C as ClaudeSessionRecord, b as CodexEvent, d as CodexItem, e as CodexSessionRecord, f as CreateReviewRequest, D as DeltaFrame, g as DoneFrame, E as ErrorFrame, F as Finding, G as GithubAccountSnippet, h as GithubAccountType, H as Harness, i as HeartbeatFrame, L as LifecycleSessionRecord, P as ParentKind, j as PromptBlockedReason, R as RecordsAppendFrame, k as ResolvedReviewScope, l as Review, m as ReviewConfiguration, n as ReviewCounters, o as ReviewFinding, p as ReviewRequester, q as ReviewScope, r as ReviewScopeKind, s as ReviewStage, t as ReviewedCommit, u as ReviewsListResponse, S as SdkAssistantRecord, v as SdkContentBlock, w as SdkRateLimitRecord, x as SdkRecord, y as SdkResultRecord, z as SdkSystemRecord, I as SdkUserRecord, J as SendSessionMessageRequest, K as Session, M as SessionExecution, N as SessionExecutionsListResponse, O as SessionExitStatus, Q as SessionFrame, T as
|
|
1
|
+
import { c as components } from './types-DB1hOyQE.js';
|
|
2
|
+
export { A as AgentConfigSource, a as AttributionType, B as BudgetSource, C as ClaudeSessionRecord, b as CodexEvent, d as CodexItem, e as CodexSessionRecord, f as CreateReviewRequest, D as DeltaFrame, g as DoneFrame, E as ErrorFrame, F as Finding, G as GithubAccountSnippet, h as GithubAccountType, H as Harness, i as HeartbeatFrame, L as LifecycleSessionRecord, P as ParentKind, j as PromptBlockedReason, R as RecordsAppendFrame, k as ResolvedReviewScope, l as Review, m as ReviewConfiguration, n as ReviewCounters, o as ReviewFinding, p as ReviewRequester, q as ReviewScope, r as ReviewScopeKind, s as ReviewStage, t as ReviewedCommit, u as ReviewsListResponse, S as SdkAssistantRecord, v as SdkContentBlock, w as SdkRateLimitRecord, x as SdkRecord, y as SdkResultRecord, z as SdkSystemRecord, I as SdkUserRecord, J as SendSessionMessageRequest, K as Session, M as SessionExecution, N as SessionExecutionsListResponse, O as SessionExitStatus, Q as SessionFrame, T as SessionGit, U as SessionLiveness, V as SessionMessage, W as SessionMessageResponse, X as SessionMessageStatus, Y as SessionPrompting, Z as SessionRecord, _ as SessionRecordsListResponse, $ as SessionResponse, a0 as SessionSource, a1 as SessionState, a2 as SessionStatus, a3 as SessionStreamFrame, a4 as SessionSurface, a5 as SessionsListResponse, a6 as SnapshotFrame, a7 as StreamFrame, a8 as TokensInfo, a9 as paths } from './types-DB1hOyQE.js';
|
|
3
3
|
|
|
4
4
|
interface CursorResponse {
|
|
5
5
|
has_more: boolean;
|
package/dist/store/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Z as SessionRecord, aa as CodexFileChangeItem, b as CodexEvent, ab as CodexMcpToolCallItem, x as SdkRecord, K as Session,
|
|
1
|
+
import { Z as SessionRecord, aa as CodexFileChangeItem, b as CodexEvent, ab as CodexMcpToolCallItem, x as SdkRecord, K as Session, V as SessionMessage, a7 as StreamFrame } from '../types-DB1hOyQE.js';
|
|
2
2
|
|
|
3
3
|
interface ChatToolNode {
|
|
4
4
|
key: string;
|
|
@@ -42,9 +42,46 @@ interface ChatTurn {
|
|
|
42
42
|
costUsd: number | null;
|
|
43
43
|
tokens: number | null;
|
|
44
44
|
resumed: boolean;
|
|
45
|
+
isError: boolean;
|
|
45
46
|
}
|
|
46
47
|
declare function groupRecordsToChatTurns(records: readonly SessionRecord[]): ChatTurn[];
|
|
47
48
|
|
|
49
|
+
type RecordSlice = {
|
|
50
|
+
feed_seq: number;
|
|
51
|
+
source: string;
|
|
52
|
+
record_type: string;
|
|
53
|
+
payload: Record<string, unknown>;
|
|
54
|
+
session_message_id?: string | null;
|
|
55
|
+
};
|
|
56
|
+
declare function recordSlice(records: readonly SessionRecord[]): readonly RecordSlice[];
|
|
57
|
+
declare function humanDuration(seconds: number): string;
|
|
58
|
+
declare function sessionLogText(recordType: string, payload: Record<string, unknown>): string | null;
|
|
59
|
+
declare function awaitingAgentPhase(records: readonly RecordSlice[]): 'boot' | 'turn' | null;
|
|
60
|
+
declare function deliveredUnechoedSends(records: readonly RecordSlice[]): {
|
|
61
|
+
id: string;
|
|
62
|
+
body: string;
|
|
63
|
+
cancelled: boolean;
|
|
64
|
+
}[];
|
|
65
|
+
type SandboxLogKind = 'step' | 'output' | 'done' | 'failed';
|
|
66
|
+
type SandboxLogLine = {
|
|
67
|
+
key: string;
|
|
68
|
+
kind: SandboxLogKind;
|
|
69
|
+
text: string;
|
|
70
|
+
};
|
|
71
|
+
type SandboxState = {
|
|
72
|
+
headline: string;
|
|
73
|
+
done: boolean;
|
|
74
|
+
readySeconds: number | null;
|
|
75
|
+
sandboxDone: boolean;
|
|
76
|
+
configName: string | null;
|
|
77
|
+
configCommitSha: string | null;
|
|
78
|
+
log: SandboxLogLine[];
|
|
79
|
+
};
|
|
80
|
+
declare function hookPhrase(step: string): string;
|
|
81
|
+
declare function deriveSandboxState(records: readonly RecordSlice[], minFeedSeq: number): SandboxState | null;
|
|
82
|
+
declare function sandboxSummary(sandbox: SandboxState | null): string;
|
|
83
|
+
declare function lastLines(log: readonly SandboxLogLine[], max: number): SandboxLogLine[];
|
|
84
|
+
|
|
48
85
|
declare function sandboxOutputStep(payload: Record<string, unknown>): string;
|
|
49
86
|
declare function sandboxOutputLines(payload: Record<string, unknown>): string[];
|
|
50
87
|
declare function sandboxOutputLine(payload: Record<string, unknown>): string | null;
|
|
@@ -104,6 +141,12 @@ interface SessionTranscriptSnapshot {
|
|
|
104
141
|
lastEventAt: number | null;
|
|
105
142
|
conversationOver: boolean;
|
|
106
143
|
}
|
|
144
|
+
declare function seedTranscriptStore(store: SessionTranscriptStore, seed: {
|
|
145
|
+
session: Session;
|
|
146
|
+
records: readonly SessionRecord[];
|
|
147
|
+
messages?: readonly SessionMessage[] | null;
|
|
148
|
+
earliestFeedSeq?: number | null;
|
|
149
|
+
}): void;
|
|
107
150
|
declare function emptySessionTranscriptSnapshot(): SessionTranscriptSnapshot;
|
|
108
151
|
declare class SessionTranscriptStore {
|
|
109
152
|
private snapshot;
|
|
@@ -122,4 +165,4 @@ declare class SessionTranscriptStore {
|
|
|
122
165
|
ingest: (rawFrame: StreamFrame) => void;
|
|
123
166
|
}
|
|
124
167
|
|
|
125
|
-
export { type ChatNode, type ChatToolNode, type ChatTurn, type EventToItemsOptions, type ItemKind, type SessionTranscriptSnapshot, SessionTranscriptStore, StreamFrame, type TranscriptItem, cacheTierLabel, clampLines, codexChangedPaths, codexEventToItems, codexMcpToolName, collapseToolRuns, emptySessionTranscriptSnapshot, eventToItems, foldCosts, formatDuration, groupRecordsToChatTurns, isConnectVisibleRecord, isConversationOver, lifecycleText, oneLine, pendingToolCalls, recordToItems, resultCostUsd, sandboxOutputLine, sandboxOutputLines, sandboxOutputStep, sandboxPhaseLabel, statusActivityText, summarizeToolInput, toolResultText };
|
|
168
|
+
export { type ChatNode, type ChatToolNode, type ChatTurn, type EventToItemsOptions, type ItemKind, type RecordSlice, type SandboxLogKind, type SandboxLogLine, type SandboxState, type SessionTranscriptSnapshot, SessionTranscriptStore, StreamFrame, type TranscriptItem, awaitingAgentPhase, cacheTierLabel, clampLines, codexChangedPaths, codexEventToItems, codexMcpToolName, collapseToolRuns, deliveredUnechoedSends, deriveSandboxState, emptySessionTranscriptSnapshot, eventToItems, foldCosts, formatDuration, groupRecordsToChatTurns, hookPhrase, humanDuration, isConnectVisibleRecord, isConversationOver, lastLines, lifecycleText, oneLine, pendingToolCalls, recordSlice, recordToItems, resultCostUsd, sandboxOutputLine, sandboxOutputLines, sandboxOutputStep, sandboxPhaseLabel, sandboxSummary, seedTranscriptStore, sessionLogText, statusActivityText, summarizeToolInput, toolResultText };
|
package/dist/store/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_STREAM_PROTOCOL_VERSION
|
|
3
|
+
} from "../chunk-RMPAMUAE.js";
|
|
4
|
+
|
|
1
5
|
// src/store/lifecycle.ts
|
|
2
6
|
function sandboxOutputStep(payload) {
|
|
3
7
|
if (typeof payload.step === "string" && payload.step) return payload.step;
|
|
@@ -483,7 +487,8 @@ function groupRecordsToChatTurns(records) {
|
|
|
483
487
|
durationMs: null,
|
|
484
488
|
costUsd: null,
|
|
485
489
|
tokens: null,
|
|
486
|
-
resumed: pendingResume
|
|
490
|
+
resumed: pendingResume,
|
|
491
|
+
isError: false
|
|
487
492
|
});
|
|
488
493
|
pendingResume = false;
|
|
489
494
|
openIdx = turns.length - 1;
|
|
@@ -549,16 +554,18 @@ function groupRecordsToChatTurns(records) {
|
|
|
549
554
|
durationMs: null,
|
|
550
555
|
costUsd: null,
|
|
551
556
|
tokens: null,
|
|
552
|
-
resumed: false
|
|
557
|
+
resumed: false,
|
|
558
|
+
isError: false
|
|
553
559
|
});
|
|
554
560
|
continue;
|
|
555
561
|
}
|
|
556
562
|
if (record.record_format === "codex_jsonl@1") {
|
|
557
563
|
codexEventIntoTurns(record, record.payload, {
|
|
558
564
|
openAgentTurn,
|
|
559
|
-
closeTurn: (completedAt) => {
|
|
565
|
+
closeTurn: (completedAt, isError) => {
|
|
560
566
|
if (openIdx >= 0) {
|
|
561
567
|
turns[openIdx].completedAt = completedAt;
|
|
568
|
+
if (isError) turns[openIdx].isError = true;
|
|
562
569
|
openIdx = -1;
|
|
563
570
|
}
|
|
564
571
|
},
|
|
@@ -581,6 +588,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
581
588
|
if (typeof data.cost_usd === "number") {
|
|
582
589
|
turns[openIdx].costUsd = data.cost_usd;
|
|
583
590
|
}
|
|
591
|
+
if (data.is_error) turns[openIdx].isError = true;
|
|
584
592
|
openIdx = -1;
|
|
585
593
|
}
|
|
586
594
|
continue;
|
|
@@ -592,6 +600,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
592
600
|
openIdx = -1;
|
|
593
601
|
turns.push({
|
|
594
602
|
resumed: false,
|
|
603
|
+
isError: false,
|
|
595
604
|
key: nextKey(record.id),
|
|
596
605
|
role: "user",
|
|
597
606
|
nodes: [
|
|
@@ -672,7 +681,7 @@ function groupRecordsToChatTurns(records) {
|
|
|
672
681
|
}
|
|
673
682
|
function codexEventIntoTurns(record, event, ops) {
|
|
674
683
|
if (event.type === "turn.completed" || event.type === "turn.failed") {
|
|
675
|
-
ops.closeTurn(record.created_at);
|
|
684
|
+
ops.closeTurn(record.created_at, event.type === "turn.failed");
|
|
676
685
|
return;
|
|
677
686
|
}
|
|
678
687
|
if (event.type === "error") {
|
|
@@ -770,6 +779,289 @@ function codexEventIntoTurns(record, event, ops) {
|
|
|
770
779
|
}
|
|
771
780
|
}
|
|
772
781
|
|
|
782
|
+
// src/store/derive.ts
|
|
783
|
+
function recordSlice(records) {
|
|
784
|
+
return records;
|
|
785
|
+
}
|
|
786
|
+
function humanDuration(seconds) {
|
|
787
|
+
const clamped = Math.max(0, seconds);
|
|
788
|
+
if (clamped === 0) return "0s";
|
|
789
|
+
if (clamped < 1) return `${Math.round(clamped * 1e3)}ms`;
|
|
790
|
+
if (clamped < 5) {
|
|
791
|
+
const s2 = clamped.toFixed(1);
|
|
792
|
+
return s2.endsWith(".0") ? `${Math.round(clamped)}s` : `${s2}s`;
|
|
793
|
+
}
|
|
794
|
+
const total = Math.round(clamped);
|
|
795
|
+
const h = Math.floor(total / 3600);
|
|
796
|
+
const m = Math.floor(total % 3600 / 60);
|
|
797
|
+
const s = total % 60;
|
|
798
|
+
const bits = [];
|
|
799
|
+
if (h > 0) bits.push(`${h}h`);
|
|
800
|
+
if (m > 0) bits.push(`${m}m`);
|
|
801
|
+
if (s > 0 || bits.length === 0) bits.push(`${s}s`);
|
|
802
|
+
return bits.join(" ");
|
|
803
|
+
}
|
|
804
|
+
function sessionLogText(recordType, payload) {
|
|
805
|
+
const p = payload;
|
|
806
|
+
switch (recordType) {
|
|
807
|
+
case "session_idle":
|
|
808
|
+
return "Session asleep";
|
|
809
|
+
case "session_starting": {
|
|
810
|
+
const wake = typeof p.wake_index === "number" ? p.wake_index : 0;
|
|
811
|
+
const attempt = typeof p.attempt === "number" ? p.attempt : 0;
|
|
812
|
+
if (attempt > 0) return "Restarting the sandbox after a transient error\u2026";
|
|
813
|
+
return wake > 0 ? "Waking the session\u2026" : null;
|
|
814
|
+
}
|
|
815
|
+
case "session_retrying":
|
|
816
|
+
return typeof p.reason === "string" && p.reason ? `Retrying \xB7 ${p.reason}` : "Retrying after a transient error\u2026";
|
|
817
|
+
case "session_resumed":
|
|
818
|
+
return "Session awake";
|
|
819
|
+
case "session_cancelled": {
|
|
820
|
+
const reason = typeof p.reason === "string" && p.reason ? ` \xB7 ${p.reason}` : "";
|
|
821
|
+
return `Session cancelled${reason}`;
|
|
822
|
+
}
|
|
823
|
+
default:
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function awaitingAgentPhase(records) {
|
|
828
|
+
let inFlight = false;
|
|
829
|
+
let sawAgent = false;
|
|
830
|
+
for (const r of records) {
|
|
831
|
+
if (r.source !== "lifecycle") {
|
|
832
|
+
sawAgent = true;
|
|
833
|
+
} else {
|
|
834
|
+
if (r.record_type === "turn_started") inFlight = true;
|
|
835
|
+
else if (r.record_type === "turn_completed" || r.record_type === "turn_failed") {
|
|
836
|
+
inFlight = false;
|
|
837
|
+
} else if (r.record_type === "session_starting" || r.record_type === "session_retrying") {
|
|
838
|
+
inFlight = false;
|
|
839
|
+
sawAgent = false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (!inFlight) return null;
|
|
844
|
+
return sawAgent ? "turn" : "boot";
|
|
845
|
+
}
|
|
846
|
+
function deliveredUnechoedSends(records) {
|
|
847
|
+
const received = /* @__PURE__ */ new Map();
|
|
848
|
+
const delivered = /* @__PURE__ */ new Map();
|
|
849
|
+
const failedTurns = /* @__PURE__ */ new Set();
|
|
850
|
+
const echoed = /* @__PURE__ */ new Set();
|
|
851
|
+
for (const r of records) {
|
|
852
|
+
if (r.session_message_id != null) echoed.add(r.session_message_id);
|
|
853
|
+
if (r.source !== "lifecycle") continue;
|
|
854
|
+
if (r.record_type === "turn_failed") {
|
|
855
|
+
if (typeof r.payload.turn_id === "string")
|
|
856
|
+
failedTurns.add(r.payload.turn_id);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const id = typeof r.payload.message_id === "string" ? r.payload.message_id : null;
|
|
860
|
+
if (!id) continue;
|
|
861
|
+
if (r.record_type === "message_received") {
|
|
862
|
+
if (!received.has(id))
|
|
863
|
+
received.set(
|
|
864
|
+
id,
|
|
865
|
+
typeof r.payload.body === "string" ? r.payload.body : ""
|
|
866
|
+
);
|
|
867
|
+
} else if (r.record_type === "message_delivered") {
|
|
868
|
+
delivered.set(
|
|
869
|
+
id,
|
|
870
|
+
typeof r.payload.turn_id === "string" ? r.payload.turn_id : ""
|
|
871
|
+
);
|
|
872
|
+
} else if (r.record_type === "message_requeued") delivered.delete(id);
|
|
873
|
+
}
|
|
874
|
+
const out = [];
|
|
875
|
+
for (const [id, body] of received) {
|
|
876
|
+
const turnId = delivered.get(id);
|
|
877
|
+
if (turnId === void 0 || echoed.has(id)) continue;
|
|
878
|
+
out.push({ id, body, cancelled: failedTurns.has(turnId) });
|
|
879
|
+
}
|
|
880
|
+
return out;
|
|
881
|
+
}
|
|
882
|
+
function msLabel(ms) {
|
|
883
|
+
if (typeof ms !== "number" || !isFinite(ms) || ms < 0) return null;
|
|
884
|
+
return humanDuration(ms / 1e3);
|
|
885
|
+
}
|
|
886
|
+
function imageStepLabel(step) {
|
|
887
|
+
switch (step) {
|
|
888
|
+
case "build":
|
|
889
|
+
return "Building image";
|
|
890
|
+
case "container":
|
|
891
|
+
return "Starting container";
|
|
892
|
+
case "smoke":
|
|
893
|
+
return "Smoke check";
|
|
894
|
+
default:
|
|
895
|
+
return step;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function hookPhrase(step) {
|
|
899
|
+
switch (step) {
|
|
900
|
+
case "setup":
|
|
901
|
+
case "image.setup":
|
|
902
|
+
return "Building image";
|
|
903
|
+
case "clone":
|
|
904
|
+
return "Fetching repositories";
|
|
905
|
+
case "post_start":
|
|
906
|
+
return "Post-start setup";
|
|
907
|
+
case "post_clone":
|
|
908
|
+
return "Post-clone setup";
|
|
909
|
+
default:
|
|
910
|
+
return step;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function stepLabel(phase, step) {
|
|
914
|
+
if (step) {
|
|
915
|
+
if (phase === "hooks") return hookPhrase(step);
|
|
916
|
+
if (phase === "image") return imageStepLabel(step);
|
|
917
|
+
return step;
|
|
918
|
+
}
|
|
919
|
+
return sandboxPhaseLabel(phase);
|
|
920
|
+
}
|
|
921
|
+
function deriveSandboxState(records, minFeedSeq) {
|
|
922
|
+
let seen = false;
|
|
923
|
+
let headline = "Starting cloud agent\u2026";
|
|
924
|
+
let done = false;
|
|
925
|
+
let sandboxDone = false;
|
|
926
|
+
let readySeconds = null;
|
|
927
|
+
let configName = null;
|
|
928
|
+
let configCommitSha = null;
|
|
929
|
+
let log = [];
|
|
930
|
+
let open = /* @__PURE__ */ new Map();
|
|
931
|
+
const push = (record, kind, text) => {
|
|
932
|
+
const entry = { key: `${record.feed_seq}:${log.length}`, kind, text };
|
|
933
|
+
log.push(entry);
|
|
934
|
+
return entry;
|
|
935
|
+
};
|
|
936
|
+
const reset = () => {
|
|
937
|
+
log = [];
|
|
938
|
+
open = /* @__PURE__ */ new Map();
|
|
939
|
+
sandboxDone = false;
|
|
940
|
+
};
|
|
941
|
+
for (const record of records) {
|
|
942
|
+
if (record.feed_seq <= minFeedSeq || record.source !== "lifecycle")
|
|
943
|
+
continue;
|
|
944
|
+
const p = record.payload;
|
|
945
|
+
switch (record.record_type) {
|
|
946
|
+
case "session_scheduled": {
|
|
947
|
+
seen = true;
|
|
948
|
+
headline = "Session scheduled\u2026";
|
|
949
|
+
done = false;
|
|
950
|
+
configName = typeof p.config_name === "string" && p.config_name ? p.config_name : null;
|
|
951
|
+
configCommitSha = typeof p.config_commit_sha === "string" && p.config_commit_sha ? p.config_commit_sha : null;
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
case "session_starting":
|
|
955
|
+
case "session_retrying": {
|
|
956
|
+
seen = true;
|
|
957
|
+
const text = lifecycleText(record.record_type, p);
|
|
958
|
+
headline = !text || text === "Session starting\u2026" ? "Starting cloud agent\u2026" : text;
|
|
959
|
+
done = false;
|
|
960
|
+
readySeconds = null;
|
|
961
|
+
reset();
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
case "session_resumed":
|
|
965
|
+
case "session_idle": {
|
|
966
|
+
seen = true;
|
|
967
|
+
done = true;
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
case "sandbox_starting": {
|
|
971
|
+
seen = true;
|
|
972
|
+
reset();
|
|
973
|
+
push(record, "step", "Starting sandbox\u2026");
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
case "sandbox_phase": {
|
|
977
|
+
seen = true;
|
|
978
|
+
const phase = typeof p.phase === "string" && p.phase ? p.phase : "setup";
|
|
979
|
+
const step = typeof p.step === "string" && p.step ? p.step : null;
|
|
980
|
+
const key = step ? `${phase}:${step}` : phase;
|
|
981
|
+
const label = stepLabel(phase, step);
|
|
982
|
+
if (p.status === "completed" || p.status === "failed") {
|
|
983
|
+
const detail = p.detail && typeof p.detail === "object" ? p.detail : {};
|
|
984
|
+
const tier = cacheTierLabel(detail.cache_tier);
|
|
985
|
+
const dur = msLabel(p.duration_ms);
|
|
986
|
+
const failed = p.status === "failed";
|
|
987
|
+
const base = failed ? `${label} failed` : label;
|
|
988
|
+
const text = [
|
|
989
|
+
base,
|
|
990
|
+
...tier ? [tier] : [],
|
|
991
|
+
...dur ? [dur] : []
|
|
992
|
+
].join(" \xB7 ");
|
|
993
|
+
const line = open.get(key);
|
|
994
|
+
if (line) {
|
|
995
|
+
line.kind = failed ? "failed" : "done";
|
|
996
|
+
line.text = text;
|
|
997
|
+
open.delete(key);
|
|
998
|
+
} else {
|
|
999
|
+
push(record, failed ? "failed" : "done", text);
|
|
1000
|
+
}
|
|
1001
|
+
} else if (!open.has(key)) {
|
|
1002
|
+
open.set(key, push(record, "step", `${label}\u2026`));
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
case "sandbox_output": {
|
|
1007
|
+
seen = true;
|
|
1008
|
+
for (const l of sandboxOutputLines(p)) push(record, "output", l);
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
case "sandbox_ready": {
|
|
1012
|
+
seen = true;
|
|
1013
|
+
for (const [, line] of open) line.kind = "done";
|
|
1014
|
+
open = /* @__PURE__ */ new Map();
|
|
1015
|
+
const timings = p.phase_timings && typeof p.phase_timings === "object" ? Object.values(p.phase_timings) : [];
|
|
1016
|
+
const totalSeconds = timings.reduce(
|
|
1017
|
+
(acc, v) => typeof v === "number" && isFinite(v) ? acc + v : acc,
|
|
1018
|
+
0
|
|
1019
|
+
);
|
|
1020
|
+
const tier = cacheTierLabel(p.cache_tier);
|
|
1021
|
+
push(
|
|
1022
|
+
record,
|
|
1023
|
+
"done",
|
|
1024
|
+
[
|
|
1025
|
+
"Sandbox ready",
|
|
1026
|
+
...tier ? [tier] : [],
|
|
1027
|
+
...totalSeconds > 0 ? [humanDuration(totalSeconds)] : []
|
|
1028
|
+
].join(" \xB7 ")
|
|
1029
|
+
);
|
|
1030
|
+
sandboxDone = true;
|
|
1031
|
+
readySeconds = totalSeconds > 0 ? totalSeconds : null;
|
|
1032
|
+
done = true;
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
default:
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
const full = configName ? [
|
|
1040
|
+
{
|
|
1041
|
+
key: "config",
|
|
1042
|
+
kind: "done",
|
|
1043
|
+
text: `Using ${configName}${configCommitSha ? ` @ ${configCommitSha.slice(0, 7)}` : ""}`
|
|
1044
|
+
},
|
|
1045
|
+
...log
|
|
1046
|
+
] : log;
|
|
1047
|
+
return seen ? {
|
|
1048
|
+
headline,
|
|
1049
|
+
done,
|
|
1050
|
+
readySeconds,
|
|
1051
|
+
sandboxDone,
|
|
1052
|
+
configName,
|
|
1053
|
+
configCommitSha,
|
|
1054
|
+
log: full
|
|
1055
|
+
} : null;
|
|
1056
|
+
}
|
|
1057
|
+
function sandboxSummary(sandbox) {
|
|
1058
|
+
const seconds = sandbox?.readySeconds ?? null;
|
|
1059
|
+
return seconds ? `Sandbox ready in ${humanDuration(seconds)}` : "Sandbox started";
|
|
1060
|
+
}
|
|
1061
|
+
function lastLines(log, max) {
|
|
1062
|
+
return log.length <= max ? [...log] : log.slice(log.length - max);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
773
1065
|
// src/store/index.ts
|
|
774
1066
|
var TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set([
|
|
775
1067
|
"completed",
|
|
@@ -783,6 +1075,18 @@ function isConversationOver(session) {
|
|
|
783
1075
|
}
|
|
784
1076
|
return TERMINAL_SESSION_STATUSES.has(session.status);
|
|
785
1077
|
}
|
|
1078
|
+
function seedTranscriptStore(store, seed) {
|
|
1079
|
+
store.ingest({
|
|
1080
|
+
type: "snapshot",
|
|
1081
|
+
protocol: SESSION_STREAM_PROTOCOL_VERSION,
|
|
1082
|
+
earliest_feed_seq: seed.earliestFeedSeq ?? null,
|
|
1083
|
+
session: seed.session,
|
|
1084
|
+
messages: [...seed.messages ?? []]
|
|
1085
|
+
});
|
|
1086
|
+
const ordered = [...seed.records].sort((a, b) => a.feed_seq - b.feed_seq);
|
|
1087
|
+
if (ordered.length)
|
|
1088
|
+
store.ingest({ type: "records_append", records: ordered });
|
|
1089
|
+
}
|
|
786
1090
|
var EMPTY_SNAPSHOT = {
|
|
787
1091
|
session: null,
|
|
788
1092
|
records: [],
|
|
@@ -963,28 +1267,38 @@ var SessionTranscriptStore = class {
|
|
|
963
1267
|
};
|
|
964
1268
|
export {
|
|
965
1269
|
SessionTranscriptStore,
|
|
1270
|
+
awaitingAgentPhase,
|
|
966
1271
|
cacheTierLabel,
|
|
967
1272
|
clampLines,
|
|
968
1273
|
codexChangedPaths,
|
|
969
1274
|
codexEventToItems,
|
|
970
1275
|
codexMcpToolName,
|
|
971
1276
|
collapseToolRuns,
|
|
1277
|
+
deliveredUnechoedSends,
|
|
1278
|
+
deriveSandboxState,
|
|
972
1279
|
emptySessionTranscriptSnapshot,
|
|
973
1280
|
eventToItems,
|
|
974
1281
|
foldCosts,
|
|
975
1282
|
formatDuration,
|
|
976
1283
|
groupRecordsToChatTurns,
|
|
1284
|
+
hookPhrase,
|
|
1285
|
+
humanDuration,
|
|
977
1286
|
isConnectVisibleRecord,
|
|
978
1287
|
isConversationOver,
|
|
1288
|
+
lastLines,
|
|
979
1289
|
lifecycleText,
|
|
980
1290
|
oneLine,
|
|
981
1291
|
pendingToolCalls,
|
|
1292
|
+
recordSlice,
|
|
982
1293
|
recordToItems,
|
|
983
1294
|
resultCostUsd,
|
|
984
1295
|
sandboxOutputLine,
|
|
985
1296
|
sandboxOutputLines,
|
|
986
1297
|
sandboxOutputStep,
|
|
987
1298
|
sandboxPhaseLabel,
|
|
1299
|
+
sandboxSummary,
|
|
1300
|
+
seedTranscriptStore,
|
|
1301
|
+
sessionLogText,
|
|
988
1302
|
statusActivityText,
|
|
989
1303
|
summarizeToolInput,
|
|
990
1304
|
toolResultText
|