@sumicom/quicksave 0.8.8 → 0.8.10

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 (122) hide show
  1. package/dist/ai/cardBuilder.d.ts +65 -4
  2. package/dist/ai/cardBuilder.d.ts.map +1 -1
  3. package/dist/ai/cardBuilder.js +338 -28
  4. package/dist/ai/cardBuilder.js.map +1 -1
  5. package/dist/ai/claudeCliProvider.d.ts +15 -0
  6. package/dist/ai/claudeCliProvider.d.ts.map +1 -1
  7. package/dist/ai/claudeCliProvider.js +87 -3
  8. package/dist/ai/claudeCliProvider.js.map +1 -1
  9. package/dist/ai/claudeCodeProvider.d.ts +4 -0
  10. package/dist/ai/claudeCodeProvider.d.ts.map +1 -1
  11. package/dist/ai/claudeCodeProvider.js +41 -0
  12. package/dist/ai/claudeCodeProvider.js.map +1 -1
  13. package/dist/ai/claudeSdkProvider.d.ts +1 -0
  14. package/dist/ai/claudeSdkProvider.d.ts.map +1 -1
  15. package/dist/ai/claudeSdkProvider.js +14 -2
  16. package/dist/ai/claudeSdkProvider.js.map +1 -1
  17. package/dist/ai/claudeTerminal/cardSynth.d.ts +63 -0
  18. package/dist/ai/claudeTerminal/cardSynth.d.ts.map +1 -0
  19. package/dist/ai/claudeTerminal/cardSynth.js +92 -0
  20. package/dist/ai/claudeTerminal/cardSynth.js.map +1 -0
  21. package/dist/ai/claudeTerminal/hookBridge.d.ts +56 -0
  22. package/dist/ai/claudeTerminal/hookBridge.d.ts.map +1 -0
  23. package/dist/ai/claudeTerminal/hookBridge.js +131 -0
  24. package/dist/ai/claudeTerminal/hookBridge.js.map +1 -0
  25. package/dist/ai/claudeTerminal/hookHandler.d.ts +18 -0
  26. package/dist/ai/claudeTerminal/hookHandler.d.ts.map +1 -0
  27. package/dist/ai/claudeTerminal/hookHandler.js +124 -0
  28. package/dist/ai/claudeTerminal/hookHandler.js.map +1 -0
  29. package/dist/ai/claudeTerminal/index.d.ts +5 -0
  30. package/dist/ai/claudeTerminal/index.d.ts.map +1 -0
  31. package/dist/ai/claudeTerminal/index.js +7 -0
  32. package/dist/ai/claudeTerminal/index.js.map +1 -0
  33. package/dist/ai/claudeTerminal/jsonlTail.d.ts +40 -0
  34. package/dist/ai/claudeTerminal/jsonlTail.d.ts.map +1 -0
  35. package/dist/ai/claudeTerminal/jsonlTail.js +129 -0
  36. package/dist/ai/claudeTerminal/jsonlTail.js.map +1 -0
  37. package/dist/ai/claudeTerminal/provider.d.ts +106 -0
  38. package/dist/ai/claudeTerminal/provider.d.ts.map +1 -0
  39. package/dist/ai/claudeTerminal/provider.js +579 -0
  40. package/dist/ai/claudeTerminal/provider.js.map +1 -0
  41. package/dist/ai/claudeTerminal/settingsBuilder.d.ts +44 -0
  42. package/dist/ai/claudeTerminal/settingsBuilder.d.ts.map +1 -0
  43. package/dist/ai/claudeTerminal/settingsBuilder.js +40 -0
  44. package/dist/ai/claudeTerminal/settingsBuilder.js.map +1 -0
  45. package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
  46. package/dist/ai/codexAppServer/cardAdapter.js +13 -5
  47. package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
  48. package/dist/ai/codexAppServer/provider.d.ts +10 -2
  49. package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
  50. package/dist/ai/codexAppServer/provider.js +158 -22
  51. package/dist/ai/codexAppServer/provider.js.map +1 -1
  52. package/dist/ai/openCodeProvider.d.ts +106 -0
  53. package/dist/ai/openCodeProvider.d.ts.map +1 -0
  54. package/dist/ai/openCodeProvider.js +617 -0
  55. package/dist/ai/openCodeProvider.js.map +1 -0
  56. package/dist/ai/openCodeServer.d.ts +88 -0
  57. package/dist/ai/openCodeServer.d.ts.map +1 -0
  58. package/dist/ai/openCodeServer.js +350 -0
  59. package/dist/ai/openCodeServer.js.map +1 -0
  60. package/dist/ai/profiles/project-sandbox.sb +26 -0
  61. package/dist/ai/provider.d.ts +54 -1
  62. package/dist/ai/provider.d.ts.map +1 -1
  63. package/dist/ai/provider.js +10 -0
  64. package/dist/ai/provider.js.map +1 -1
  65. package/dist/ai/queuedUserPrompts.d.ts +8 -0
  66. package/dist/ai/queuedUserPrompts.d.ts.map +1 -0
  67. package/dist/ai/queuedUserPrompts.js +19 -0
  68. package/dist/ai/queuedUserPrompts.js.map +1 -0
  69. package/dist/ai/realtimeTranscription.d.ts +39 -0
  70. package/dist/ai/realtimeTranscription.d.ts.map +1 -0
  71. package/dist/ai/realtimeTranscription.js +154 -0
  72. package/dist/ai/realtimeTranscription.js.map +1 -0
  73. package/dist/ai/sandboxMcp.d.ts +7 -0
  74. package/dist/ai/sandboxMcp.d.ts.map +1 -1
  75. package/dist/ai/sandboxMcp.js +4 -0
  76. package/dist/ai/sandboxMcp.js.map +1 -1
  77. package/dist/ai/sandboxMcpStdio.d.ts +1 -1
  78. package/dist/ai/sandboxMcpStdio.js +120 -47
  79. package/dist/ai/sandboxMcpStdio.js.map +1 -1
  80. package/dist/ai/sessionManager.d.ts +20 -1
  81. package/dist/ai/sessionManager.d.ts.map +1 -1
  82. package/dist/ai/sessionManager.js +220 -10
  83. package/dist/ai/sessionManager.js.map +1 -1
  84. package/dist/ai/sessionRegistry.d.ts +6 -0
  85. package/dist/ai/sessionRegistry.d.ts.map +1 -1
  86. package/dist/ai/sessionRegistry.js +13 -0
  87. package/dist/ai/sessionRegistry.js.map +1 -1
  88. package/dist/ai/sessionRegistryLocator.d.ts +14 -0
  89. package/dist/ai/sessionRegistryLocator.d.ts.map +1 -0
  90. package/dist/ai/sessionRegistryLocator.js +44 -0
  91. package/dist/ai/sessionRegistryLocator.js.map +1 -0
  92. package/dist/ai/systemPrompt.d.ts.map +1 -1
  93. package/dist/ai/systemPrompt.js +2 -1
  94. package/dist/ai/systemPrompt.js.map +1 -1
  95. package/dist/ai/voiceStream.d.ts +74 -0
  96. package/dist/ai/voiceStream.d.ts.map +1 -0
  97. package/dist/ai/voiceStream.js +179 -0
  98. package/dist/ai/voiceStream.js.map +1 -0
  99. package/dist/ai/voiceTranscription.d.ts +33 -0
  100. package/dist/ai/voiceTranscription.d.ts.map +1 -0
  101. package/dist/ai/voiceTranscription.js +122 -0
  102. package/dist/ai/voiceTranscription.js.map +1 -0
  103. package/dist/files/fileBrowser.d.ts.map +1 -1
  104. package/dist/files/fileBrowser.js +15 -2
  105. package/dist/files/fileBrowser.js.map +1 -1
  106. package/dist/handlers/legacyBusAdapter.d.ts +1 -1
  107. package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
  108. package/dist/handlers/legacyBusAdapter.js +7 -1
  109. package/dist/handlers/legacyBusAdapter.js.map +1 -1
  110. package/dist/handlers/messageHandler.d.ts +6 -0
  111. package/dist/handlers/messageHandler.d.ts.map +1 -1
  112. package/dist/handlers/messageHandler.js +119 -8
  113. package/dist/handlers/messageHandler.js.map +1 -1
  114. package/dist/index.js +2 -2
  115. package/dist/index.js.map +1 -1
  116. package/dist/service/ipcClient.js +1 -1
  117. package/dist/service/ipcClient.js.map +1 -1
  118. package/dist/service/run.d.ts.map +1 -1
  119. package/dist/service/run.js +81 -3
  120. package/dist/service/run.js.map +1 -1
  121. package/dist/service/types.js +1 -1
  122. package/package.json +6 -3
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from 'events';
2
- import type { AgentId, Attachment, ClaudeSessionSummary, ClaudeUserInputRequestPayload, ClaudeUserInputResponsePayload, ClaudePreferences, ConfigValue, CardHistoryResponse, SessionUpdatePayload } from '@sumicom/quicksave-shared';
2
+ import type { AgentId, AgentProviderInfo, Attachment, ClaudeSessionSummary, ClaudeUserInputRequestPayload, ClaudeUserInputResponsePayload, ClaudePreferences, ConfigValue, CardHistoryResponse, SessionUpdatePayload } from '@sumicom/quicksave-shared';
3
3
  import { StreamCardBuilder } from './cardBuilder.js';
4
4
  import type { PermissionLevel, ProviderSession, ProviderCallbacks, CodingAgentProvider } from './provider.js';
5
5
  export interface ManagedSession {
@@ -24,6 +24,11 @@ export interface ManagedSession {
24
24
  /** Per-session UUID baked into the PermissionRequest hook command. The daemon
25
25
  * toggles bypass by creating/removing the sentinel file at `bypassFlagPath(bypassToken)`. */
26
26
  bypassToken: string;
27
+ /** Per-session correlation id minted at spawn and baked into the sandbox MCP
28
+ * server's `--corr`. Persisted onto the registry entry (`mcpCorrId`) so that
29
+ * stdio server can locate this session's entry on a fresh start, before the
30
+ * real sessionId exists. */
31
+ mcpCorrId: string;
27
32
  /** Epoch ms of the most recent SDK message whose `usage` reported a cache
28
33
  * hit or write. Provider fires `onCacheTouch` per inner API call; this is
29
34
  * the most reliable anchor for the PWA's prompt-cache countdown because
@@ -87,8 +92,13 @@ export declare class SessionManager extends EventEmitter {
87
92
  agent?: AgentId;
88
93
  /** Attachments resolved from staging by the messageHandler. */
89
94
  attachments?: readonly Attachment[];
95
+ interruptCurrentTurn?: boolean;
90
96
  }): Promise<string>;
91
97
  cancelSession(sessionId: string): Promise<boolean>;
98
+ interruptSession(sessionId: string): Promise<boolean>;
99
+ steerQueuedMessage(sessionId: string, opts?: {
100
+ interruptCurrentTurn?: boolean;
101
+ }): Promise<boolean>;
92
102
  /** Send a raw control_request to the active provider session (CLI only). */
93
103
  sendControlRequest(sessionId: string, subtype: string, params?: Record<string, unknown>): Promise<unknown>;
94
104
  closeSession(sessionId: string): boolean;
@@ -124,6 +134,10 @@ export declare class SessionManager extends EventEmitter {
124
134
  * `get_context_usage` (only the claude-code CLI does). */
125
135
  getSessionContextUsage(sessionId: string): Promise<import('@sumicom/quicksave-shared').ContextUsageBreakdown | null>;
126
136
  getSessionAgent(sessionId: string, cwd?: string): AgentId;
137
+ /** Correlation id minted for this session's sandbox MCP server, persisted
138
+ * onto the registry entry so the stdio server can locate it on a fresh
139
+ * start. Undefined for sessions not tracked in memory. */
140
+ getSessionMcpCorrId(sessionId: string): string | undefined;
127
141
  getActiveSessionCount(): number;
128
142
  getCardBuilder(sessionId: string): StreamCardBuilder | null;
129
143
  listAvailableSessions(cwd: string): Promise<ClaudeSessionSummary[]>;
@@ -193,6 +207,11 @@ export declare class SessionManager extends EventEmitter {
193
207
  snapshotActiveSessions(): SessionUpdatePayload[];
194
208
  private persistRegistryField;
195
209
  private persistAllowPattern;
210
+ /** Probe every registered provider and return discoverable metadata.
211
+
212
+ * Used as the response to `agent:probe` commands and to populate the
213
+ * `availableProviders` field of the {@link HandshakeAckPayload}. */
214
+ probeProviders(): Promise<AgentProviderInfo[]>;
196
215
  private waitForUserInput;
197
216
  }
198
217
  //# sourceMappingURL=sessionManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionManager.d.ts","sourceRoot":"","sources":["../../src/ai/sessionManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sessionManager.d.ts","sourceRoot":"","sources":["../../src/ai/sessionManager.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,18 @@ import { SANDBOX_BASH_TOOL, UPDATE_SESSION_STATUS_TOOL } from './sandboxMcp.js';
13
13
  import { getSessionRegistry } from './sessionRegistry.js';
14
14
  import { getEventStore } from '../storage/eventStore.js';
15
15
  import { buildSystemPrompt } from './systemPrompt.js';
16
- import { defaultPermissionLevelForAgent, isFullAccessPermission, isPermissionLevelAcceptedForAgent, normalizePermissionLevelForAgent, } from './provider.js';
16
+ import { defaultPermissionLevelForAgent, getAgentLabel, isFullAccessPermission, isPermissionLevelAcceptedForAgent, normalizePermissionLevelForAgent, } from './provider.js';
17
+ /** Capabilities sentinel for providers that haven't implemented `probeProvider`
18
+ * yet. Lets the handshake still report the provider's existence without
19
+ * fabricating unsupported features. */
20
+ const EMPTY_AGENT_CAPABILITIES = {
21
+ hasApiKey: false,
22
+ hasCli: false,
23
+ hasPlugin: false,
24
+ supportsResume: false,
25
+ supportsSandbox: false,
26
+ supportsStreaming: false,
27
+ };
17
28
  /**
18
29
  * Events emitted:
19
30
  * 'card-event' (event: CardEvent)
@@ -27,12 +38,15 @@ import { defaultPermissionLevelForAgent, isFullAccessPermission, isPermissionLev
27
38
  /** Tools auto-approved at each permission level (no user prompt). */
28
39
  const CLAUDE_AUTO_APPROVE = {
29
40
  bypassPermissions: new Set([
30
- 'Edit', 'Write', 'NotebookEdit', 'TodoWrite', 'Agent', 'EnterWorktree', 'ExitWorktree',
41
+ 'Edit', 'Write', 'NotebookEdit', 'TodoWrite', 'Agent', 'EnterWorktree',
31
42
  'WebFetch', 'WebSearch', 'Bash',
32
43
  'Skill', 'ToolSearch', 'Config',
33
44
  'CronCreate', 'CronDelete', 'CronList', 'RemoteTrigger',
34
- 'EnterPlanMode', 'ExitPlanMode',
45
+ 'EnterPlanMode',
35
46
  'TaskOutput', 'TaskStop',
47
+ // ExitWorktree intentionally absent: exiting a worktree must require user confirmation.
48
+ // ExitPlanMode intentionally absent: plan review must always reach the user.
49
+ // AskUserQuestion intentionally absent: interactive prompts must always reach the user.
36
50
  ]),
37
51
  acceptEdits: new Set(['Edit', 'Write', 'NotebookEdit', 'TodoWrite', 'Agent', 'EnterWorktree', 'ExitWorktree', 'EnterPlanMode']),
38
52
  default: new Set(['TodoWrite', 'EnterWorktree', 'ExitWorktree', 'Agent', 'EnterPlanMode']),
@@ -61,6 +75,29 @@ const SESSION_STAGES = [
61
75
  function isSessionStage(value) {
62
76
  return SESSION_STAGES.includes(value);
63
77
  }
78
+ function parsePendingMissionUntil(value) {
79
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0)
80
+ return value;
81
+ if (typeof value !== 'string' || value.trim().length === 0)
82
+ return null;
83
+ const parsed = Date.parse(value);
84
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
85
+ }
86
+ function buildPendingMission(input, existing) {
87
+ const label = typeof input.pendingMissionLabel === 'string' && input.pendingMissionLabel.trim().length > 0
88
+ ? input.pendingMissionLabel.trim()
89
+ : existing?.label;
90
+ const until = parsePendingMissionUntil(input.pendingMissionUntil) ?? existing?.until;
91
+ if (!label || typeof until !== 'number')
92
+ return null;
93
+ const isNewSchedule = existing?.label !== label || existing?.until !== until;
94
+ return {
95
+ label,
96
+ until,
97
+ startedAt: existing?.startedAt ?? Date.now(),
98
+ ...(isNewSchedule || existing?.dismissedAt === undefined ? {} : { dismissedAt: existing.dismissedAt }),
99
+ };
100
+ }
64
101
  function autoApproveToolsFor(agentId, level) {
65
102
  const normalized = normalizePermissionLevelForAgent(agentId, level);
66
103
  if (agentId === 'codex') {
@@ -99,9 +136,21 @@ function normalizeAgentId(value) {
99
136
  if (value === 'claude-code' || value === 'claude-cli' || value === 'claude-sdk') {
100
137
  return 'claude-code';
101
138
  }
139
+ // claude-terminal is a distinct provider (TUI + PTY), NOT an alias of
140
+ // claude-code. Omitting it here made resolveAgentId() return undefined for
141
+ // claude-terminal sessions on every config/registry/request path, so a cold
142
+ // resume (when the in-memory agentId is gone) silently fell back to the
143
+ // default agent — downgrading the session to claude-code and dropping its
144
+ // terminalId. See sessionManager resolveAgentId() fallback chain.
145
+ if (value === 'claude-terminal')
146
+ return 'claude-terminal';
102
147
  if (value === 'codex' || value === 'codex-mcp') {
103
148
  return 'codex';
104
149
  }
150
+ if (value === 'opencode')
151
+ return 'opencode';
152
+ if (value === 'pi')
153
+ return 'pi';
105
154
  return undefined;
106
155
  }
107
156
  /** How often we record a `cache_touched` event to the event store. Anthropic's
@@ -263,10 +312,33 @@ export class SessionManager extends EventEmitter {
263
312
  // agent prefs bucket — causing a Codex GPT pick to corrupt the user's
264
313
  // saved Claude default and leak across sessions on resume.
265
314
  // Codex app-server sessions pick the change up on the next turn/start
266
- // via the runtime override pipeline. Claude's model is fixed per
267
- // session; the value here is consumed by the next cold resume.
315
+ // via the runtime override pipeline.
268
316
  this.enqueueCodexOverrideIfApplicable(sessionId, { model: value });
269
317
  this.persistRegistryField(sessionId, 'model', value);
318
+ // For Claude CLI sessions, also drive a `set_model` control_request so
319
+ // the running process switches immediately rather than waiting for the
320
+ // next prompt's cold-respawn. The CLI keeps the same session_id when
321
+ // it accepts `set_model`, so we also bump `spawnedModel` to keep
322
+ // resumeSession's modelChanged check from kicking off an unnecessary
323
+ // respawn on the next turn.
324
+ const ps = this.sessions.get(sessionId);
325
+ const cliSession = ps?.providerSession;
326
+ if (ps?.providerSession?.alive && cliSession && typeof cliSession.sendControlRequest === 'function') {
327
+ const { decorateModelWithContextWindow } = await import('./claudeCliProvider.js');
328
+ const ctxWindow = next.contextWindow ?? ps.spawnedContextWindow;
329
+ const decorated = decorateModelWithContextWindow(value, ctxWindow) ?? value;
330
+ try {
331
+ await cliSession.sendControlRequest('set_model', { model: decorated }, 5_000);
332
+ ps.spawnedModel = value;
333
+ }
334
+ catch (err) {
335
+ // CLI rejected — fall back to cold-respawn on next prompt via the
336
+ // modelChanged check. Leave spawnedModel untouched so that check
337
+ // fires. Don't roll back sessionConfigs: the user-intended value is
338
+ // what should apply on respawn.
339
+ console.warn(`[session-manager] set_model failed for ${sessionId.slice(0, 8)}, will cold-respawn on next prompt:`, err);
340
+ }
341
+ }
270
342
  }
271
343
  else if (key === 'reasoningEffort' && (typeof value === 'string' || value === null)) {
272
344
  const effort = value === null ? null : value;
@@ -362,6 +434,10 @@ export class SessionManager extends EventEmitter {
362
434
  // the sentinel file without a respawn.
363
435
  const bypassToken = randomUUID();
364
436
  applyBypassFlag(bypassToken, isFullAccessPermission(provider.id, level));
437
+ // Mint a correlation id up front so the sandbox MCP server (spawned by the
438
+ // provider before the real sessionId exists) can find this session's
439
+ // registry entry by matching `mcpCorrId`. See `sandboxMcpStdio.ts`.
440
+ const mcpCorrId = randomUUID();
365
441
  // Create cardBuilder with 'pending' sessionId — will be updated after provider returns real one
366
442
  const cardBuilder = new StreamCardBuilder('pending', opts.cwd);
367
443
  const callbacks = this.makeCallbacks(provider.id);
@@ -375,6 +451,7 @@ export class SessionManager extends EventEmitter {
375
451
  reasoningEffort: opts.reasoningEffort,
376
452
  contextWindow: opts.contextWindow,
377
453
  bypassFlagPath: bypassFlagPath(bypassToken),
454
+ mcpCorrId,
378
455
  attachments: opts.attachments,
379
456
  }, cardBuilder, callbacks);
380
457
  // Update cardBuilder with real sessionId — new session has no prior JSONL
@@ -402,6 +479,7 @@ export class SessionManager extends EventEmitter {
402
479
  ...(recordedModel !== undefined ? { model: recordedModel } : {}),
403
480
  ...(opts.reasoningEffort !== undefined ? { reasoningEffort: opts.reasoningEffort } : {}),
404
481
  ...(opts.contextWindow !== undefined ? { contextWindow: opts.contextWindow } : {}),
482
+ ...(providerSession?.terminalId !== undefined ? { terminalId: providerSession.terminalId } : {}),
405
483
  permissionMode: level,
406
484
  sandboxed,
407
485
  });
@@ -417,6 +495,7 @@ export class SessionManager extends EventEmitter {
417
495
  spawnedModel: opts.model,
418
496
  spawnedContextWindow: opts.contextWindow,
419
497
  bypassToken,
498
+ mcpCorrId,
420
499
  };
421
500
  this.sessions.set(sessionId, managed);
422
501
  this.emitSessionUpdate(sessionId);
@@ -463,7 +542,17 @@ export class SessionManager extends EventEmitter {
463
542
  if (opts.attachments && opts.attachments.length > 0) {
464
543
  await persistAttachments(opts.sessionId, opts.attachments);
465
544
  }
466
- existing.providerSession.sendUserMessage(opts.prompt, opts.attachments);
545
+ if (opts.interruptCurrentTurn && existing.providerSession.interruptThenSendUserMessage) {
546
+ existing.providerSession.interruptThenSendUserMessage(opts.prompt, opts.attachments);
547
+ }
548
+ else if (opts.interruptCurrentTurn) {
549
+ existing.providerSession.interrupt();
550
+ existing.providerSession.sendUserMessage(opts.prompt, opts.attachments);
551
+ }
552
+ else {
553
+ existing.providerSession.sendUserMessage(opts.prompt, opts.attachments);
554
+ }
555
+ this.emitSessionUpdate(opts.sessionId);
467
556
  return opts.sessionId;
468
557
  }
469
558
  // Hot resume (idle): CLI alive but between turns — reuse the same process
@@ -524,6 +613,10 @@ export class SessionManager extends EventEmitter {
524
613
  // e.g. after a daemon restart).
525
614
  const bypassToken = existing?.bypassToken ?? randomUUID();
526
615
  applyBypassFlag(bypassToken, isFullAccessPermission(provider.id, level));
616
+ // Reuse the session's existing correlation id (in-memory or persisted on
617
+ // the registry entry) so a cold re-spawn's MCP server still matches the
618
+ // same entry; mint one only when resuming a session we've never tracked.
619
+ const mcpCorrId = existing?.mcpCorrId ?? registryEntry?.mcpCorrId ?? randomUUID();
527
620
  const { sessionId, session: providerSession } = await provider.resumeSession({
528
621
  sessionId: opts.sessionId,
529
622
  prompt: opts.prompt,
@@ -535,6 +628,7 @@ export class SessionManager extends EventEmitter {
535
628
  reasoningEffort: resumeReasoningEffort,
536
629
  contextWindow: resumeContextWindow,
537
630
  bypassFlagPath: bypassFlagPath(bypassToken),
631
+ mcpCorrId,
538
632
  attachments: opts.attachments,
539
633
  }, cardBuilder, callbacks);
540
634
  // Seed sessionConfigs so /sessions/config snapshot reflects the
@@ -567,6 +661,7 @@ export class SessionManager extends EventEmitter {
567
661
  existing.streaming = true;
568
662
  existing.spawnedModel = resumeModel;
569
663
  existing.spawnedContextWindow = resumeContextWindow;
664
+ existing.mcpCorrId = mcpCorrId;
570
665
  // Cold resume can fork a new CLI session_id. Rekey the map + side
571
666
  // maps to the new ID so emitSessionUpdate finds the entry and the
572
667
  // PWA sees isActive=true (the ghost-inactive bug otherwise).
@@ -592,6 +687,7 @@ export class SessionManager extends EventEmitter {
592
687
  spawnedModel: resumeModel,
593
688
  spawnedContextWindow: resumeContextWindow,
594
689
  bypassToken,
690
+ mcpCorrId,
595
691
  };
596
692
  this.sessions.set(sessionId, managed);
597
693
  }
@@ -625,6 +721,27 @@ export class SessionManager extends EventEmitter {
625
721
  this.cancelPendingInputsForSession(sessionId);
626
722
  return true;
627
723
  }
724
+ async interruptSession(sessionId) {
725
+ const ps = this.sessions.get(sessionId);
726
+ if (!ps?.providerSession)
727
+ return false;
728
+ console.log(`[session-manager] interrupt current turn session=${sessionId.slice(0, 8)}`);
729
+ ps.providerSession.interrupt();
730
+ this.emitSessionUpdate(sessionId);
731
+ return true;
732
+ }
733
+ async steerQueuedMessage(sessionId, opts) {
734
+ const ps = this.sessions.get(sessionId);
735
+ if (!ps?.providerSession?.alive)
736
+ return false;
737
+ const steerQueuedMessage = ps.providerSession.steerQueuedMessage;
738
+ if (!steerQueuedMessage)
739
+ return false;
740
+ console.log(`[session-manager] steer queued message session=${sessionId.slice(0, 8)} interrupt=${opts?.interruptCurrentTurn === true}`);
741
+ const ok = await steerQueuedMessage.call(ps.providerSession, opts);
742
+ this.emitSessionUpdate(sessionId);
743
+ return ok;
744
+ }
628
745
  /** Send a raw control_request to the active provider session (CLI only). */
629
746
  async sendControlRequest(sessionId, subtype, params) {
630
747
  const ps = this.sessions.get(sessionId);
@@ -767,10 +884,13 @@ export class SessionManager extends EventEmitter {
767
884
  return false;
768
885
  this.pendingInputRequests.delete(response.requestId);
769
886
  pending.resolve(response);
770
- // Clear pending input on the card
887
+ // Clear pending input on the card; attach rejection reason if present
771
888
  const ps = this.sessions.get(pending.request.sessionId);
772
889
  if (ps?.cardBuilder) {
773
- const cardEvt = ps.cardBuilder.clearPendingInput(response.requestId);
890
+ const rejectionAnswers = (response.action === 'deny' && response.response)
891
+ ? { _rejection: response.response }
892
+ : undefined;
893
+ const cardEvt = ps.cardBuilder.clearPendingInput(response.requestId, rejectionAnswers);
774
894
  if (cardEvt)
775
895
  this.emit('card-event', cardEvt);
776
896
  }
@@ -825,6 +945,12 @@ export class SessionManager extends EventEmitter {
825
945
  getSessionAgent(sessionId, cwd) {
826
946
  return this.resolveAgentId(sessionId, cwd);
827
947
  }
948
+ /** Correlation id minted for this session's sandbox MCP server, persisted
949
+ * onto the registry entry so the stdio server can locate it on a fresh
950
+ * start. Undefined for sessions not tracked in memory. */
951
+ getSessionMcpCorrId(sessionId) {
952
+ return this.sessions.get(sessionId)?.mcpCorrId;
953
+ }
828
954
  getActiveSessionCount() {
829
955
  return this.sessions.size;
830
956
  }
@@ -871,6 +997,7 @@ export class SessionManager extends EventEmitter {
871
997
  lastTurnCacheReadTokens: lastTurn?.cacheReadTokens,
872
998
  lastTurnContextUsage: lastTurn?.contextUsage,
873
999
  lastReadAt: entry.lastReadAt,
1000
+ pendingMission: entry.pendingMission,
874
1001
  };
875
1002
  });
876
1003
  }
@@ -887,7 +1014,12 @@ export class SessionManager extends EventEmitter {
887
1014
  // Memory-mode: use in-memory cards from active turn, falling back to persisted history
888
1015
  const streamCards = ps?.cardBuilder?.getCards() ?? [];
889
1016
  const persisted = await loadPersistedCards(sessionId);
890
- const cards = [...persisted, ...streamCards];
1017
+ const cardsById = new Map();
1018
+ for (const card of persisted)
1019
+ cardsById.set(card.id, card);
1020
+ for (const card of streamCards)
1021
+ cardsById.set(card.id, card);
1022
+ const cards = Array.from(cardsById.values());
891
1023
  const total = cards.length;
892
1024
  const start = Math.max(0, total - offset - limit);
893
1025
  const end = Math.max(0, total - offset);
@@ -1025,6 +1157,13 @@ export class SessionManager extends EventEmitter {
1025
1157
  }
1026
1158
  this.emitSessionUpdate(sessionId);
1027
1159
  },
1160
+ onQueueStateChange: (sessionId) => {
1161
+ const ps = this.sessions.get(sessionId);
1162
+ if (ps?.providerSession?.alive) {
1163
+ ps.streaming = true;
1164
+ }
1165
+ this.emitSessionUpdate(sessionId);
1166
+ },
1028
1167
  onModelDetected: (model) => {
1029
1168
  console.log(`[session-manager] model detected: ${model} (agent=${agentId})`);
1030
1169
  if (agentId === 'claude-code') {
@@ -1136,6 +1275,16 @@ export class SessionManager extends EventEmitter {
1136
1275
  const level = this.sessions.get(sessionId)?.permissionLevel
1137
1276
  ?? this.sessionPermissions.get(sessionId)
1138
1277
  ?? defaultPermissionLevelForAgent(agentId);
1278
+ // In full-access (bypass) mode the PermissionRequest hook handles built-in
1279
+ // tools via the sentinel file. MCP tools (full names like
1280
+ // "mcp__quicksave-sandbox__TaskCreate") bypass the hook entirely in default
1281
+ // CLI mode and ALWAYS send can_use_tool to the daemon. Rather than keeping
1282
+ // a fragile explicit list, approve everything here except the three tools
1283
+ // that must always reach the user (matching the hook's own exclusion list).
1284
+ if (isFullAccessPermission(agentId, level)) {
1285
+ const BYPASS_NEVER_SKIP = new Set(['AskUserQuestion', 'ExitPlanMode', 'ExitWorktree']);
1286
+ return !BYPASS_NEVER_SKIP.has(toolName);
1287
+ }
1139
1288
  if (!autoApproveToolsFor(agentId, level).has(toolName))
1140
1289
  return false;
1141
1290
  // For file-writing tools, restrict to project cwd
@@ -1165,6 +1314,9 @@ export class SessionManager extends EventEmitter {
1165
1314
  const entryUpdates = {};
1166
1315
  let noteToAppend = null;
1167
1316
  let changed = false;
1317
+ const registryEntry = this.sessions.get(sessionId)?.cwd
1318
+ ? getSessionRegistry().getEntry(this.sessions.get(sessionId).cwd, sessionId)
1319
+ : undefined;
1168
1320
  if (typeof input.subject === 'string' && input.subject.length > 0) {
1169
1321
  configUpdates.title = input.subject;
1170
1322
  entryUpdates.title = input.subject;
@@ -1174,6 +1326,12 @@ export class SessionManager extends EventEmitter {
1174
1326
  configUpdates.stage = input.stage;
1175
1327
  entryUpdates.stage = input.stage;
1176
1328
  changed = true;
1329
+ if (input.stage === 'done') {
1330
+ configUpdates.pendingMissionLabel = null;
1331
+ configUpdates.pendingMissionUntil = null;
1332
+ configUpdates.pendingMissionDismissedAt = null;
1333
+ entryUpdates.pendingMission = undefined;
1334
+ }
1177
1335
  }
1178
1336
  if (typeof input.blocked === 'boolean') {
1179
1337
  configUpdates.blocked = input.blocked;
@@ -1186,6 +1344,23 @@ export class SessionManager extends EventEmitter {
1186
1344
  noteToAppend = input.note;
1187
1345
  changed = true;
1188
1346
  }
1347
+ if (input.clearPendingMission === true) {
1348
+ configUpdates.pendingMissionLabel = null;
1349
+ configUpdates.pendingMissionUntil = null;
1350
+ configUpdates.pendingMissionDismissedAt = null;
1351
+ entryUpdates.pendingMission = undefined;
1352
+ changed = true;
1353
+ }
1354
+ else if (input.pendingMissionLabel !== undefined || input.pendingMissionUntil !== undefined) {
1355
+ const pendingMission = buildPendingMission(input, registryEntry?.pendingMission);
1356
+ if (pendingMission) {
1357
+ configUpdates.pendingMissionLabel = pendingMission.label;
1358
+ configUpdates.pendingMissionUntil = pendingMission.until;
1359
+ configUpdates.pendingMissionDismissedAt = pendingMission.dismissedAt ?? null;
1360
+ entryUpdates.pendingMission = pendingMission;
1361
+ changed = true;
1362
+ }
1363
+ }
1189
1364
  if (!changed)
1190
1365
  return;
1191
1366
  const prev = this.sessionConfigs.get(sessionId) ?? {};
@@ -1195,7 +1370,7 @@ export class SessionManager extends EventEmitter {
1195
1370
  const ps = this.sessions.get(sessionId);
1196
1371
  if (ps?.cwd) {
1197
1372
  const registry = getSessionRegistry();
1198
- const entry = registry.getEntry(ps.cwd, sessionId);
1373
+ const entry = registryEntry ?? registry.getEntry(ps.cwd, sessionId);
1199
1374
  if (entry) {
1200
1375
  let nextHistory = entry.noteHistory;
1201
1376
  if (noteToAppend !== null) {
@@ -1211,6 +1386,9 @@ export class SessionManager extends EventEmitter {
1211
1386
  registry.upsertEntry({
1212
1387
  ...entry,
1213
1388
  ...entryUpdates,
1389
+ ...(Object.prototype.hasOwnProperty.call(entryUpdates, 'pendingMission')
1390
+ ? { pendingMission: entryUpdates.pendingMission }
1391
+ : {}),
1214
1392
  ...(nextHistory !== entry.noteHistory ? { noteHistory: nextHistory } : {}),
1215
1393
  lastAccessedAt: Date.now(),
1216
1394
  });
@@ -1292,6 +1470,7 @@ export class SessionManager extends EventEmitter {
1292
1470
  agent: ps?.agentId ?? this.sessionAgents.get(sessionId),
1293
1471
  isStreaming: ps?.streaming ?? false,
1294
1472
  hasPendingInput,
1473
+ queueState: ps?.providerSession?.getQueueState?.() ?? null,
1295
1474
  permissionMode: ps?.permissionLevel ?? this.sessionPermissions.get(sessionId),
1296
1475
  sandboxed: ps?.sandboxed ?? this.sessionSandboxed.get(sessionId) ?? false,
1297
1476
  lastPromptAt: stats.lastPromptAt ?? undefined,
@@ -1309,6 +1488,12 @@ export class SessionManager extends EventEmitter {
1309
1488
  // update so each PWA client converges on the latest read state without
1310
1489
  // having to wait for the slower /sessions/history snapshot to roll over.
1311
1490
  lastReadAt: registryEntry?.lastReadAt,
1491
+ pendingMission: registryEntry?.pendingMission,
1492
+ // Surface the PTY terminal id when this provider owns one. Only the
1493
+ // claude-terminal provider populates it today. The PWA mirrors it onto
1494
+ // ClaudeSessionSummary so the session view can render a TerminalView
1495
+ // alongside the structured card stream.
1496
+ terminalId: ps?.providerSession?.terminalId ?? undefined,
1312
1497
  };
1313
1498
  }
1314
1499
  /** Snapshot of every in-memory session's state (both streaming and idle —
@@ -1359,6 +1544,31 @@ export class SessionManager extends EventEmitter {
1359
1544
  console.log(`[persistAllowPattern] saved ${pattern} to ${settingsPath}`);
1360
1545
  }
1361
1546
  }
1547
+ /** Probe every registered provider and return discoverable metadata.
1548
+
1549
+ * Used as the response to `agent:probe` commands and to populate the
1550
+ * `availableProviders` field of the {@link HandshakeAckPayload}. */
1551
+ async probeProviders() {
1552
+ const results = [];
1553
+ for (const [id, provider] of this.providers) {
1554
+ const label = provider.label ?? getAgentLabel(id);
1555
+ if (typeof provider.probeProvider !== 'function') {
1556
+ // Provider hasn't been migrated yet — advertise it with zeroed
1557
+ // capabilities so the PWA still knows it exists.
1558
+ results.push({ id, label, capabilities: EMPTY_AGENT_CAPABILITIES });
1559
+ continue;
1560
+ }
1561
+ try {
1562
+ const probe = await provider.probeProvider();
1563
+ results.push({ id, label, ...probe });
1564
+ }
1565
+ catch (error) {
1566
+ console.warn(`[SessionManager] probe failed for ${id}:`, error);
1567
+ results.push({ id, label, capabilities: EMPTY_AGENT_CAPABILITIES });
1568
+ }
1569
+ }
1570
+ return results;
1571
+ }
1362
1572
  waitForUserInput(requestId, request) {
1363
1573
  return new Promise((resolve) => {
1364
1574
  this.pendingInputRequests.set(requestId, { resolve, request });