@nuvin/session 0.4.0-rc.1 → 0.4.0-rc.11

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 (44) hide show
  1. package/dist/chunk-4AOFJGGN.js +141 -0
  2. package/dist/{chunk-DTLHDYZC.js → chunk-4FXP5TSI.js} +249 -46
  3. package/dist/{chunk-X6DYNSV2.js → chunk-O3YF6Q77.js} +50 -1
  4. package/dist/client/control-center-socket.d.ts +14 -0
  5. package/dist/client/control-center-socket.d.ts.map +1 -0
  6. package/dist/client/daemon-client.d.ts.map +1 -1
  7. package/dist/client/data-client.d.ts +8 -1
  8. package/dist/client/data-client.d.ts.map +1 -1
  9. package/dist/client/http-data-client.d.ts.map +1 -1
  10. package/dist/client/index.js +270 -8
  11. package/dist/client/session-client.d.ts +8 -2
  12. package/dist/client/session-client.d.ts.map +1 -1
  13. package/dist/client/websocket.d.ts +1 -1
  14. package/dist/client/websocket.d.ts.map +1 -1
  15. package/dist/controller/agent-channel.d.ts +7 -2
  16. package/dist/controller/agent-channel.d.ts.map +1 -1
  17. package/dist/controller/index.js +469 -82
  18. package/dist/controller/session-controller.d.ts +37 -9
  19. package/dist/controller/session-controller.d.ts.map +1 -1
  20. package/dist/protocol/background-work.d.ts +3 -1
  21. package/dist/protocol/background-work.d.ts.map +1 -1
  22. package/dist/protocol/control-center.d.ts +141 -0
  23. package/dist/protocol/control-center.d.ts.map +1 -0
  24. package/dist/protocol/data-rpc.d.ts +13 -1
  25. package/dist/protocol/data-rpc.d.ts.map +1 -1
  26. package/dist/protocol/index.d.ts +2 -0
  27. package/dist/protocol/index.d.ts.map +1 -1
  28. package/dist/protocol/index.js +14 -2
  29. package/dist/protocol/submission-recovery.d.ts +6 -0
  30. package/dist/protocol/submission-recovery.d.ts.map +1 -0
  31. package/dist/protocol/types.d.ts +159 -17
  32. package/dist/protocol/types.d.ts.map +1 -1
  33. package/dist/protocol/version.d.ts +1 -1
  34. package/dist/state/approvals.d.ts.map +1 -1
  35. package/dist/state/index.js +11 -3
  36. package/dist/state/messages.d.ts +30 -2
  37. package/dist/state/messages.d.ts.map +1 -1
  38. package/dist/state/session.d.ts +4 -1
  39. package/dist/state/session.d.ts.map +1 -1
  40. package/dist/state/tool-preview.d.ts.map +1 -1
  41. package/dist/state/tool-preview.js +1 -45
  42. package/dist/test-utils/index.js +2 -2
  43. package/package.json +2 -2
  44. package/dist/chunk-5PHEC5GZ.js +0 -85
@@ -0,0 +1,141 @@
1
+ // src/protocol/control-center.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function isStringArray(value) {
6
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
7
+ }
8
+ function isScope(value) {
9
+ return isRecord(value) && ["profile", "workspace", "cwd"].every(
10
+ (key) => value[key] === void 0 || typeof value[key] === "string"
11
+ );
12
+ }
13
+ function isAgentDefinition(value) {
14
+ return isRecord(value) && ["filename", "name", "description", "systemPrompt"].every(
15
+ (key) => typeof value[key] === "string"
16
+ ) && (value.model === void 0 || typeof value.model === "string") && typeof value.enabled === "boolean" && isStringArray(value.tools) && isStringArray(value.skills);
17
+ }
18
+ function isAgentMutation(value) {
19
+ return isRecord(value) && ["sourceKey", "agentId", "expectedRevision"].every((key) => typeof value[key] === "string");
20
+ }
21
+ var noArgs = (args) => args.length === 0;
22
+ var oneString = (args) => args.length === 1 && typeof args[0] === "string";
23
+ var argumentValidators = {
24
+ getState: noArgs,
25
+ completeSlashCommand: (args) => args.length === 2 && args.every((value) => typeof value === "string"),
26
+ runSlashCommand: oneString,
27
+ setConfig: (args) => args.length === 1 && Array.isArray(args[0]) && args[0].every(
28
+ (patch) => isRecord(patch) && typeof patch.key === "string" && Object.hasOwn(patch, "value") && patch.value !== void 0 && (patch.scope === "global" || patch.scope === "local")
29
+ ),
30
+ listSkills: noArgs,
31
+ getSkill: oneString,
32
+ listAgents: noArgs,
33
+ getAgent: oneString,
34
+ getAgentEditorData: (args) => args.length === 0 || args.length === 1 && (args[0] === void 0 || typeof args[0] === "string"),
35
+ createAgent: (args) => args.length === 1 && isAgentDefinition(args[0]),
36
+ updateAgent: (args) => args.length === 1 && isAgentMutation(args[0]) && isAgentDefinition(args[0].definition),
37
+ deleteAgent: (args) => args.length === 1 && isAgentMutation(args[0]),
38
+ getCommand: oneString,
39
+ listMcpServers: noArgs,
40
+ mcpAction: (args) => args.length === 2 && ["enable", "disable", "reload", "login"].includes(args[0]) && typeof args[1] === "string",
41
+ listWorkspaceMemories: noArgs,
42
+ getWorkspaceMemory: oneString,
43
+ removeWorkspaceMemory: oneString,
44
+ listConsolidateProposals: noArgs,
45
+ getConsolidateProposal: oneString,
46
+ decideConsolidateProposal: (args) => (args.length === 2 || args.length === 3) && typeof args[0] === "string" && (args[1] === "approve" || args[1] === "reject") && (args.length === 2 || args[2] === void 0 || isStringArray(args[2])),
47
+ runConsolidate: noArgs
48
+ };
49
+ function parseControlCenterRequest(value) {
50
+ if (!isRecord(value) || !isScope(value.scope) || typeof value.operation !== "string" || !Object.hasOwn(argumentValidators, value.operation) || !Array.isArray(value.args) || !argumentValidators[value.operation](value.args)) {
51
+ throw new Error("Invalid management request.");
52
+ }
53
+ return value;
54
+ }
55
+
56
+ // src/protocol/history-text.ts
57
+ function stripTerminalStringSequences(value) {
58
+ let sanitized = "";
59
+ for (let index = 0; index < value.length; ) {
60
+ const code = value.charCodeAt(index);
61
+ const next = value.charCodeAt(index + 1);
62
+ const isSevenBitSequence = code === 27 && [80, 88, 93, 94, 95].includes(next);
63
+ const isEightBitSequence = [144, 152, 157, 158, 159].includes(code);
64
+ if (!isSevenBitSequence && !isEightBitSequence) {
65
+ sanitized += value[index];
66
+ index += 1;
67
+ continue;
68
+ }
69
+ index += isSevenBitSequence ? 2 : 1;
70
+ while (index < value.length) {
71
+ const terminator = value.charCodeAt(index);
72
+ if (terminator === 7 || terminator === 156) {
73
+ index += 1;
74
+ break;
75
+ }
76
+ if (terminator === 27 && value.charCodeAt(index + 1) === 92) {
77
+ index += 2;
78
+ break;
79
+ }
80
+ index += 1;
81
+ }
82
+ }
83
+ return sanitized;
84
+ }
85
+ function stripTerminalControlSequences(value) {
86
+ let sanitized = "";
87
+ for (let index = 0; index < value.length; ) {
88
+ const code = value.charCodeAt(index);
89
+ const isCsi = code === 155 || code === 27 && value.charCodeAt(index + 1) === 91;
90
+ if (isCsi) {
91
+ index += code === 27 ? 2 : 1;
92
+ while (index < value.length) {
93
+ const current = value.charCodeAt(index);
94
+ index += 1;
95
+ if (current >= 64 && current <= 126) break;
96
+ }
97
+ continue;
98
+ }
99
+ if (code === 27) {
100
+ index += 1;
101
+ while (index < value.length) {
102
+ const current = value.charCodeAt(index);
103
+ index += 1;
104
+ if (current < 32 || current > 47) break;
105
+ }
106
+ continue;
107
+ }
108
+ sanitized += value[index];
109
+ index += 1;
110
+ }
111
+ return sanitized;
112
+ }
113
+ function sanitizeHistoryDisplayText(value) {
114
+ return stripTerminalControlSequences(stripTerminalStringSequences(value.normalize("NFKC"))).replace(new RegExp("\\p{Cc}", "gu"), (control) => /\s/u.test(control) ? " " : "").replace(/\s+/gu, " ").trim();
115
+ }
116
+ function normalizeHistoryText(value) {
117
+ return sanitizeHistoryDisplayText(value).toLowerCase();
118
+ }
119
+ function escapeLikeLiteral(value) {
120
+ return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
121
+ }
122
+
123
+ // src/protocol/model-identity.ts
124
+ function modelNameFromIdentity(modelIdentity) {
125
+ const separator = modelIdentity.indexOf(":");
126
+ return separator === -1 ? modelIdentity : modelIdentity.slice(separator + 1);
127
+ }
128
+ function providerFromIdentity(modelIdentity) {
129
+ const separator = modelIdentity.indexOf(":");
130
+ if (separator <= 0) return void 0;
131
+ return modelIdentity.slice(0, separator);
132
+ }
133
+
134
+ export {
135
+ parseControlCenterRequest,
136
+ sanitizeHistoryDisplayText,
137
+ normalizeHistoryText,
138
+ escapeLikeLiteral,
139
+ modelNameFromIdentity,
140
+ providerFromIdentity
141
+ };
@@ -3,8 +3,6 @@ var BACKGROUNDLESS_TOOLS = /* @__PURE__ */ new Set([
3
3
  "FileRead",
4
4
  "ViewFile",
5
5
  "Ls",
6
- "Grep",
7
- "Glob",
8
6
  "LoadSkill",
9
7
  "Lsp",
10
8
  "WebFetch",
@@ -179,6 +177,16 @@ function withUpdatedMessage(state, id, update) {
179
177
  messages: state.messages.map((message) => message.id === id ? update(message) : message)
180
178
  };
181
179
  }
180
+ function withDelegatedModel(message, model) {
181
+ if (message.toolName !== "AssignTask") return message;
182
+ const prior = message.delegatedModel;
183
+ if (prior !== void 0) {
184
+ if (prior.runId !== model.runId || prior.turnNumber > model.turnNumber) return message;
185
+ if (prior.turnNumber === model.turnNumber && (prior.source === "resolution" || model.source === "assistant"))
186
+ return message;
187
+ }
188
+ return { ...message, delegatedModel: { ...model } };
189
+ }
182
190
  function addMessage(state, message) {
183
191
  return {
184
192
  ...state,
@@ -192,20 +200,33 @@ function cloneToolCallPath(toolCallPath) {
192
200
  function scopedRegistryKey(toolCallPath, id) {
193
201
  return scopedToolKey(toolCallPath, id);
194
202
  }
195
- function compactError(error) {
196
- const normalized = error.trim().replace(/\s+/g, " ");
197
- return normalized.length <= 160 ? normalized : `${normalized.slice(0, 159)}\u2026`;
198
- }
199
- function retryDelayText(delayMs) {
200
- return delayMs === 0 ? "retry now" : `retry after ${Math.max(1, Math.ceil(delayMs / 1e3))}s`;
201
- }
202
- function retryReasonText(event) {
203
- if (event.reason === "transport_error") return "Network interrupted";
204
- if (event.reason === "stream_error") return "Provider stream interrupted";
205
- return event.status === void 0 ? "Provider unavailable" : `Provider unavailable (HTTP ${event.status})`;
206
- }
207
- function formatProviderRetryText(event) {
208
- return `${retryReasonText(event)}: ${compactError(event.error)} \xB7 ${retryDelayText(event.delayMs)} \xB7 attempt ${event.attempt + 1}/${event.maxAttempts}`;
203
+ function retryReasonText(reason, status) {
204
+ if (reason === "transport_error") return "Network interrupted";
205
+ if (reason === "stream_error") return "Provider stream interrupted";
206
+ return status === void 0 ? "Provider unavailable" : `Provider unavailable (HTTP ${status})`;
207
+ }
208
+ function pluralize(count, singular, plural = `${singular}s`) {
209
+ return `${count} ${count === 1 ? singular : plural}`;
210
+ }
211
+ function formatProviderRetrySummary(detail) {
212
+ const last = detail.attempts[detail.attempts.length - 1];
213
+ if (last === void 0) return "Provider request interrupted";
214
+ const reason = retryReasonText(last.reason, last.status);
215
+ if (detail.outcome === "recovered") {
216
+ const restored = `restored after ${pluralize(detail.attempts.length, "retry", "retries")}`;
217
+ if (detail.settledAt === void 0) return `${reason} \u2014 ${restored}`;
218
+ const elapsedSeconds = Math.max(
219
+ 0,
220
+ Math.round((detail.settledAt - detail.attempts[0].occurredAt) / 1e3)
221
+ );
222
+ return `${reason} \u2014 ${restored} \xB7 ${elapsedSeconds}s`;
223
+ }
224
+ if (detail.outcome === "stopped") {
225
+ return `${reason} \u2014 gave up after ${pluralize(detail.attempts.length, "attempt")}`;
226
+ }
227
+ const nextAttempt = Math.min(last.failedAttempt + 1, last.maxAttempts);
228
+ const delay = last.delayMs === 0 ? "now" : `in ${Math.max(1, Math.ceil(last.delayMs / 1e3))}s`;
229
+ return `${reason} \u2014 retrying attempt ${nextAttempt}/${last.maxAttempts} ${delay}`;
209
230
  }
210
231
  function removeProviderStreamMessages(state, assistantMessageId, reasoningMessageId, toolCallPath) {
211
232
  const assistantKey = scopedRegistryKey(toolCallPath, assistantMessageId);
@@ -249,33 +270,39 @@ function upsertProviderRetryMessage(state, event, toolCallPath, now) {
249
270
  ...event.status !== void 0 ? { status: event.status } : {},
250
271
  occurredAt: now
251
272
  };
252
- const text = formatProviderRetryText(event);
253
273
  if (existingId) {
254
274
  return withUpdatedMessage(state, existingId, (message) => {
255
275
  if (!isProviderRetryMessage(message)) {
256
276
  return message;
257
277
  }
278
+ const detail2 = {
279
+ ...message.providerRetry,
280
+ attempts: [...message.providerRetry.attempts, attempt],
281
+ // A retry arriving for a settled row means the request recovered once
282
+ // and dropped again — the new attempt reopens the row.
283
+ outcome: "retrying",
284
+ settledAt: void 0
285
+ };
258
286
  return {
259
287
  ...message,
260
- text,
261
- providerRetry: {
262
- ...message.providerRetry,
263
- attempts: [...message.providerRetry.attempts, attempt]
264
- }
288
+ text: formatProviderRetrySummary(detail2),
289
+ providerRetry: detail2
265
290
  };
266
291
  });
267
292
  }
293
+ const detail = {
294
+ assistantMessageId: event.assistantMessageId,
295
+ attempts: [attempt],
296
+ outcome: "retrying"
297
+ };
268
298
  const id = createMessageId(state);
269
299
  const storedToolCallPath = cloneToolCallPath(toolCallPath);
270
300
  const nextState = addMessage(state, {
271
301
  id,
272
302
  role: "warning",
273
- text,
303
+ text: formatProviderRetrySummary(detail),
274
304
  ...storedToolCallPath ? { toolCallPath: storedToolCallPath } : {},
275
- providerRetry: {
276
- assistantMessageId: event.assistantMessageId,
277
- attempts: [attempt]
278
- }
305
+ providerRetry: detail
279
306
  });
280
307
  return {
281
308
  ...nextState,
@@ -285,6 +312,41 @@ function upsertProviderRetryMessage(state, event, toolCallPath, now) {
285
312
  }
286
313
  };
287
314
  }
315
+ var REASONING_MESSAGE_ID_SUFFIX = ":reasoning";
316
+ function assistantIdFromStreamMessageId(messageId) {
317
+ return messageId.endsWith(REASONING_MESSAGE_ID_SUFFIX) ? messageId.slice(0, -REASONING_MESSAGE_ID_SUFFIX.length) : messageId;
318
+ }
319
+ function markProviderRetryRecovered(state, assistantMessageId, toolCallPath, now) {
320
+ const rowId = state.providerRetryMessageIds[scopedRegistryKey(toolCallPath, assistantMessageId)];
321
+ if (rowId === void 0) return state;
322
+ return withUpdatedMessage(state, rowId, (message) => {
323
+ if (!isProviderRetryMessage(message) || message.providerRetry.outcome !== "retrying") {
324
+ return message;
325
+ }
326
+ const detail = {
327
+ ...message.providerRetry,
328
+ outcome: "recovered",
329
+ settledAt: now
330
+ };
331
+ return { ...message, text: formatProviderRetrySummary(detail), providerRetry: detail };
332
+ });
333
+ }
334
+ function settleAllProviderRetries(state, now) {
335
+ let changed = false;
336
+ const messages = state.messages.map((message) => {
337
+ if (isProviderRetryMessage(message) && message.providerRetry.outcome === "retrying") {
338
+ changed = true;
339
+ const detail = {
340
+ ...message.providerRetry,
341
+ outcome: "stopped",
342
+ settledAt: now
343
+ };
344
+ return { ...message, text: formatProviderRetrySummary(detail), providerRetry: detail };
345
+ }
346
+ return message;
347
+ });
348
+ return changed ? { ...state, messages } : state;
349
+ }
288
350
  function upsertTrackedTextMessage(state, registryKey, messageId, role, text, mode, live, toolCallPath, timing) {
289
351
  const trackingKey = scopedRegistryKey(toolCallPath, messageId);
290
352
  const existingId = state[registryKey][trackingKey];
@@ -608,13 +670,15 @@ function createMessageStateFromMessages(messages) {
608
670
  toolMessageIds
609
671
  };
610
672
  }
611
- function appendUserMessage(state, text, attachmentLabels, createdAt) {
673
+ function appendUserMessage(state, text, attachmentLabels, createdAt, steeredAt, submissionId) {
612
674
  return addMessage(state, {
613
675
  id: createMessageId(state),
614
676
  role: "user",
615
677
  text,
616
678
  ...attachmentLabels && attachmentLabels.length > 0 ? { attachmentLabels } : {},
617
- ...createdAt !== void 0 ? { createdAt } : {}
679
+ ...createdAt !== void 0 ? { createdAt } : {},
680
+ ...steeredAt !== void 0 ? { steeredAt } : {},
681
+ ...submissionId === void 0 ? {} : { submissionId }
618
682
  });
619
683
  }
620
684
  function appendErrorMessage(state, text) {
@@ -668,12 +732,18 @@ function applyAgentEvent(state, event, toolCallPath, now = Date.now()) {
668
732
  toolCallPath: nestedPath
669
733
  });
670
734
  }
671
- case "assistant_chunk":
735
+ case "assistant_chunk": {
736
+ const recovered = markProviderRetryRecovered(
737
+ state,
738
+ assistantIdFromStreamMessageId(event.messageId),
739
+ toolCallPath,
740
+ now
741
+ );
672
742
  if (!event.chunk.text) {
673
- return state;
743
+ return recovered;
674
744
  }
675
745
  return upsertTrackedTextMessage(
676
- settleLiveReasoning(state, toolCallPath, now),
746
+ settleLiveReasoning(recovered, toolCallPath, now),
677
747
  "assistantMessageIds",
678
748
  event.messageId,
679
749
  "assistant",
@@ -682,13 +752,20 @@ function applyAgentEvent(state, event, toolCallPath, now = Date.now()) {
682
752
  true,
683
753
  toolCallPath
684
754
  );
755
+ }
685
756
  case "assistant_message": {
757
+ const recovered = markProviderRetryRecovered(
758
+ state,
759
+ assistantIdFromStreamMessageId(event.message.id),
760
+ toolCallPath,
761
+ now
762
+ );
686
763
  const text = getTextFromMessage(event.message).trim();
687
764
  if (text.length === 0) {
688
- return state;
765
+ return recovered;
689
766
  }
690
767
  return upsertTrackedTextMessage(
691
- settleLiveReasoning(state, toolCallPath, now),
768
+ settleLiveReasoning(recovered, toolCallPath, now),
692
769
  "assistantMessageIds",
693
770
  event.message.id,
694
771
  "assistant",
@@ -698,12 +775,18 @@ function applyAgentEvent(state, event, toolCallPath, now = Date.now()) {
698
775
  toolCallPath
699
776
  );
700
777
  }
701
- case "reasoning_chunk":
778
+ case "reasoning_chunk": {
779
+ const recovered = markProviderRetryRecovered(
780
+ state,
781
+ assistantIdFromStreamMessageId(event.messageId),
782
+ toolCallPath,
783
+ now
784
+ );
702
785
  if (!event.text) {
703
- return state;
786
+ return recovered;
704
787
  }
705
788
  return upsertTrackedTextMessage(
706
- state,
789
+ recovered,
707
790
  "reasoningMessageIds",
708
791
  event.messageId,
709
792
  "reasoning",
@@ -713,13 +796,20 @@ function applyAgentEvent(state, event, toolCallPath, now = Date.now()) {
713
796
  toolCallPath,
714
797
  now
715
798
  );
799
+ }
716
800
  case "reasoning_message": {
801
+ const recovered = markProviderRetryRecovered(
802
+ state,
803
+ assistantIdFromStreamMessageId(event.message.id),
804
+ toolCallPath,
805
+ now
806
+ );
717
807
  const text = getReasoningTextFromMessage(event.message).trim();
718
808
  if (text.length === 0) {
719
- return state;
809
+ return recovered;
720
810
  }
721
811
  return upsertTrackedTextMessage(
722
- state,
812
+ recovered,
723
813
  "reasoningMessageIds",
724
814
  event.message.id,
725
815
  "reasoning",
@@ -731,12 +821,18 @@ function applyAgentEvent(state, event, toolCallPath, now = Date.now()) {
731
821
  );
732
822
  }
733
823
  case "final_message": {
824
+ const recovered = markProviderRetryRecovered(
825
+ state,
826
+ assistantIdFromStreamMessageId(event.message.id),
827
+ toolCallPath,
828
+ now
829
+ );
734
830
  const text = getTextFromMessage(event.message).trim();
735
831
  if (text.length === 0) {
736
- return state;
832
+ return recovered;
737
833
  }
738
834
  return upsertTrackedTextMessage(
739
- settleLiveReasoning(state, toolCallPath, now),
835
+ settleLiveReasoning(recovered, toolCallPath, now),
740
836
  "assistantMessageIds",
741
837
  event.message.id,
742
838
  "assistant",
@@ -746,6 +842,39 @@ function applyAgentEvent(state, event, toolCallPath, now = Date.now()) {
746
842
  toolCallPath
747
843
  );
748
844
  }
845
+ // Tool-use stream/message events prove the retried request resumed even
846
+ // when the response carries no assistant text — mark recovery only; tool
847
+ // rows themselves are created by the tool_call case.
848
+ case "tool_use_chunk":
849
+ return markProviderRetryRecovered(
850
+ state,
851
+ assistantIdFromStreamMessageId(event.messageId),
852
+ toolCallPath,
853
+ now
854
+ );
855
+ case "tool_use_message":
856
+ return markProviderRetryRecovered(
857
+ state,
858
+ assistantIdFromStreamMessageId(event.message.id),
859
+ toolCallPath,
860
+ now
861
+ );
862
+ case "delegated_model_resolved": {
863
+ const id = state.toolMessageIds[scopedToolKey(toolCallPath, event.parentToolCallId)];
864
+ if (id === void 0) return state;
865
+ const model = {
866
+ runId: event.runId,
867
+ turnNumber: event.turnNumber,
868
+ ...event.turnId === void 0 ? {} : { turnId: event.turnId },
869
+ modelIdentity: event.modelIdentity,
870
+ source: "resolution"
871
+ };
872
+ return withUpdatedMessage(
873
+ state,
874
+ id,
875
+ (message) => message.role === "tool" ? withDelegatedModel(message, model) : message
876
+ );
877
+ }
749
878
  case "tool_call":
750
879
  return ensureToolMessage(state, event.toolCall, toolCallPath);
751
880
  case "tool_started": {
@@ -893,6 +1022,7 @@ ${sanitizeMultilineDisplayText(split.body)}`;
893
1022
  }
894
1023
 
895
1024
  // src/state/session.ts
1025
+ import { isAgentOnlyCoordinatorNotice } from "@nuvin/agent-core/shared";
896
1026
  function cloneApproval(approval) {
897
1027
  const { toolCallPath, ...rootApproval } = approval;
898
1028
  return toolCallPath && toolCallPath.length > 0 ? { ...rootApproval, toolCallPath: [...toolCallPath] } : rootApproval;
@@ -902,7 +1032,8 @@ function cloneQuestion2(question) {
902
1032
  return toolCallPath && toolCallPath.length > 0 ? { ...rootQuestion, toolCallPath: [...toolCallPath] } : rootQuestion;
903
1033
  }
904
1034
  function cloneMessage(message) {
905
- const { toolCallPath, ...rootMessage } = message;
1035
+ const cloned = message.role === "tool" && message.delegatedModel !== void 0 ? { ...message, delegatedModel: { ...message.delegatedModel } } : message;
1036
+ const { toolCallPath, ...rootMessage } = cloned;
906
1037
  return toolCallPath && toolCallPath.length > 0 ? { ...rootMessage, toolCallPath: [...toolCallPath] } : rootMessage;
907
1038
  }
908
1039
  function cloneBackgroundWorkSummary(summary) {
@@ -917,6 +1048,8 @@ function cloneBackgroundWorkActive(active) {
917
1048
  function normalizeResetState(state) {
918
1049
  return {
919
1050
  ...state,
1051
+ recall: copyRecallState(state.recall),
1052
+ activeHumanSubmission: state.activeHumanSubmission === null ? null : structuredClone(state.activeHumanSubmission),
920
1053
  backgroundAgents: {
921
1054
  ...state.backgroundAgents,
922
1055
  runningRunIds: [...state.backgroundAgents.runningRunIds]
@@ -944,6 +1077,8 @@ function normalizeResetState(state) {
944
1077
  }
945
1078
  function createSessionViewState() {
946
1079
  return {
1080
+ recall: { identity: null, entries: [] },
1081
+ activeHumanSubmission: null,
947
1082
  approval: createApprovalQueueState(),
948
1083
  authFlow: null,
949
1084
  backgroundAgents: { running: 0, queued: 0, runningRunIds: [] },
@@ -960,6 +1095,32 @@ function createSessionViewState() {
960
1095
  }
961
1096
  function reduceSessionEvent(state, event) {
962
1097
  switch (event.type) {
1098
+ case "active-human-submission":
1099
+ return {
1100
+ ...state,
1101
+ activeHumanSubmission: event.key === null ? null : structuredClone(event.key)
1102
+ };
1103
+ case "submission-retracted": {
1104
+ const { key, rawText } = event.result;
1105
+ const identity = state.recall.identity;
1106
+ if (identity === null || identity.profile !== key.identity.profile || identity.workspace !== key.identity.workspace || identity.sessionId !== key.identity.sessionId)
1107
+ return state;
1108
+ const recall = appendRecallEntry(state.recall, { key, rawText, retracted: true });
1109
+ const removed = new Set(event.removedMessageIds);
1110
+ return {
1111
+ ...state,
1112
+ recall,
1113
+ activeHumanSubmission: state.activeHumanSubmission?.submissionId === key.submissionId ? null : state.activeHumanSubmission,
1114
+ messages: {
1115
+ ...state.messages,
1116
+ messages: state.messages.messages.filter(
1117
+ (message) => !(message.role === "user" && message.submissionId === key.submissionId && removed.has(message.id))
1118
+ )
1119
+ }
1120
+ };
1121
+ }
1122
+ case "recall-state":
1123
+ return { ...state, recall: copyRecallState(event.recall) };
963
1124
  case "agent-event": {
964
1125
  let messages = applyAgentEvent(
965
1126
  state.messages,
@@ -988,7 +1149,14 @@ function reduceSessionEvent(state, event) {
988
1149
  case "user-message":
989
1150
  return {
990
1151
  ...state,
991
- messages: appendUserMessage(state.messages, event.text, event.attachmentLabels, event.at)
1152
+ messages: appendUserMessage(
1153
+ state.messages,
1154
+ event.text,
1155
+ event.attachmentLabels,
1156
+ event.at,
1157
+ event.steeredAt,
1158
+ event.submissionId
1159
+ )
992
1160
  };
993
1161
  case "approval-requested":
994
1162
  return {
@@ -1031,7 +1199,7 @@ function reduceSessionEvent(state, event) {
1031
1199
  question: removeQuestion(state.question, event.questionId, event.toolCallPath)
1032
1200
  };
1033
1201
  case "turn-status": {
1034
- const messages = event.busy ? state.messages : settleAllLiveReasoning(state.messages, event.at);
1202
+ const messages = event.busy ? state.messages : settleAllProviderRetries(settleAllLiveReasoning(state.messages, event.at), event.at);
1035
1203
  return {
1036
1204
  ...state,
1037
1205
  busy: event.busy,
@@ -1052,6 +1220,7 @@ function reduceSessionEvent(state, event) {
1052
1220
  case "background-work-active":
1053
1221
  return { ...state, backgroundWork: cloneBackgroundWorkActive(event.active) };
1054
1222
  case "info": {
1223
+ if (isAgentOnlyCoordinatorNotice(event)) return state;
1055
1224
  const report = resolveSubagentReport(event.message, event.subagentReport);
1056
1225
  return {
1057
1226
  ...state,
@@ -1062,7 +1231,7 @@ function reduceSessionEvent(state, event) {
1062
1231
  return {
1063
1232
  ...state,
1064
1233
  messages: appendErrorMessage(
1065
- settleAllLiveReasoning(state.messages, event.at),
1234
+ settleAllProviderRetries(settleAllLiveReasoning(state.messages, event.at), event.at),
1066
1235
  event.message
1067
1236
  )
1068
1237
  };
@@ -1080,6 +1249,36 @@ function reduceSessionEvent(state, event) {
1080
1249
  return state;
1081
1250
  }
1082
1251
  }
1252
+ function appendRecallEntry(state, entry) {
1253
+ const identity = state.identity;
1254
+ if (identity === null || identity.profile !== entry.key.identity.profile || identity.workspace !== entry.key.identity.workspace || identity.sessionId !== entry.key.identity.sessionId)
1255
+ return state;
1256
+ if (typeof entry.key.submissionId !== "string" || entry.key.submissionId.trim().length === 0 || typeof entry.rawText !== "string" || typeof entry.retracted !== "boolean") {
1257
+ throw new Error("Invalid recall entry");
1258
+ }
1259
+ const index = state.entries.findIndex((item) => item.key.submissionId === entry.key.submissionId);
1260
+ const previous = state.entries[index];
1261
+ if (previous !== void 0) {
1262
+ if (previous.rawText !== entry.rawText) throw new Error("Conflicting submission raw text");
1263
+ if (previous.retracted || !entry.retracted) return state;
1264
+ const entries = [...state.entries];
1265
+ entries[index] = { ...previous, retracted: true };
1266
+ return { ...state, entries };
1267
+ }
1268
+ return { ...state, entries: [structuredClone(entry), ...state.entries] };
1269
+ }
1270
+ function copyRecallState(recall) {
1271
+ let copy = {
1272
+ identity: recall.identity === null ? null : { ...recall.identity },
1273
+ entries: []
1274
+ };
1275
+ for (const entry of [...recall.entries].reverse()) {
1276
+ if (copy.identity === null || copy.identity.profile !== entry.key.identity.profile || copy.identity.workspace !== entry.key.identity.workspace || copy.identity.sessionId !== entry.key.identity.sessionId)
1277
+ throw new Error("Recall entry identity mismatch");
1278
+ copy = appendRecallEntry(copy, entry);
1279
+ }
1280
+ return copy;
1281
+ }
1083
1282
 
1084
1283
  export {
1085
1284
  BACKGROUNDLESS_TOOLS,
@@ -1094,6 +1293,8 @@ export {
1094
1293
  asJsonObject,
1095
1294
  scopedToolKey,
1096
1295
  isProviderRetryMessage,
1296
+ withDelegatedModel,
1297
+ settleAllProviderRetries,
1097
1298
  settleAllLiveReasoning,
1098
1299
  summarizeToolCallInput,
1099
1300
  setToolMessageStatus,
@@ -1115,5 +1316,7 @@ export {
1115
1316
  splitSanitizableDelivery,
1116
1317
  sanitizeCoordinatorDeliveryText,
1117
1318
  createSessionViewState,
1118
- reduceSessionEvent
1319
+ reduceSessionEvent,
1320
+ appendRecallEntry,
1321
+ copyRecallState
1119
1322
  };