@nuvin/session 0.1.0-rc.5

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 (72) hide show
  1. package/dist/chunk-3IPZO7LP.js +66 -0
  2. package/dist/chunk-7G3R25NS.js +6 -0
  3. package/dist/chunk-DGC7JSCG.js +0 -0
  4. package/dist/chunk-FR3R66RD.js +678 -0
  5. package/dist/chunk-UVJIG4DT.js +17 -0
  6. package/dist/client/directory.d.ts +141 -0
  7. package/dist/client/directory.d.ts.map +1 -0
  8. package/dist/client/endpoint.d.ts +33 -0
  9. package/dist/client/endpoint.d.ts.map +1 -0
  10. package/dist/client/index.d.ts +8 -0
  11. package/dist/client/index.d.ts.map +1 -0
  12. package/dist/client/index.js +1060 -0
  13. package/dist/client/session-client.d.ts +60 -0
  14. package/dist/client/session-client.d.ts.map +1 -0
  15. package/dist/client/socket.d.ts +38 -0
  16. package/dist/client/socket.d.ts.map +1 -0
  17. package/dist/client/transport.d.ts +9 -0
  18. package/dist/client/transport.d.ts.map +1 -0
  19. package/dist/client/uds-socket.d.ts +23 -0
  20. package/dist/client/uds-socket.d.ts.map +1 -0
  21. package/dist/client/websocket.d.ts +54 -0
  22. package/dist/client/websocket.d.ts.map +1 -0
  23. package/dist/controller/agent-channel.d.ts +58 -0
  24. package/dist/controller/agent-channel.d.ts.map +1 -0
  25. package/dist/controller/index.d.ts +3 -0
  26. package/dist/controller/index.d.ts.map +1 -0
  27. package/dist/controller/index.js +441 -0
  28. package/dist/controller/session-controller.d.ts +147 -0
  29. package/dist/controller/session-controller.d.ts.map +1 -0
  30. package/dist/controller/test-utils.d.ts +15 -0
  31. package/dist/controller/test-utils.d.ts.map +1 -0
  32. package/dist/grant/index.d.ts +33 -0
  33. package/dist/grant/index.d.ts.map +1 -0
  34. package/dist/grant/index.js +12 -0
  35. package/dist/protocol/index.d.ts +3 -0
  36. package/dist/protocol/index.d.ts.map +1 -0
  37. package/dist/protocol/index.js +7 -0
  38. package/dist/protocol/types.d.ts +855 -0
  39. package/dist/protocol/types.d.ts.map +1 -0
  40. package/dist/protocol/version.d.ts +8 -0
  41. package/dist/protocol/version.d.ts.map +1 -0
  42. package/dist/state/approvals.d.ts +34 -0
  43. package/dist/state/approvals.d.ts.map +1 -0
  44. package/dist/state/dir-access.d.ts +9 -0
  45. package/dist/state/dir-access.d.ts.map +1 -0
  46. package/dist/state/index.d.ts +6 -0
  47. package/dist/state/index.d.ts.map +1 -0
  48. package/dist/state/index.js +48 -0
  49. package/dist/state/json.d.ts +7 -0
  50. package/dist/state/json.d.ts.map +1 -0
  51. package/dist/state/messages.d.ts +105 -0
  52. package/dist/state/messages.d.ts.map +1 -0
  53. package/dist/state/session.d.ts +9 -0
  54. package/dist/state/session.d.ts.map +1 -0
  55. package/dist/state/tool-preview.d.ts +31 -0
  56. package/dist/state/tool-preview.d.ts.map +1 -0
  57. package/dist/state/tool-preview.js +300 -0
  58. package/dist/state/workflow-view.d.ts +10 -0
  59. package/dist/state/workflow-view.d.ts.map +1 -0
  60. package/dist/test-utils/fake-relay.d.ts +24 -0
  61. package/dist/test-utils/fake-relay.d.ts.map +1 -0
  62. package/dist/test-utils/index.d.ts +2 -0
  63. package/dist/test-utils/index.d.ts.map +1 -0
  64. package/dist/test-utils/index.js +250 -0
  65. package/dist/ui/history-grouping.d.ts +36 -0
  66. package/dist/ui/history-grouping.d.ts.map +1 -0
  67. package/dist/ui/index.d.ts +3 -0
  68. package/dist/ui/index.d.ts.map +1 -0
  69. package/dist/ui/index.js +135 -0
  70. package/dist/ui/picker-rows.d.ts +62 -0
  71. package/dist/ui/picker-rows.d.ts.map +1 -0
  72. package/package.json +70 -0
@@ -0,0 +1,678 @@
1
+ // src/state/approvals.ts
2
+ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
3
+ "FileRead",
4
+ "ViewFile",
5
+ "Ls",
6
+ "Grep",
7
+ "Glob",
8
+ "LoadSkill",
9
+ "Lsp",
10
+ "WebFetch",
11
+ "WebSearch"
12
+ ]);
13
+ var BACKGROUNDLESS_TOOLS = /* @__PURE__ */ new Set([...READ_ONLY_TOOLS, "FileNew"]);
14
+ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set(["AskUserTool", "TodoWrite", "UpdateTopic"]);
15
+ var AUTO_APPROVED_TOOLS = /* @__PURE__ */ new Set([...READ_ONLY_TOOLS, ...INTERACTIVE_TOOLS]);
16
+ function isAutoApprovedTool(toolName) {
17
+ return AUTO_APPROVED_TOOLS.has(toolName);
18
+ }
19
+ function createApprovalQueueState() {
20
+ return { active: null, pending: [] };
21
+ }
22
+ function enqueueApproval(state, approval) {
23
+ if (!state.active) {
24
+ return { ...state, active: approval };
25
+ }
26
+ return { ...state, pending: [...state.pending, approval] };
27
+ }
28
+ function dequeueActiveApproval(state) {
29
+ const [nextApproval, ...remainingApprovals] = state.pending;
30
+ return { active: nextApproval ?? null, pending: remainingApprovals };
31
+ }
32
+ function removeApproval(state, matches) {
33
+ if (state.active && matches(state.active)) {
34
+ return dequeueActiveApproval(state);
35
+ }
36
+ const pending = state.pending.filter((item) => !matches(item));
37
+ return pending.length === state.pending.length ? state : { ...state, pending };
38
+ }
39
+
40
+ // src/state/json.ts
41
+ function asJsonObject(value) {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
43
+ }
44
+
45
+ // src/state/messages.ts
46
+ import { getReasoningTextFromMessage, getTextFromMessage } from "@nuvin/agent-core/formats";
47
+ function createMessageId(state) {
48
+ return `message-${state.nextId}`;
49
+ }
50
+ function withUpdatedMessage(state, id, update) {
51
+ return {
52
+ ...state,
53
+ messages: state.messages.map((message) => message.id === id ? update(message) : message)
54
+ };
55
+ }
56
+ function addMessage(state, message) {
57
+ return {
58
+ ...state,
59
+ messages: [...state.messages, message],
60
+ nextId: state.nextId + 1
61
+ };
62
+ }
63
+ function scopedRegistryKey(scope, id) {
64
+ return scope ? `${scope}::${id}` : id;
65
+ }
66
+ function upsertTrackedTextMessage(state, registryKey, messageId, role, text, mode, live, parentToolCallId, timing) {
67
+ const trackingKey = scopedRegistryKey(parentToolCallId, messageId);
68
+ const existingId = state[registryKey][trackingKey];
69
+ if (existingId) {
70
+ return withUpdatedMessage(state, existingId, (message) => {
71
+ if (message.role !== role) {
72
+ return message;
73
+ }
74
+ const updated = {
75
+ ...message,
76
+ live,
77
+ text: mode === "append" ? `${message.text}${text}` : text
78
+ };
79
+ if (timing !== void 0 && !live && updated.completedAt === void 0) {
80
+ updated.completedAt = timing;
81
+ }
82
+ return updated;
83
+ });
84
+ }
85
+ const id = createMessageId(state);
86
+ const nextState = addMessage(state, {
87
+ id,
88
+ role,
89
+ text,
90
+ live,
91
+ ...parentToolCallId ? { parentToolCallId } : {},
92
+ ...timing !== void 0 ? { startedAt: timing } : {},
93
+ // A reasoning message that arrives already-settled stamps completedAt too.
94
+ ...timing !== void 0 && !live ? { completedAt: timing } : {}
95
+ });
96
+ return {
97
+ ...nextState,
98
+ [registryKey]: {
99
+ ...nextState[registryKey],
100
+ [trackingKey]: id
101
+ }
102
+ };
103
+ }
104
+ function settleLiveReasoning(state, parentToolCallId, now) {
105
+ let changed = false;
106
+ const messages = state.messages.map((message) => {
107
+ if (message.role === "reasoning" && message.live && message.parentToolCallId === parentToolCallId) {
108
+ changed = true;
109
+ return { ...message, live: false, completedAt: message.completedAt ?? now };
110
+ }
111
+ return message;
112
+ });
113
+ return changed ? { ...state, messages } : state;
114
+ }
115
+ function summarizeToolInput(input) {
116
+ const objectInput = asJsonObject(input);
117
+ if (!objectInput) {
118
+ return JSON.stringify(input);
119
+ }
120
+ const command = objectInput.command;
121
+ if (typeof command === "string") {
122
+ return command;
123
+ }
124
+ const entries = Object.entries(objectInput);
125
+ if (entries.length === 1 && typeof entries[0]?.[1] === "string") {
126
+ return String(entries[0][1]);
127
+ }
128
+ return JSON.stringify(objectInput);
129
+ }
130
+ var BASH_STREAMING_PREVIEW_LINES = 5;
131
+ var STREAM_TAIL_BUDGET = 64 * 1024;
132
+ function countNewlines(text) {
133
+ let count = 0;
134
+ for (let index = 0; index < text.length; index++) {
135
+ if (text.charCodeAt(index) === 10) count++;
136
+ }
137
+ return count;
138
+ }
139
+ function capTail(text) {
140
+ return text.length > STREAM_TAIL_BUDGET ? text.slice(-STREAM_TAIL_BUDGET) : text;
141
+ }
142
+ function trailingWhitespaceLength(text) {
143
+ const match = /\s*$/.exec(text);
144
+ return match ? match[0].length : 0;
145
+ }
146
+ function seedStreamAcc(priorText) {
147
+ const trailLen = trailingWhitespaceLength(priorText);
148
+ const contentEnd = priorText.length - trailLen;
149
+ const trailingWs = priorText.slice(contentEnd);
150
+ return {
151
+ rawNewlines: countNewlines(priorText),
152
+ contentTail: capTail(priorText.slice(0, contentEnd)),
153
+ trailingWs: capTail(trailingWs),
154
+ trailingWsNewlines: countNewlines(trailingWs)
155
+ };
156
+ }
157
+ function advanceStreamAcc(acc, chunk) {
158
+ const rawNewlines = acc.rawNewlines + countNewlines(chunk);
159
+ const chunkTrailLen = trailingWhitespaceLength(chunk);
160
+ const chunkContentEnd = chunk.length - chunkTrailLen;
161
+ if (chunkContentEnd === 0) {
162
+ return {
163
+ rawNewlines,
164
+ contentTail: acc.contentTail,
165
+ trailingWs: capTail(acc.trailingWs + chunk),
166
+ trailingWsNewlines: acc.trailingWsNewlines + countNewlines(chunk)
167
+ };
168
+ }
169
+ const chunkTrailingWs = chunk.slice(chunkContentEnd);
170
+ return {
171
+ rawNewlines,
172
+ contentTail: capTail(acc.contentTail + acc.trailingWs + chunk.slice(0, chunkContentEnd)),
173
+ trailingWs: chunkTrailingWs,
174
+ trailingWsNewlines: countNewlines(chunkTrailingWs)
175
+ };
176
+ }
177
+ function previewFromStreamAcc(acc) {
178
+ if (acc.contentTail.length === 0) return void 0;
179
+ const totalLines = acc.rawNewlines - acc.trailingWsNewlines + 1;
180
+ let start = acc.contentTail.length;
181
+ let newlinesSeen = 0;
182
+ while (start > 0) {
183
+ if (acc.contentTail.charCodeAt(start - 1) === 10) {
184
+ newlinesSeen++;
185
+ if (newlinesSeen === BASH_STREAMING_PREVIEW_LINES) break;
186
+ }
187
+ start--;
188
+ }
189
+ return { text: acc.contentTail.slice(start), totalLines, acc };
190
+ }
191
+ function advanceStreamingPreview(priorText, chunk, previous) {
192
+ const acc = previous?.acc ?? seedStreamAcc(priorText);
193
+ return previewFromStreamAcc(advanceStreamAcc(acc, chunk));
194
+ }
195
+ function mergeToolResultText(currentText, result) {
196
+ if (currentText.length === 0) {
197
+ return result.output;
198
+ }
199
+ if (result.output.length === 0 || currentText === result.output) {
200
+ return currentText;
201
+ }
202
+ if (result.output.startsWith(currentText)) {
203
+ return result.output;
204
+ }
205
+ return currentText;
206
+ }
207
+ function ensureToolMessage(state, toolCall, parentToolCallId) {
208
+ const existingId = state.toolMessageIds[toolCall.id];
209
+ if (existingId) {
210
+ return withUpdatedMessage(state, existingId, (message) => {
211
+ if (message.role !== "tool" || message.input !== void 0) {
212
+ return message;
213
+ }
214
+ return {
215
+ ...message,
216
+ input: asJsonObject(toolCall.input)
217
+ };
218
+ });
219
+ }
220
+ const id = createMessageId(state);
221
+ const nextState = addMessage(state, {
222
+ id,
223
+ role: "tool",
224
+ toolName: toolCall.name,
225
+ toolCallId: toolCall.id,
226
+ summary: summarizeToolInput(toolCall.input),
227
+ text: "",
228
+ status: "pending",
229
+ input: asJsonObject(toolCall.input),
230
+ ...parentToolCallId ? { parentToolCallId } : {}
231
+ });
232
+ return {
233
+ ...nextState,
234
+ toolMessageIds: {
235
+ ...nextState.toolMessageIds,
236
+ [toolCall.id]: id
237
+ }
238
+ };
239
+ }
240
+ function setToolMessageStatus(state, toolCallId, status) {
241
+ const toolMessageId = state.toolMessageIds[toolCallId];
242
+ if (!toolMessageId) {
243
+ return state;
244
+ }
245
+ return withUpdatedMessage(state, toolMessageId, (message) => {
246
+ if (message.role !== "tool") {
247
+ return message;
248
+ }
249
+ return {
250
+ ...message,
251
+ status
252
+ };
253
+ });
254
+ }
255
+ function setToolMessageHidden(state, toolCallId, hidden) {
256
+ const toolMessageId = state.toolMessageIds[toolCallId];
257
+ if (!toolMessageId) {
258
+ return state;
259
+ }
260
+ return withUpdatedMessage(state, toolMessageId, (message) => {
261
+ if (message.role !== "tool") {
262
+ return message;
263
+ }
264
+ return {
265
+ ...message,
266
+ hidden
267
+ };
268
+ });
269
+ }
270
+ function applyToolResult(state, result, now) {
271
+ const toolMessageId = state.toolMessageIds[result.callId];
272
+ if (!toolMessageId) {
273
+ const id = createMessageId(state);
274
+ const next = addMessage(state, {
275
+ id,
276
+ completedAt: now,
277
+ role: "tool",
278
+ toolName: result.toolName,
279
+ toolCallId: result.callId,
280
+ status: result.status,
281
+ summary: "",
282
+ text: result.output,
283
+ structured: result.structured
284
+ });
285
+ return {
286
+ ...next,
287
+ toolMessageIds: {
288
+ ...next.toolMessageIds,
289
+ [result.callId]: id
290
+ }
291
+ };
292
+ }
293
+ return withUpdatedMessage(state, toolMessageId, (message) => {
294
+ if (message.role !== "tool") {
295
+ return message;
296
+ }
297
+ return {
298
+ ...message,
299
+ completedAt: message.completedAt ?? now,
300
+ status: result.status,
301
+ text: mergeToolResultText(message.text, result),
302
+ structured: result.structured,
303
+ hidden: false,
304
+ streamingPreview: void 0
305
+ };
306
+ });
307
+ }
308
+ function createMessageState() {
309
+ return {
310
+ assistantMessageIds: {},
311
+ messages: [],
312
+ nextId: 1,
313
+ reasoningMessageIds: {},
314
+ toolMessageIds: {}
315
+ };
316
+ }
317
+ function createMessageStateFromMessages(messages) {
318
+ const nextId = messages.reduce((max, message) => {
319
+ const match = /^message-(\d+)$/.exec(message.id);
320
+ if (!match) return max;
321
+ return Math.max(max, Number(match[1]) + 1);
322
+ }, 1) + messages.length;
323
+ const toolMessageIds = {};
324
+ for (const message of messages) {
325
+ if (message.role === "tool") {
326
+ toolMessageIds[message.toolCallId] = message.id;
327
+ }
328
+ }
329
+ return {
330
+ assistantMessageIds: {},
331
+ messages,
332
+ nextId,
333
+ reasoningMessageIds: {},
334
+ toolMessageIds
335
+ };
336
+ }
337
+ function appendUserMessage(state, text, attachmentLabels, createdAt) {
338
+ return addMessage(state, {
339
+ id: createMessageId(state),
340
+ role: "user",
341
+ text,
342
+ ...attachmentLabels && attachmentLabels.length > 0 ? { attachmentLabels } : {},
343
+ ...createdAt !== void 0 ? { createdAt } : {}
344
+ });
345
+ }
346
+ function appendErrorMessage(state, text) {
347
+ return addMessage(state, {
348
+ id: createMessageId(state),
349
+ role: "error",
350
+ text
351
+ });
352
+ }
353
+ function appendInfoMessage(state, text) {
354
+ return addMessage(state, {
355
+ id: createMessageId(state),
356
+ role: "info",
357
+ text
358
+ });
359
+ }
360
+ function appendWarningMessage(state, text) {
361
+ return addMessage(state, {
362
+ id: createMessageId(state),
363
+ role: "warning",
364
+ text
365
+ });
366
+ }
367
+ function applyAgentEvent(state, event, parentToolCallId, now = Date.now()) {
368
+ switch (event.type) {
369
+ case "assistant_chunk":
370
+ if (!event.chunk.text) {
371
+ return state;
372
+ }
373
+ return upsertTrackedTextMessage(
374
+ settleLiveReasoning(state, parentToolCallId, now),
375
+ "assistantMessageIds",
376
+ event.messageId,
377
+ "assistant",
378
+ event.chunk.text,
379
+ "append",
380
+ true,
381
+ parentToolCallId
382
+ );
383
+ case "assistant_message": {
384
+ const text = getTextFromMessage(event.message).trim();
385
+ if (text.length === 0) {
386
+ return state;
387
+ }
388
+ return upsertTrackedTextMessage(
389
+ settleLiveReasoning(state, parentToolCallId, now),
390
+ "assistantMessageIds",
391
+ event.message.id,
392
+ "assistant",
393
+ text,
394
+ "replace",
395
+ false,
396
+ parentToolCallId
397
+ );
398
+ }
399
+ case "reasoning_chunk":
400
+ if (!event.text) {
401
+ return state;
402
+ }
403
+ return upsertTrackedTextMessage(
404
+ state,
405
+ "reasoningMessageIds",
406
+ event.messageId,
407
+ "reasoning",
408
+ event.text,
409
+ "append",
410
+ true,
411
+ parentToolCallId,
412
+ now
413
+ );
414
+ case "reasoning_message": {
415
+ const text = getReasoningTextFromMessage(event.message).trim();
416
+ if (text.length === 0) {
417
+ return state;
418
+ }
419
+ return upsertTrackedTextMessage(
420
+ state,
421
+ "reasoningMessageIds",
422
+ event.message.id,
423
+ "reasoning",
424
+ text,
425
+ "replace",
426
+ false,
427
+ parentToolCallId,
428
+ now
429
+ );
430
+ }
431
+ case "final_message": {
432
+ const text = getTextFromMessage(event.message).trim();
433
+ if (text.length === 0) {
434
+ return state;
435
+ }
436
+ return upsertTrackedTextMessage(
437
+ settleLiveReasoning(state, parentToolCallId, now),
438
+ "assistantMessageIds",
439
+ event.message.id,
440
+ "assistant",
441
+ text,
442
+ "replace",
443
+ false,
444
+ parentToolCallId
445
+ );
446
+ }
447
+ case "tool_call":
448
+ return ensureToolMessage(state, event.toolCall, parentToolCallId);
449
+ case "tool_started": {
450
+ const toolState = ensureToolMessage(state, event.toolCall, parentToolCallId);
451
+ const toolMessageId = toolState.toolMessageIds[event.toolCall.id];
452
+ return withUpdatedMessage(toolState, toolMessageId, (message) => {
453
+ if (message.role !== "tool") {
454
+ return message;
455
+ }
456
+ return {
457
+ ...message,
458
+ startedAt: message.startedAt ?? now,
459
+ status: "running"
460
+ };
461
+ });
462
+ }
463
+ case "tool_output_chunk": {
464
+ const toolState = ensureToolMessage(state, event.toolCall, parentToolCallId);
465
+ const toolMessageId = toolState.toolMessageIds[event.toolCall.id];
466
+ return withUpdatedMessage(toolState, toolMessageId, (message) => {
467
+ if (message.role !== "tool") {
468
+ return message;
469
+ }
470
+ const nextText = `${message.text}${event.chunk.output}`;
471
+ return {
472
+ ...message,
473
+ text: nextText,
474
+ streamingPreview: message.toolName === "Bash" ? (
475
+ // Pass the PRIOR text (not the concatenation): the builder works
476
+ // from a bounded accumulator and only reads prior text on reseed,
477
+ // so `message.text` is never indexed and never flattened here.
478
+ advanceStreamingPreview(message.text, event.chunk.output, message.streamingPreview)
479
+ ) : message.streamingPreview
480
+ };
481
+ });
482
+ }
483
+ case "tool_completed": {
484
+ const toolState = ensureToolMessage(state, event.toolCall, parentToolCallId);
485
+ return applyToolResult(toolState, event.result, now);
486
+ }
487
+ case "tool_result":
488
+ return applyToolResult(state, event.result, now);
489
+ case "tool_rejected": {
490
+ const toolState = ensureToolMessage(state, event.toolCall, parentToolCallId);
491
+ const toolMessageId = toolState.toolMessageIds[event.toolCall.id];
492
+ return withUpdatedMessage(toolState, toolMessageId, (message) => {
493
+ if (message.role !== "tool") {
494
+ return message;
495
+ }
496
+ return {
497
+ ...message,
498
+ completedAt: message.completedAt ?? now,
499
+ status: "rejected",
500
+ text: event.result.output,
501
+ structured: event.result.structured
502
+ };
503
+ });
504
+ }
505
+ case "provider_retry": {
506
+ const delaySeconds = Math.max(1, Math.ceil(event.delayMs / 1e3));
507
+ const delayText = event.delayMs > 0 ? `in ${delaySeconds}s` : "now";
508
+ return appendWarningMessage(
509
+ state,
510
+ `Provider request failed, retrying ${delayText} (${event.attempt}/${event.maxAttempts})\u2026`
511
+ );
512
+ }
513
+ default:
514
+ return state;
515
+ }
516
+ }
517
+
518
+ // src/state/workflow-view.ts
519
+ function createWorkflowViewState(runId) {
520
+ return { runId, status: "running", tree: [], counts: {}, tokens: 0, lastSeq: -1, nodeKeys: {} };
521
+ }
522
+ function nodeIdentity(delta) {
523
+ return `${delta.nodeId}\0${delta.instanceKey ?? ""}\0${delta.roundKey ?? ""}`;
524
+ }
525
+ function coarseStatus(status) {
526
+ if (status === "failed") return "failed";
527
+ if (status === "skipped") return "skipped";
528
+ if (status === "aborted") return "stopped";
529
+ return "done";
530
+ }
531
+ function setStatus(tree, phase, title, status) {
532
+ const phaseIdx = tree.findIndex((p) => p.title === phase);
533
+ if (phaseIdx === -1) return [...tree, { title: phase, children: [{ title, status }] }];
534
+ const phaseNode = tree[phaseIdx];
535
+ const children = phaseNode.children ?? [];
536
+ const childIdx = children.findIndex((c) => c.title === title);
537
+ const nextChildren = childIdx === -1 ? [...children, { title, status }] : children.map((c, i) => i === childIdx ? { ...c, status } : c);
538
+ return tree.map((p, i) => i === phaseIdx ? { ...phaseNode, children: nextChildren } : p);
539
+ }
540
+ function foldWorkflowDelta(state, delta) {
541
+ if (delta.seq <= state.lastSeq) return state;
542
+ const base = { ...state, lastSeq: delta.seq };
543
+ switch (delta.type) {
544
+ case "node-started": {
545
+ if (delta.kind === "source" || delta.kind === "sink") return base;
546
+ const phase = delta.phase ?? "";
547
+ const title = delta.title ?? delta.nodeId;
548
+ return {
549
+ ...base,
550
+ currentPhase: phase,
551
+ counts: { ...base.counts, [delta.kind]: (base.counts[delta.kind] ?? 0) + 1 },
552
+ nodeKeys: { ...base.nodeKeys, [nodeIdentity(delta)]: [phase, title] },
553
+ tree: setStatus(base.tree, phase, title, "running")
554
+ };
555
+ }
556
+ case "node-finished": {
557
+ const tokens = base.tokens + (delta.tokens ?? 0);
558
+ const key = base.nodeKeys[nodeIdentity(delta)];
559
+ if (!key) return { ...base, tokens };
560
+ return {
561
+ ...base,
562
+ tokens,
563
+ tree: setStatus(base.tree, key[0], key[1], coarseStatus(delta.status))
564
+ };
565
+ }
566
+ case "run-finished":
567
+ return { ...base, status: delta.status };
568
+ default:
569
+ return base;
570
+ }
571
+ }
572
+
573
+ // src/state/session.ts
574
+ function createSessionViewState() {
575
+ return {
576
+ approval: createApprovalQueueState(),
577
+ authFlow: null,
578
+ busy: false,
579
+ messages: createMessageState(),
580
+ meta: { approvalMode: "ask before tools", cwd: "", modelName: "", persona: "default" },
581
+ metrics: null,
582
+ queuedCount: 0,
583
+ queued: [],
584
+ workflows: {},
585
+ question: { active: null }
586
+ };
587
+ }
588
+ function reduceSessionEvent(state, event) {
589
+ switch (event.type) {
590
+ case "agent-event": {
591
+ let messages = applyAgentEvent(
592
+ state.messages,
593
+ event.event,
594
+ event.scope?.parentToolCallId,
595
+ event.at
596
+ );
597
+ if (event.toolStatus && event.event.type === "tool_call") {
598
+ messages = setToolMessageStatus(messages, event.event.toolCall.id, event.toolStatus);
599
+ }
600
+ return { ...state, messages };
601
+ }
602
+ case "user-message":
603
+ return {
604
+ ...state,
605
+ messages: appendUserMessage(state.messages, event.text, event.attachmentLabels, event.at)
606
+ };
607
+ case "approval-requested":
608
+ return {
609
+ ...state,
610
+ approval: enqueueApproval(state.approval, event.approval),
611
+ messages: setToolMessageHidden(state.messages, event.approval.toolCallId, true)
612
+ };
613
+ case "approval-settled": {
614
+ const stamped = setToolMessageStatus(state.messages, event.toolCallId, event.status);
615
+ return {
616
+ ...state,
617
+ approval: removeApproval(state.approval, (item) => item.toolCallId === event.toolCallId),
618
+ // Approved tools materialize as a transcript row; rejected ones never
619
+ // ran and stay hidden.
620
+ messages: event.status === "approved" ? setToolMessageHidden(stamped, event.toolCallId, false) : stamped
621
+ };
622
+ }
623
+ case "question-asked":
624
+ return { ...state, question: { active: event.question } };
625
+ case "question-settled":
626
+ return state.question.active?.questionId === event.questionId ? { ...state, question: { active: null } } : state;
627
+ case "turn-status":
628
+ return {
629
+ ...state,
630
+ busy: event.busy,
631
+ queuedCount: event.queuedCount,
632
+ queued: event.queued ?? []
633
+ };
634
+ case "info":
635
+ return { ...state, messages: appendInfoMessage(state.messages, event.message) };
636
+ case "error":
637
+ return { ...state, messages: appendErrorMessage(state.messages, event.message) };
638
+ case "session-meta":
639
+ return { ...state, meta: event.meta };
640
+ case "metrics":
641
+ return { ...state, metrics: event.snapshot };
642
+ case "auth-flow":
643
+ return { ...state, authFlow: event.flow };
644
+ case "workflow-progress": {
645
+ const prev = state.workflows[event.runId] ?? createWorkflowViewState(event.runId);
646
+ return {
647
+ ...state,
648
+ workflows: { ...state.workflows, [event.runId]: foldWorkflowDelta(prev, event.delta) }
649
+ };
650
+ }
651
+ case "state-reset":
652
+ return event.state;
653
+ default:
654
+ return state;
655
+ }
656
+ }
657
+
658
+ export {
659
+ READ_ONLY_TOOLS,
660
+ BACKGROUNDLESS_TOOLS,
661
+ isAutoApprovedTool,
662
+ createApprovalQueueState,
663
+ enqueueApproval,
664
+ dequeueActiveApproval,
665
+ removeApproval,
666
+ asJsonObject,
667
+ setToolMessageStatus,
668
+ setToolMessageHidden,
669
+ createMessageState,
670
+ createMessageStateFromMessages,
671
+ appendUserMessage,
672
+ appendErrorMessage,
673
+ appendInfoMessage,
674
+ appendWarningMessage,
675
+ applyAgentEvent,
676
+ createSessionViewState,
677
+ reduceSessionEvent
678
+ };
@@ -0,0 +1,17 @@
1
+ // src/state/dir-access.ts
2
+ import * as path from "path";
3
+ function dirContains(dir, target) {
4
+ const relative2 = path.relative(path.resolve(dir), path.resolve(target));
5
+ return relative2 === "" || !relative2.startsWith("..") && !path.isAbsolute(relative2);
6
+ }
7
+ function resolveGrantDir(requestedPath, proposedDir, edited) {
8
+ const trimmed = edited?.trim();
9
+ if (!trimmed) return proposedDir;
10
+ const dir = path.resolve(trimmed);
11
+ return dirContains(dir, requestedPath) ? dir : proposedDir;
12
+ }
13
+
14
+ export {
15
+ dirContains,
16
+ resolveGrantDir
17
+ };