@ellipsis-dev/sdk 0.35.0 → 0.37.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.
@@ -47,6 +47,42 @@ function claudePayload(record) {
47
47
  }
48
48
 
49
49
  // src/store/codexAppServer.ts
50
+ function codexSubagentThreads(records) {
51
+ const threads = /* @__PURE__ */ new Set();
52
+ for (const record of records) addCodexSubagentThreads(threads, record);
53
+ return threads;
54
+ }
55
+ function addCodexSubagentThreads(threads, record) {
56
+ if (record.kind !== "codex_app_server") return false;
57
+ const event = record.payload;
58
+ if (event.method !== "item/started" && event.method !== "item/completed") {
59
+ return false;
60
+ }
61
+ const item = event.params.item;
62
+ if (item.type === "subAgentActivity") {
63
+ threads.add(item.agentThreadId);
64
+ return true;
65
+ }
66
+ if (item.type === "collabAgentToolCall") {
67
+ for (const thread of item.receiverThreadIds) threads.add(thread);
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+ function isCodexSubagentSpawn(record) {
73
+ if (record.kind !== "codex_app_server") return false;
74
+ const event = record.payload;
75
+ if (event.method !== "item/started" && event.method !== "item/completed") {
76
+ return false;
77
+ }
78
+ const item = event.params.item;
79
+ return item.type === "subAgentActivity" && item.kind === "started";
80
+ }
81
+ function isCodexSubagentRecord(threads, record) {
82
+ if (record.kind !== "codex_app_server") return false;
83
+ const params = record.payload.params;
84
+ return "threadId" in params && typeof params.threadId === "string" && threads.has(params.threadId);
85
+ }
50
86
  function codexAppServerContent(event) {
51
87
  if (event.method === "turn/completed") {
52
88
  return { kind: "end", isError: event.params.turn.status !== "completed" };
@@ -86,6 +122,15 @@ function codexAppServerContent(event) {
86
122
  result: item.changes.map((change) => change.diff).join("\n"),
87
123
  isError: item.status === "failed" || item.status === "declined"
88
124
  };
125
+ case "subAgentActivity":
126
+ return {
127
+ kind: "tool",
128
+ name: "Agent",
129
+ input: { agentPath: item.agentPath ?? null, kind: item.kind },
130
+ summary: item.agentPath ?? item.agentThreadId,
131
+ result: item.kind,
132
+ isError: false
133
+ };
89
134
  case "webSearch":
90
135
  return {
91
136
  kind: "tool",
@@ -101,16 +146,19 @@ function codexAppServerContent(event) {
101
146
  }
102
147
 
103
148
  // src/store/lifecycle.ts
104
- function environmentOutputStep(payload) {
149
+ function environmentOutputStep(source) {
150
+ const payload = source;
105
151
  if (typeof payload.step === "string" && payload.step) return payload.step;
106
152
  return typeof payload.phase === "string" ? payload.phase : "setup";
107
153
  }
108
- function environmentOutputLines(payload) {
154
+ function environmentOutputLines(source) {
155
+ const payload = source;
109
156
  return Array.isArray(payload.lines) ? payload.lines.filter(
110
157
  (l) => typeof l === "string" && l.trim().length > 0
111
158
  ) : [];
112
159
  }
113
- function environmentOutputLine(payload) {
160
+ function environmentOutputLine(source) {
161
+ const payload = source;
114
162
  const lines = environmentOutputLines(payload);
115
163
  return lines.length ? lines[lines.length - 1].trim() : null;
116
164
  }
@@ -170,13 +218,16 @@ function environmentPhaseText(payload) {
170
218
  return label;
171
219
  }
172
220
  }
173
- function turnEndedText(payload) {
221
+ function messageEndedText(source) {
222
+ const payload = source;
174
223
  const status = typeof payload.status === "string" ? payload.status : "";
175
224
  if (!status || status === "completed") return null;
176
- const detail = typeof payload.detail === "string" ? payload.detail.trim() : "";
177
- return detail || `Turn ${status}`;
225
+ const error = payload.error && typeof payload.error === "object" ? payload.error : {};
226
+ const detail = typeof error.detail === "string" ? error.detail.trim() : "";
227
+ return detail || `Message ${status}`;
178
228
  }
179
- function lifecycleText(recordType, payload) {
229
+ function lifecycleText(recordType, source) {
230
+ const payload = source;
180
231
  switch (recordType) {
181
232
  case "environment_phase":
182
233
  return environmentPhaseText(payload);
@@ -194,12 +245,12 @@ function lifecycleText(recordType, payload) {
194
245
  if (duration) parts.push(duration);
195
246
  return parts.join(", ");
196
247
  }
197
- case "turn_queued":
248
+ case "message_queued":
198
249
  return typeof payload.limit === "number" ? `Waiting because ${payload.limit} sessions are already alive on this account` : null;
199
- case "turn_ended":
200
- return turnEndedText(payload);
250
+ case "message_ended":
251
+ return messageEndedText(payload);
201
252
  case "session_closed":
202
- return "Conversation closed";
253
+ return "Session closed";
203
254
  default:
204
255
  return null;
205
256
  }
@@ -689,97 +740,74 @@ function geminiStreamContent(event) {
689
740
  // src/store/progress.ts
690
741
  function messageProgress(records) {
691
742
  const messages = /* @__PURE__ */ new Map();
692
- const pending = /* @__PURE__ */ new Map();
693
- const turns = /* @__PURE__ */ new Map();
743
+ const pending = /* @__PURE__ */ new Set();
694
744
  let ready = false;
695
- const getTurn = (id) => {
696
- let turn = turns.get(id);
697
- if (!turn) {
698
- turn = {
699
- timing: { id, startedAt: null, completedAt: null },
700
- phase: ready ? "working" : "starting",
701
- messages: /* @__PURE__ */ new Set()
745
+ const get = (id) => {
746
+ let message = messages.get(id);
747
+ if (!message) {
748
+ message = {
749
+ phase: "queued",
750
+ timing: { startedAt: null, completedAt: null }
702
751
  };
703
- turns.set(id, turn);
752
+ messages.set(id, message);
704
753
  }
705
- return turn;
754
+ return message;
706
755
  };
707
- const attach = (messageId, turnId) => {
708
- const message = messages.get(messageId);
709
- if (!message) return;
710
- const turn = getTurn(turnId);
711
- if (!turn.messages.has(messageId)) {
712
- turn.messages.add(messageId);
713
- message.turns.push(turn.timing);
714
- }
715
- pending.delete(messageId);
716
- message.phase = turn.phase;
717
- };
718
- const setPending = (turnId, phase) => {
719
- for (const [id, turn] of pending) {
720
- if (turnId == null || turn == null || turn === turnId)
721
- messages.get(id).phase = phase;
756
+ const setPending = (messageId, phase) => {
757
+ for (const id of pending) {
758
+ if (messageId == null || messageId === id) get(id).phase = phase;
722
759
  }
723
760
  };
724
761
  for (const record of records) {
762
+ const recordMessageId = record.message_id ?? null;
725
763
  if (record.kind === "platform") {
726
- const p = record.payload;
727
- const messageId = typeof p.message_id === "string" ? p.message_id : null;
728
- const turnId = typeof p.turn_id === "string" ? p.turn_id : record.turn_id;
764
+ const p = { ...record.payload };
765
+ const messageId = typeof p.message_id === "string" ? p.message_id : typeof p.id === "string" && record.record_type === "message" ? p.id : recordMessageId;
729
766
  switch (record.record_type) {
730
- case "message_received":
767
+ case "message":
731
768
  if (messageId && !messages.has(messageId)) {
732
- messages.set(messageId, { phase: "queued", turns: [] });
733
- if (turnId && turns.has(turnId)) attach(messageId, turnId);
734
- else pending.set(messageId, turnId ?? null);
769
+ get(messageId);
770
+ pending.add(messageId);
735
771
  }
736
772
  break;
737
- case "message_delivered":
738
- if (messageId && turnId) attach(messageId, turnId);
739
- break;
740
773
  case "message_requeued":
741
774
  if (messageId && messages.has(messageId)) {
742
- const requeued = typeof p.requeued_turn_id === "string" ? p.requeued_turn_id : null;
743
- pending.set(messageId, requeued);
744
- messages.get(messageId).phase = "queued";
775
+ pending.add(messageId);
776
+ const message = get(messageId);
777
+ message.phase = "queued";
778
+ message.timing.startedAt = null;
779
+ message.timing.completedAt = null;
745
780
  }
746
781
  break;
747
782
  case "environment_phase":
748
783
  case "environment_output":
749
784
  ready = false;
750
- setPending(turnId ?? null, "preparing");
785
+ setPending(recordMessageId, "preparing");
751
786
  break;
752
787
  case "environment_ready":
753
788
  ready = false;
754
- setPending(turnId ?? null, "starting");
789
+ setPending(recordMessageId, "starting");
755
790
  break;
756
- case "turn_started":
757
- if (turnId) {
758
- getTurn(turnId).timing.startedAt = record.created_at;
759
- for (const [id, turn] of pending) {
760
- if (turn === turnId) attach(id, turnId);
761
- }
791
+ case "message_started":
792
+ if (messageId) {
793
+ const message = get(messageId);
794
+ message.timing.startedAt = record.created_at;
795
+ message.phase = ready ? "working" : "starting";
796
+ pending.delete(messageId);
762
797
  }
763
798
  break;
764
- case "turn_ended":
765
- if (turnId) {
766
- const turn = getTurn(turnId);
767
- turn.timing.completedAt = record.created_at;
768
- turn.phase = null;
769
- for (const id of turn.messages) {
770
- if (!pending.has(id)) messages.get(id).phase = null;
771
- }
772
- for (const [id, pendingTurn] of pending) {
773
- if (pendingTurn === turnId) {
774
- pending.delete(id);
775
- messages.get(id).phase = null;
776
- }
777
- }
799
+ case "message_ended":
800
+ if (messageId) {
801
+ const message = get(messageId);
802
+ message.timing.completedAt = record.created_at;
803
+ message.phase = null;
804
+ pending.delete(messageId);
778
805
  }
779
806
  break;
780
807
  case "session_closed":
781
808
  ready = false;
782
- for (const id of pending.keys()) messages.get(id).phase = null;
809
+ for (const id of pending) get(id).phase = null;
810
+ pending.clear();
783
811
  break;
784
812
  }
785
813
  continue;
@@ -788,18 +816,15 @@ function messageProgress(records) {
788
816
  const agentStarted = record.kind === "codex_app_server" && record.payload.method === "turn/started" || record.kind === "codex" && record.payload.type === "turn.started" || record.kind === "gemini" && record.payload.type === "init" || claude != null && (claude.kind === "assistant" || claude.kind === "user" || claude.kind === "system" && claude.subtype === "init");
789
817
  if (agentStarted) {
790
818
  ready = true;
791
- if (record.turn_id) {
792
- const turn = getTurn(record.turn_id);
793
- if (turn.timing.completedAt == null) {
794
- turn.timing.startedAt ??= record.created_at;
795
- turn.phase = "working";
796
- for (const id of turn.messages) messages.get(id).phase = "working";
819
+ if (recordMessageId) {
820
+ const message = get(recordMessageId);
821
+ if (message.timing.completedAt == null) {
822
+ message.timing.startedAt ??= record.created_at;
823
+ message.phase = "working";
824
+ pending.delete(recordMessageId);
797
825
  }
798
826
  }
799
827
  }
800
- if (record.session_message_id && record.turn_id) {
801
- attach(record.session_message_id, record.turn_id);
802
- }
803
828
  }
804
829
  return messages;
805
830
  }
@@ -815,10 +840,9 @@ function groupRecordsToChatTurns(records, session) {
815
840
  const turns = [];
816
841
  const toolNodeByUseId = /* @__PURE__ */ new Map();
817
842
  const openGeminiTools = /* @__PURE__ */ new Map();
843
+ const codexSubagentThreads2 = /* @__PURE__ */ new Set();
818
844
  const userTurnByMessageId = /* @__PURE__ */ new Map();
819
- const turnByMessageId = /* @__PURE__ */ new Map();
820
- const unansweredTurnIds = /* @__PURE__ */ new Set();
821
- const activeMessageByTurnId = /* @__PURE__ */ new Map();
845
+ const unansweredMessageIds = /* @__PURE__ */ new Set();
822
846
  let lastDeliveredMessageId;
823
847
  let seq = 0;
824
848
  const nextKey = (base) => `${base}:${seq++}`;
@@ -826,15 +850,14 @@ function groupRecordsToChatTurns(records, session) {
826
850
  let initCount = 0;
827
851
  let pendingResume = false;
828
852
  const openSessionTurn = (record) => {
829
- const messageId = (record.turn_id ? activeMessageByTurnId.get(record.turn_id) : void 0) ?? lastDeliveredMessageId;
830
- if (openIdx >= 0 && (turns[openIdx].turnId !== (record.turn_id ?? void 0) || turns[openIdx].messageId !== messageId)) {
853
+ const messageId = record.message_id ?? lastDeliveredMessageId;
854
+ if (openIdx >= 0 && turns[openIdx].messageId !== messageId) {
831
855
  openIdx = -1;
832
856
  }
833
857
  if (openIdx < 0) {
834
858
  turns.push({
835
859
  key: nextKey(record.id),
836
860
  messageId,
837
- turnId: record.turn_id ?? void 0,
838
861
  role: "assistant",
839
862
  nodes: [],
840
863
  startedAt: record.created_at,
@@ -850,13 +873,26 @@ function groupRecordsToChatTurns(records, session) {
850
873
  }
851
874
  return turns[openIdx];
852
875
  };
876
+ const subagentTurn = (record) => {
877
+ for (let i = turns.length - 1; i >= 0; i--) {
878
+ const turn = turns[i];
879
+ if (turn.role === "assistant" && turn.messageId === (record.message_id ?? void 0))
880
+ return turn;
881
+ }
882
+ return null;
883
+ };
884
+ const newestAssistantTurn = () => {
885
+ for (let i = turns.length - 1; i >= 0; i--) {
886
+ if (turns[i].role === "assistant") return turns[i];
887
+ }
888
+ return null;
889
+ };
853
890
  for (const record of records) {
854
891
  if (record.kind === "platform") {
855
- const messageId = typeof record.payload.message_id === "string" ? record.payload.message_id : null;
856
- if (record.record_type === "message_received" && messageId) {
857
- if (typeof record.payload.turn_id === "string")
858
- turnByMessageId.set(messageId, record.payload.turn_id);
859
- const body = typeof record.payload.body === "string" ? record.payload.body : "";
892
+ const payload = { ...record.payload };
893
+ const messageId = typeof payload.message_id === "string" ? payload.message_id : record.record_type === "message" && typeof payload.id === "string" ? payload.id : null;
894
+ if (record.record_type === "message" && messageId) {
895
+ const body = typeof payload.body === "string" ? payload.body : "";
860
896
  if (!body.trim() || userTurnByMessageId.has(messageId)) continue;
861
897
  const turn2 = {
862
898
  resumed: false,
@@ -875,29 +911,23 @@ function groupRecordsToChatTurns(records, session) {
875
911
  turns.push(turn2);
876
912
  continue;
877
913
  }
878
- if (record.record_type === "message_delivered" && messageId) {
879
- if (typeof record.payload.turn_id === "string") {
880
- turnByMessageId.set(messageId, record.payload.turn_id);
881
- activeMessageByTurnId.set(record.payload.turn_id, messageId);
882
- lastDeliveredMessageId = messageId;
883
- }
914
+ if (record.record_type === "message_started" && messageId) {
915
+ lastDeliveredMessageId = messageId;
884
916
  continue;
885
917
  }
886
918
  if (record.record_type === "message_requeued" && messageId) {
887
- if (typeof record.payload.requeued_turn_id === "string")
888
- turnByMessageId.set(messageId, record.payload.requeued_turn_id);
889
- else turnByMessageId.delete(messageId);
919
+ unansweredMessageIds.delete(messageId);
890
920
  continue;
891
921
  }
892
- if (record.record_type === "turn_ended" && typeof record.payload.turn_id === "string" && record.payload.status !== "completed")
893
- unansweredTurnIds.add(record.payload.turn_id);
894
- const text2 = lifecycleText(record.record_type, record.payload);
922
+ if (record.record_type === "message_ended" && messageId && payload.status !== "completed")
923
+ unansweredMessageIds.add(messageId);
924
+ const text2 = lifecycleText(record.record_type, payload);
895
925
  if (!text2) continue;
896
926
  const isEnvironment = ENVIRONMENT_RECORD_TYPES.has(record.record_type);
897
927
  const isOutput = record.record_type === "environment_output";
898
- const step = isOutput ? environmentOutputStep(record.payload) : void 0;
899
- const lines = isOutput ? environmentOutputLines(record.payload) : void 0;
900
- const turnId = record.turn_id ?? void 0;
928
+ const step = isOutput ? environmentOutputStep(payload) : void 0;
929
+ const lines = isOutput ? environmentOutputLines(payload) : void 0;
930
+ const messageKey = record.message_id ?? void 0;
901
931
  let foldTarget = null;
902
932
  if (isEnvironment) {
903
933
  for (let i = turns.length - 1; i >= 0; i--) {
@@ -906,7 +936,7 @@ function groupRecordsToChatTurns(records, session) {
906
936
  if (candidate.nodes.some(
907
937
  (n) => n.kind === "lifecycle" && ENVIRONMENT_RECORD_TYPES.has(n.recordType)
908
938
  )) {
909
- if (candidate.turnId === turnId) foldTarget = candidate;
939
+ if (candidate.messageId === messageKey) foldTarget = candidate;
910
940
  break;
911
941
  }
912
942
  }
@@ -924,7 +954,7 @@ function groupRecordsToChatTurns(records, session) {
924
954
  recordType: record.record_type,
925
955
  step,
926
956
  lines,
927
- payload: record.payload
957
+ payload
928
958
  });
929
959
  }
930
960
  foldTarget.completedAt = record.created_at;
@@ -932,7 +962,7 @@ function groupRecordsToChatTurns(records, session) {
932
962
  }
933
963
  turns.push({
934
964
  key: nextKey(record.id),
935
- turnId,
965
+ messageId: messageKey,
936
966
  role: "lifecycle",
937
967
  nodes: [
938
968
  {
@@ -942,7 +972,7 @@ function groupRecordsToChatTurns(records, session) {
942
972
  recordType: record.record_type,
943
973
  step,
944
974
  lines,
945
- payload: record.payload
975
+ payload
946
976
  }
947
977
  ],
948
978
  startedAt: record.created_at,
@@ -955,11 +985,7 @@ function groupRecordsToChatTurns(records, session) {
955
985
  });
956
986
  continue;
957
987
  }
958
- if (record.session_message_id) {
959
- lastDeliveredMessageId = record.session_message_id;
960
- if (record.turn_id)
961
- activeMessageByTurnId.set(record.turn_id, record.session_message_id);
962
- }
988
+ if (record.message_id) lastDeliveredMessageId = record.message_id;
963
989
  if (record.kind === "codex") {
964
990
  codexEventIntoTurns(record, record.payload, {
965
991
  openSessionTurn,
@@ -1018,16 +1044,31 @@ function groupRecordsToChatTurns(records, session) {
1018
1044
  continue;
1019
1045
  }
1020
1046
  if (record.kind === "codex_app_server") {
1047
+ const fromSubagent = isCodexSubagentRecord(codexSubagentThreads2, record);
1048
+ const announces = addCodexSubagentThreads(codexSubagentThreads2, record);
1021
1049
  const content = codexAppServerContent(record.payload);
1022
1050
  if (!content) continue;
1051
+ const late = fromSubagent || announces && !isCodexSubagentSpawn(record);
1052
+ const turnFor = () => {
1053
+ if (!late) return openSessionTurn(record);
1054
+ const open = openIdx >= 0 ? turns[openIdx] : null;
1055
+ if (open !== null && open.messageId === (record.message_id ?? void 0)) {
1056
+ return open;
1057
+ }
1058
+ if (record.message_id == null) {
1059
+ return open ?? newestAssistantTurn() ?? openSessionTurn(record);
1060
+ }
1061
+ return subagentTurn(record) ?? openSessionTurn(record);
1062
+ };
1023
1063
  if (content.kind === "end") {
1064
+ if (fromSubagent) continue;
1024
1065
  if (openIdx >= 0) {
1025
1066
  turns[openIdx].completedAt = record.created_at;
1026
1067
  if (content.isError) turns[openIdx].isError = true;
1027
1068
  openIdx = -1;
1028
1069
  }
1029
1070
  } else if (content.kind === "tool") {
1030
- openSessionTurn(record).nodes.push({
1071
+ turnFor().nodes.push({
1031
1072
  key: nextKey(record.id),
1032
1073
  kind: "tool",
1033
1074
  name: content.name,
@@ -1039,8 +1080,8 @@ function groupRecordsToChatTurns(records, session) {
1039
1080
  completedAt: record.created_at
1040
1081
  });
1041
1082
  } else if (content.text.trim()) {
1042
- const turn2 = openSessionTurn(record);
1043
- if (content.kind === "error") turn2.isError = true;
1083
+ const turn2 = turnFor();
1084
+ if (content.kind === "error" && !fromSubagent) turn2.isError = true;
1044
1085
  turn2.nodes.push({
1045
1086
  key: nextKey(record.id),
1046
1087
  kind: content.kind === "error" ? "assistant" : content.kind,
@@ -1077,7 +1118,7 @@ function groupRecordsToChatTurns(records, session) {
1077
1118
  if (typeof content === "string") {
1078
1119
  if (content.trim()) {
1079
1120
  openIdx = -1;
1080
- if (record.session_message_id != null && userTurnByMessageId.has(record.session_message_id))
1121
+ if (record.message_id != null && userTurnByMessageId.has(record.message_id))
1081
1122
  continue;
1082
1123
  turns.push({
1083
1124
  resumed: false,
@@ -1162,27 +1203,19 @@ function groupRecordsToChatTurns(records, session) {
1162
1203
  }
1163
1204
  }
1164
1205
  }
1165
- for (const [messageId, turnId] of turnByMessageId) {
1166
- const turn = userTurnByMessageId.get(messageId);
1167
- if (turn) turn.isError = unansweredTurnIds.has(turnId);
1206
+ for (const [messageId, turn] of userTurnByMessageId) {
1207
+ turn.isError = unansweredMessageIds.has(messageId);
1168
1208
  }
1169
1209
  for (const [messageId, progress] of messageProgress(records)) {
1170
1210
  const turn = userTurnByMessageId.get(messageId);
1171
1211
  if (turn) turn.progress = progress;
1172
1212
  }
1173
- const initialTurn = records.find(
1174
- (r) => r.kind === "platform" && r.record_type === "turn_started" && r.payload.turn_index === 0
1175
- );
1176
- const openingTurnId = initialTurn?.turn_id ?? initialTurn?.payload.turn_id ?? (session?.turn?.index === 0 ? session.turn.id : void 0);
1177
- const initialReceived = openingTurnId == null ? void 0 : records.find(
1178
- (r) => r.kind === "platform" && r.record_type === "message_received" && (r.turn_id === openingTurnId || r.payload.turn_id === openingTurnId)
1213
+ const initialMessage = records.find(
1214
+ (r) => r.kind === "platform" && r.record_type === "message" && r.payload.index === 0
1179
1215
  );
1180
- const initialDelivery = openingTurnId == null ? void 0 : records.find(
1181
- (r) => r.kind === "platform" && r.record_type === "message_delivered" && r.payload.turn_id === openingTurnId
1182
- );
1183
- const initialId = (initialReceived ?? initialDelivery)?.payload.message_id;
1216
+ const initialId = initialMessage?.payload.id;
1184
1217
  const opening = initialId ? userTurnByMessageId.get(initialId) : void 0;
1185
- const includesStart = records.length === 0 || initialTurn != null;
1218
+ const includesStart = records.length === 0 || initialMessage != null;
1186
1219
  const prompt = (session?.claude_code ?? session?.codex)?.prompt;
1187
1220
  if (prompt?.trim() && session && (opening || includesStart)) {
1188
1221
  const first = opening ?? {
@@ -1196,7 +1229,10 @@ function groupRecordsToChatTurns(records, session) {
1196
1229
  tokens: null,
1197
1230
  resumed: false,
1198
1231
  isError: false,
1199
- progress: { phase: "preparing", turns: [] }
1232
+ progress: {
1233
+ phase: "preparing",
1234
+ timing: { startedAt: null, completedAt: null }
1235
+ }
1200
1236
  };
1201
1237
  if (opening) turns.splice(turns.indexOf(opening), 1);
1202
1238
  first.key = "prompt";
@@ -1204,7 +1240,7 @@ function groupRecordsToChatTurns(records, session) {
1204
1240
  turns.unshift(first);
1205
1241
  if (!opening) {
1206
1242
  for (const turn of turns) {
1207
- if (turn !== first && turn.role === "user" && turn.progress && !turn.progress.turns.length) {
1243
+ if (turn !== first && turn.role === "user" && turn.progress && turn.progress.timing.startedAt == null) {
1208
1244
  turn.progress.phase = turn.progress.phase === null ? null : "queued";
1209
1245
  }
1210
1246
  }
@@ -1318,9 +1354,9 @@ function codexEventIntoTurns(record, event, ops) {
1318
1354
  function recordSlice(records) {
1319
1355
  return records;
1320
1356
  }
1321
- function turnOf(record) {
1322
- if (record.turn_id != null) return record.turn_id;
1323
- return typeof record.payload.turn_id === "string" ? record.payload.turn_id : null;
1357
+ function messageOf(record) {
1358
+ if (record.message_id != null) return record.message_id;
1359
+ return typeof record.payload.message_id === "string" ? record.payload.message_id : null;
1324
1360
  }
1325
1361
  function humanDuration(seconds) {
1326
1362
  const clamped = Math.max(0, seconds);
@@ -1342,10 +1378,10 @@ function humanDuration(seconds) {
1342
1378
  }
1343
1379
  function sessionLogText(recordType, payload) {
1344
1380
  switch (recordType) {
1345
- case "turn_ended":
1346
- return turnEndedText(payload);
1381
+ case "message_ended":
1382
+ return messageEndedText(payload);
1347
1383
  case "session_closed":
1348
- return "Conversation closed";
1384
+ return "Session closed";
1349
1385
  default:
1350
1386
  return null;
1351
1387
  }
@@ -1359,10 +1395,10 @@ function awaitingAgentPhase(records) {
1359
1395
  continue;
1360
1396
  }
1361
1397
  switch (r.record_type) {
1362
- case "turn_started":
1398
+ case "message_started":
1363
1399
  inFlight = true;
1364
1400
  break;
1365
- case "turn_ended":
1401
+ case "message_ended":
1366
1402
  inFlight = false;
1367
1403
  break;
1368
1404
  case "environment_phase":
@@ -1377,37 +1413,34 @@ function awaitingAgentPhase(records) {
1377
1413
  }
1378
1414
  function deliveredUnechoedSends(records) {
1379
1415
  const received = /* @__PURE__ */ new Map();
1380
- const delivered = /* @__PURE__ */ new Map();
1416
+ const started = /* @__PURE__ */ new Set();
1381
1417
  const unanswered = /* @__PURE__ */ new Set();
1382
1418
  const echoed = /* @__PURE__ */ new Set();
1383
1419
  for (const r of records) {
1384
- if (r.session_message_id != null) echoed.add(r.session_message_id);
1385
- if (r.source !== "lifecycle") continue;
1386
- if (r.record_type === "turn_ended") {
1387
- if (typeof r.payload.turn_id === "string" && r.payload.status !== "completed")
1388
- unanswered.add(r.payload.turn_id);
1420
+ if (r.source !== "lifecycle") {
1421
+ if (r.message_id != null) echoed.add(r.message_id);
1389
1422
  continue;
1390
1423
  }
1391
- const id = typeof r.payload.message_id === "string" ? r.payload.message_id : null;
1424
+ const id = messageOf(r) ?? (typeof r.payload.id === "string" ? r.payload.id : null);
1392
1425
  if (!id) continue;
1393
- if (r.record_type === "message_received") {
1426
+ if (r.record_type === "message") {
1394
1427
  if (!received.has(id))
1395
1428
  received.set(
1396
1429
  id,
1397
1430
  typeof r.payload.body === "string" ? r.payload.body : ""
1398
1431
  );
1399
- } else if (r.record_type === "message_delivered") {
1400
- delivered.set(
1401
- id,
1402
- typeof r.payload.turn_id === "string" ? r.payload.turn_id : ""
1403
- );
1404
- } else if (r.record_type === "message_requeued") delivered.delete(id);
1432
+ } else if (r.record_type === "message_started") {
1433
+ started.add(id);
1434
+ } else if (r.record_type === "message_requeued") {
1435
+ started.delete(id);
1436
+ } else if (r.record_type === "message_ended") {
1437
+ if (r.payload.status !== "completed") unanswered.add(id);
1438
+ }
1405
1439
  }
1406
1440
  const out = [];
1407
1441
  for (const [id, body] of received) {
1408
- const turnId = delivered.get(id);
1409
- if (turnId === void 0 || echoed.has(id)) continue;
1410
- out.push({ id, body, cancelled: unanswered.has(turnId) });
1442
+ if (!started.has(id) || echoed.has(id)) continue;
1443
+ out.push({ id, body, cancelled: unanswered.has(id) });
1411
1444
  }
1412
1445
  return out;
1413
1446
  }
@@ -1457,22 +1490,22 @@ function deriveEnvironmentState(records, minFeedSeq) {
1457
1490
  let readySeconds = null;
1458
1491
  let log = [];
1459
1492
  let open = /* @__PURE__ */ new Map();
1460
- let storyTurn = null;
1493
+ let storyMessage = null;
1461
1494
  const push = (record, kind, text2) => {
1462
1495
  const entry = { key: `${record.feed_seq}:${log.length}`, kind, text: text2 };
1463
1496
  log.push(entry);
1464
1497
  return entry;
1465
1498
  };
1466
1499
  const begin = (record) => {
1467
- const turn = turnOf(record);
1468
- if (seen && turn !== storyTurn) {
1500
+ const message = messageOf(record);
1501
+ if (seen && message !== storyMessage) {
1469
1502
  log = [];
1470
1503
  open = /* @__PURE__ */ new Map();
1471
1504
  done = false;
1472
1505
  readySeconds = null;
1473
1506
  }
1474
1507
  seen = true;
1475
- storyTurn = turn;
1508
+ storyMessage = message;
1476
1509
  };
1477
1510
  for (const record of records) {
1478
1511
  if (record.feed_seq <= minFeedSeq || record.source !== "lifecycle")
@@ -1528,9 +1561,9 @@ function deriveEnvironmentState(records, minFeedSeq) {
1528
1561
  done = true;
1529
1562
  break;
1530
1563
  }
1531
- case "turn_started":
1532
- case "turn_ended":
1533
- if (seen && turnOf(record) === storyTurn) done = true;
1564
+ case "message_started":
1565
+ case "message_ended":
1566
+ if (seen && messageOf(record) === storyMessage) done = true;
1534
1567
  break;
1535
1568
  case "session_closed":
1536
1569
  if (seen) done = true;
@@ -1730,12 +1763,10 @@ function splitLedger(turns) {
1730
1763
  return { preamble, entries };
1731
1764
  }
1732
1765
  function entrySeconds(entry, now) {
1733
- if (entry.progress?.turns.length) {
1734
- return entry.progress.turns.reduce((ms2, turn) => {
1735
- if (turn.startedAt == null) return ms2;
1736
- const end = turn.completedAt == null ? now : Date.parse(turn.completedAt);
1737
- return ms2 + Math.max(0, end - Date.parse(turn.startedAt));
1738
- }, 0) / 1e3;
1766
+ const timing = entry.progress?.timing;
1767
+ if (timing?.startedAt != null) {
1768
+ const end = timing.completedAt == null ? now : Date.parse(timing.completedAt);
1769
+ return Math.max(0, end - Date.parse(timing.startedAt)) / 1e3;
1739
1770
  }
1740
1771
  let ms = 0;
1741
1772
  for (const turn of entry.turns) {
@@ -1811,7 +1842,7 @@ function entryBody(entry, {
1811
1842
  const items = [];
1812
1843
  const work = [];
1813
1844
  const phase = entry.progress?.phase;
1814
- const seconds = entry.progress && !entry.progress.turns.some((turn) => turn.startedAt != null) ? null : entrySeconds(entry, now);
1845
+ const seconds = entry.progress && entry.progress.timing.startedAt == null ? null : entrySeconds(entry, now);
1815
1846
  const key = `work:${entry.key}`;
1816
1847
  const header = {
1817
1848
  key,
@@ -1981,25 +2012,17 @@ function activitySummary(activities) {
1981
2012
 
1982
2013
  // src/store/index.ts
1983
2014
  function isConversationOver(session) {
1984
- return session.conversation.state === "closed";
2015
+ return session.status === "closed";
1985
2016
  }
1986
2017
  function seedTranscriptStore(store, seed) {
1987
- store.ingest({
1988
- type: "snapshot",
1989
- earliest_feed_seq: seed.earliestFeedSeq ?? null,
1990
- session: seed.session,
1991
- messages: [...seed.messages ?? []]
1992
- });
2018
+ store.ingest({ type: "session", session: seed.session });
1993
2019
  const ordered = [...seed.records].sort((a, b) => a.feed_seq - b.feed_seq);
1994
- if (ordered.length)
1995
- store.ingest({ type: "records_append", records: ordered });
2020
+ if (ordered.length) store.ingest({ type: "records", records: ordered });
1996
2021
  }
1997
2022
  var EMPTY_SNAPSHOT = {
1998
2023
  session: null,
1999
2024
  records: [],
2000
- messages: [],
2001
2025
  acknowledgedMessageIds: /* @__PURE__ */ new Set(),
2002
- historyTruncated: false,
2003
2026
  liveText: "",
2004
2027
  liveSummary: "",
2005
2028
  liveOutputTokens: null,
@@ -2013,21 +2036,16 @@ var SessionTranscriptStore = class {
2013
2036
  snapshot = EMPTY_SNAPSHOT;
2014
2037
  listeners = /* @__PURE__ */ new Set();
2015
2038
  acknowledged = /* @__PURE__ */ new Set();
2016
- // The inbox projection: message_received/delivered/requeued
2017
- // RECORDS drive it; the snapshot seeds the pending set at connect. Keyed by
2018
- // message id so a requeue can restore a message the delivered record
2019
- // removed from the pending view.
2020
- inbox = /* @__PURE__ */ new Map();
2021
- // The resume cursor: the highest feed_seq received via records_append. Only
2022
- // records advance it — never messages/session frames. streamSession
2023
- // tracks its own copy; this one seeds a NEW streamSession call (e.g. a
2024
- // page-level reconnect after unmount) from already-held state.
2039
+ // The resume cursor: the highest feed_seq received via records frames. Only
2040
+ // records advance it — never session frames. streamSession tracks its own
2041
+ // copy; this one seeds a NEW streamSession call (e.g. a page-level
2042
+ // reconnect after unmount) from already-held state.
2025
2043
  cursorSeq = 0;
2026
2044
  // Derived-turn cache, keyed on the records array identity.
2027
2045
  turnsFor = null;
2028
2046
  turnsSession = null;
2029
2047
  turnsCache = [];
2030
- summaryTurnId = null;
2048
+ summaryMessageId = null;
2031
2049
  get cursor() {
2032
2050
  return this.cursorSeq;
2033
2051
  }
@@ -2049,59 +2067,6 @@ var SessionTranscriptStore = class {
2049
2067
  }
2050
2068
  return this.turnsCache;
2051
2069
  };
2052
- // The current pending set, in feed order — what queued chips render.
2053
- pendingMessages = () => [...this.inbox.values()].filter((m) => m.status === "pending").sort((a, b) => (a.feed_seq ?? 0) - (b.feed_seq ?? 0));
2054
- // Fold one lifecycle record into the inbox projection; returns whether the
2055
- // pending view changed. Unknown fields/types are ignored.
2056
- applyMessageRecord = (record) => {
2057
- if (record.kind !== "platform") return false;
2058
- const payload = record.payload;
2059
- const messageId = typeof payload.message_id === "string" ? payload.message_id : null;
2060
- switch (record.record_type) {
2061
- case "message_received": {
2062
- if (messageId == null || this.inbox.has(messageId)) return false;
2063
- this.inbox.set(messageId, {
2064
- id: messageId,
2065
- session_id: record.session_id,
2066
- feed_seq: record.feed_seq,
2067
- status: "pending",
2068
- body: typeof payload.body === "string" ? payload.body : "",
2069
- author: typeof payload.author === "string" ? payload.author : null,
2070
- sender_attribution_type: typeof payload.sender_attribution_type === "string" ? payload.sender_attribution_type : null,
2071
- sender_attribution_id: typeof payload.sender_attribution_id === "string" ? payload.sender_attribution_id : null,
2072
- created_at: record.created_at,
2073
- delivered_at: null,
2074
- images: [],
2075
- turn_id: typeof payload.turn_id === "string" ? payload.turn_id : null
2076
- });
2077
- return true;
2078
- }
2079
- case "message_delivered": {
2080
- const existing = messageId != null ? this.inbox.get(messageId) : null;
2081
- if (!existing || existing.status !== "pending") return false;
2082
- this.inbox.set(messageId, {
2083
- ...existing,
2084
- status: "delivered",
2085
- delivered_at: record.created_at,
2086
- turn_id: typeof payload.turn_id === "string" ? payload.turn_id : existing.turn_id
2087
- });
2088
- return true;
2089
- }
2090
- case "message_requeued": {
2091
- const existing = messageId != null ? this.inbox.get(messageId) : null;
2092
- if (!existing || existing.status === "pending") return false;
2093
- this.inbox.set(messageId, {
2094
- ...existing,
2095
- status: "pending",
2096
- delivered_at: null,
2097
- turn_id: typeof payload.requeued_turn_id === "string" ? payload.requeued_turn_id : null
2098
- });
2099
- return true;
2100
- }
2101
- default:
2102
- return false;
2103
- }
2104
- };
2105
2070
  // Ingest one frame from the stream client. Unknown frame types stamp
2106
2071
  // liveness and change nothing else.
2107
2072
  ingest = (rawFrame) => {
@@ -2112,45 +2077,29 @@ var SessionTranscriptStore = class {
2112
2077
  };
2113
2078
  const frame = rawFrame;
2114
2079
  switch (frame.type) {
2115
- case "snapshot": {
2116
- next.liveSummary = "";
2117
- next.session = frame.session;
2118
- for (const message of frame.messages) {
2119
- this.inbox.set(message.id, message);
2120
- this.acknowledged.add(message.id);
2121
- }
2122
- next.messages = this.pendingMessages();
2123
- next.acknowledgedMessageIds = new Set(this.acknowledged);
2124
- next.conversationOver = isConversationOver(frame.session);
2125
- next.historyTruncated = frame.earliest_feed_seq != null && this.cursorSeq < frame.earliest_feed_seq - 1;
2126
- break;
2127
- }
2128
- case "records_append": {
2080
+ case "records": {
2129
2081
  const fresh = frame.records.filter((r) => r.feed_seq > this.cursorSeq);
2130
2082
  if (fresh.length === 0) break;
2131
2083
  this.cursorSeq = fresh[fresh.length - 1].feed_seq;
2132
2084
  next.records = [...prev.records, ...fresh];
2133
2085
  let acknowledgedNew = false;
2134
- let inboxChanged = false;
2135
2086
  for (const record of fresh) {
2136
- if (record.session_message_id != null) {
2137
- this.acknowledged.add(record.session_message_id);
2087
+ if (record.message_id != null) {
2088
+ this.acknowledged.add(record.message_id);
2138
2089
  acknowledgedNew = true;
2139
2090
  }
2140
2091
  if (record.source === "lifecycle") {
2141
- if (record.record_type === "turn_started" && record.turn_id !== this.summaryTurnId || record.record_type === "turn_ended" && record.turn_id === this.summaryTurnId)
2092
+ if (record.record_type === "message_started" && record.message_id !== this.summaryMessageId || record.record_type === "message_ended" && record.message_id === this.summaryMessageId)
2142
2093
  next.liveSummary = "";
2143
- inboxChanged = this.applyMessageRecord(record) || inboxChanged;
2144
- if (record.record_type === "message_received" || record.record_type === "message_delivered") {
2094
+ if (record.record_type === "message") {
2145
2095
  const payload = record.payload;
2146
- if (typeof payload.message_id === "string") {
2147
- this.acknowledged.add(payload.message_id);
2096
+ if (typeof payload.id === "string") {
2097
+ this.acknowledged.add(payload.id);
2148
2098
  acknowledgedNew = true;
2149
2099
  }
2150
2100
  }
2151
2101
  }
2152
2102
  }
2153
- if (inboxChanged) next.messages = this.pendingMessages();
2154
2103
  if (acknowledgedNew) {
2155
2104
  next.acknowledgedMessageIds = new Set(this.acknowledged);
2156
2105
  }
@@ -2161,15 +2110,13 @@ var SessionTranscriptStore = class {
2161
2110
  case "session": {
2162
2111
  next.session = frame.session;
2163
2112
  next.conversationOver = isConversationOver(frame.session);
2164
- if (next.conversationOver || frame.session.turn?.status !== "running") {
2165
- next.liveSummary = "";
2166
- }
2113
+ if (frame.session.status !== "running") next.liveSummary = "";
2167
2114
  break;
2168
2115
  }
2169
2116
  case "delta": {
2170
2117
  if (frame.kind === "thinking") {
2171
2118
  if (next.conversationOver) break;
2172
- this.summaryTurnId = frame.turn_id;
2119
+ this.summaryMessageId = frame.message_id;
2173
2120
  if (frame.text != null) next.liveSummary = frame.text;
2174
2121
  break;
2175
2122
  }
@@ -2182,11 +2129,6 @@ var SessionTranscriptStore = class {
2182
2129
  next.liveOutputTokens = frame.output_tokens;
2183
2130
  break;
2184
2131
  }
2185
- case "done": {
2186
- next.conversationOver = true;
2187
- next.liveSummary = "";
2188
- break;
2189
- }
2190
2132
  case "error": {
2191
2133
  next.liveSummary = "";
2192
2134
  break;
@@ -2212,6 +2154,7 @@ export {
2212
2154
  codexChangedPaths,
2213
2155
  codexEventToItems,
2214
2156
  codexMcpToolName,
2157
+ codexSubagentThreads,
2215
2158
  collapseToolRuns,
2216
2159
  deliveredUnechoedSends,
2217
2160
  deriveEnvironmentState,
@@ -2232,6 +2175,7 @@ export {
2232
2175
  gutterFor,
2233
2176
  hookPhrase,
2234
2177
  humanDuration,
2178
+ isCodexSubagentRecord,
2235
2179
  isConnectVisibleRecord,
2236
2180
  isConversationOver,
2237
2181
  isFoldable,
@@ -2241,6 +2185,7 @@ export {
2241
2185
  lastLines,
2242
2186
  layOutItems,
2243
2187
  lifecycleText,
2188
+ messageEndedText,
2244
2189
  oneLine,
2245
2190
  pendingToolCalls,
2246
2191
  reasoningSummaryTitle,
@@ -2257,6 +2202,5 @@ export {
2257
2202
  tailLines,
2258
2203
  toolActivities,
2259
2204
  toolResultText,
2260
- toolRunMembers,
2261
- turnEndedText
2205
+ toolRunMembers
2262
2206
  };