@hyperdreamer/pi-webui 1.11.0-beta.6 → 1.11.0-beta.7

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 (34) hide show
  1. package/dist/client/assets/{CodeViewer-CH1qYiww.js → CodeViewer-DnVESWeo.js} +1 -1
  2. package/dist/client/assets/{UnifiedDiffViewer-Dm-7IWHF.js → UnifiedDiffViewer-DtcErd2T.js} +1 -1
  3. package/dist/client/assets/{index-CcRQiTO_.js → index-BnqpOnKq.js} +418 -416
  4. package/dist/client/index.html +1 -1
  5. package/dist/server/sessiond.js +6 -1
  6. package/dist/server/sessiond.js.map +1 -1
  7. package/dist/server/sessions/modelPolicyCapability.js +88 -0
  8. package/dist/server/sessions/modelPolicyCapability.js.map +1 -0
  9. package/dist/server/sessions/modelPolicyTool.js +31 -0
  10. package/dist/server/sessions/modelPolicyTool.js.map +1 -0
  11. package/dist/server/sessions/piSessionService.js +877 -290
  12. package/dist/server/sessions/piSessionService.js.map +1 -1
  13. package/dist/server/sessions/sessionDefaultsRoutes.js +39 -0
  14. package/dist/server/sessions/sessionDefaultsRoutes.js.map +1 -1
  15. package/dist/server/sessions/sessionDefaultsService.js +36 -6
  16. package/dist/server/sessions/sessionDefaultsService.js.map +1 -1
  17. package/dist/server/sessions/starterModelPolicyPreferenceStore.js +164 -0
  18. package/dist/server/sessions/starterModelPolicyPreferenceStore.js.map +1 -0
  19. package/dist/shared/apiTypes.d.ts +23 -3
  20. package/dist/shared/apiTypes.js +1 -0
  21. package/dist/shared/apiTypes.js.map +1 -1
  22. package/dist/shared/capabilities.js +3 -0
  23. package/dist/shared/capabilities.js.map +1 -1
  24. package/docs/config.md +10 -4
  25. package/optional-skills/deterministic-subagent-driven-development/SKILL.md +15 -15
  26. package/optional-skills/deterministic-subagent-driven-development/pi-webui-skill.json +2 -2
  27. package/optional-skills/deterministic-subagent-driven-development/references/capability-contract.md +16 -16
  28. package/optional-skills/deterministic-subagent-driven-development/references/plan-contract.md +4 -4
  29. package/optional-skills/deterministic-subagent-driven-development/references/state-machine.md +3 -3
  30. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/plan-policy.mjs +18 -9
  31. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/prompt-renderer.mjs +3 -3
  32. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-machine.mjs +6 -5
  33. package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state.mjs +1 -0
  34. package/package.json +1 -1
@@ -7,28 +7,34 @@ import { projectBrowserMessage } from "../browserMessageProjection.js";
7
7
  import { pageMessagesAtSafeBoundary } from "./messagePaging.js";
8
8
  import { exportSessionHistoryHtml } from "./sessionHistoryExport.js";
9
9
  import { BUILTIN_COMMANDS } from "./builtinCommands.js";
10
- import { SessionCommandService, clientSessionFromRuntime } from "./sessionCommandService.js";
11
- import { projectSessionTree } from "./sessionTreeProjection.js";
12
- import { SessionArchiveStore } from "./sessionArchiveStore.js";
10
+ import { SessionCommandService, clientSessionFromRuntime, } from "./sessionCommandService.js";
11
+ import { projectSessionTree, } from "./sessionTreeProjection.js";
12
+ import { SessionArchiveStore, } from "./sessionArchiveStore.js";
13
13
  import { SessionMetadataStore } from "./sessionMetadataStore.js";
14
- import { findArchiveCandidateByIdOrPrefix, planSessionArchiveTree } from "./sessionArchiveTree.js";
15
- import { deterministicSessionName, fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
14
+ import { findArchiveCandidateByIdOrPrefix, planSessionArchiveTree, } from "./sessionArchiveTree.js";
15
+ import { deterministicSessionName, fallbackSessionName, generateShortSessionName, } from "./sessionNameGenerator.js";
16
16
  import { computeEditPreview } from "./editPreview.js";
17
- import { attachmentsToInlineImages, saveAttachmentsToWorkspace } from "./attachmentService.js";
17
+ import { attachmentsToInlineImages, saveAttachmentsToWorkspace, } from "./attachmentService.js";
18
18
  import { parsePromptAttachments } from "../../shared/promptAttachments.js";
19
19
  import { isKnownThinkingLevel } from "../../shared/thinkingLevels.js";
20
- import { SESSION_TREE_CUSTOM_INSTRUCTIONS_MAX_LENGTH, SESSION_UNREAD_LIMIT } from "../../shared/apiTypes.js";
20
+ import { SESSION_TREE_CUSTOM_INSTRUCTIONS_MAX_LENGTH, SESSION_UNREAD_LIMIT, } from "../../shared/apiTypes.js";
21
21
  import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
22
- import { createSpawnSessionToolDefinition } from "./spawnSessionTool.js";
23
- import { createSubsessionToolDefinitions } from "./spawnSubsessionTool.js";
22
+ import { createSpawnSessionToolDefinition, } from "./spawnSessionTool.js";
23
+ import { createSubsessionToolDefinitions, } from "./spawnSubsessionTool.js";
24
+ import { createModelPolicyToolDefinition, } from "./modelPolicyTool.js";
25
+ import { buildModelPolicyCapability, } from "./modelPolicyCapability.js";
24
26
  import { buildTranscriptView } from "./subsessionTranscript.js";
25
- import { planForceCleanup, planSessionCleanup, summarizeForceCleanupExecution, summarizeSessionCleanupExecution } from "./sessionCleanup.js";
27
+ import { planForceCleanup, planSessionCleanup, summarizeForceCleanupExecution, summarizeSessionCleanupExecution, } from "./sessionCleanup.js";
26
28
  import { SessionNotificationStore, } from "./sessionNotificationStore.js";
27
29
  import { plainTextTheme } from "./plainTextTheme.js";
28
- import { SessionUnreadStore } from "./sessionUnreadStore.js";
29
- import { createModelTierRegistry, isModelTier, runtimeThinkingLevels } from "./modelTierRegistry.js";
30
+ import { SessionUnreadStore, } from "./sessionUnreadStore.js";
31
+ import { createModelTierRegistry, isModelTier, runtimeThinkingLevels, } from "./modelTierRegistry.js";
30
32
  import { inspectSessionModelPolicy, planSessionModelPolicyUpdate, serializeSessionModelPolicy, SESSION_MODEL_POLICY_CUSTOM_TYPE, } from "./sessionModelPolicy.js";
31
- const noopLogger = { info() { } };
33
+ const noopLogger = {
34
+ info() {
35
+ /* no-op */
36
+ },
37
+ };
32
38
  const DEFAULT_UNREAD_PUBLICATION_RETRY_MS = 1_000;
33
39
  const MAX_UNREAD_PUBLICATION_RETRY_MS = 30_000;
34
40
  const MAX_PENDING_UNREAD_MUTATIONS = SESSION_UNREAD_LIMIT + 1;
@@ -90,13 +96,13 @@ function requirePromptText(value) {
90
96
  throw new Error("Prompt text is required");
91
97
  return value;
92
98
  }
93
- function leadingTierDirective(prompt) {
94
- const match = /^(?:\uFEFF)?\/tier-([^\s\r\n]+)(?:[ \t]*(?:\r?\n|$))/u.exec(prompt);
99
+ function leadingTierEcho(prompt) {
100
+ const match = /^(?:\uFEFF)?Model tier: ([^\s\r\n]+)(?:[ \t]*(?:\r?\n|$))/u.exec(prompt);
95
101
  if (match === null)
96
102
  return undefined;
97
103
  const value = match[1];
98
104
  if (value === undefined || !isModelTier(value))
99
- throw new Error(`Unknown leading tier directive: /tier-${value ?? ""}`);
105
+ throw new Error(`Unknown leading model tier label: ${value ?? ""}`);
100
106
  return value;
101
107
  }
102
108
  function parsePromptStreamingBehavior(value) {
@@ -106,7 +112,10 @@ function parsePromptStreamingBehavior(value) {
106
112
  return value;
107
113
  throw new Error('Prompt streamingBehavior must be "steer" or "followUp"');
108
114
  }
109
- const CLEAR_RUNTIME_NOTIFICATIONS = { kind: "clear", reason: "runtime-close" };
115
+ const CLEAR_RUNTIME_NOTIFICATIONS = {
116
+ kind: "clear",
117
+ reason: "runtime-close",
118
+ };
110
119
  const DEFER_RUNTIME_NOTIFICATIONS = { kind: "defer" };
111
120
  function resourceDiagnosticToWarning(diagnostic, source) {
112
121
  return {
@@ -117,7 +126,11 @@ function resourceDiagnosticToWarning(diagnostic, source) {
117
126
  };
118
127
  }
119
128
  function runtimeDiagnosticToWarning(diagnostic) {
120
- return { severity: diagnostic.type, message: diagnostic.message, source: "runtime" };
129
+ return {
130
+ severity: diagnostic.type,
131
+ message: diagnostic.message,
132
+ source: "runtime",
133
+ };
121
134
  }
122
135
  /**
123
136
  * Compute the live warnings for a runtime by re-reading its current resource
@@ -141,7 +154,12 @@ export function collectRuntimeWarnings(runtime) {
141
154
  for (const diagnostic of resourceLoader.getThemes().diagnostics)
142
155
  warnings.push(resourceDiagnosticToWarning(diagnostic, "theme"));
143
156
  for (const error of resourceLoader.getExtensions().errors) {
144
- warnings.push({ severity: "error", message: `${error.path}: ${error.error}`, source: "extension", path: error.path });
157
+ warnings.push({
158
+ severity: "error",
159
+ message: `${error.path}: ${error.error}`,
160
+ source: "extension",
161
+ path: error.path,
162
+ });
145
163
  }
146
164
  }
147
165
  return warnings;
@@ -207,7 +225,10 @@ export function dismissSessionWarning(session, dismissId) {
207
225
  if (dismissId !== ANTHROPIC_EXTRA_USAGE_DISMISS_ID) {
208
226
  throw new Error(`Unknown session warning dismiss id: ${dismissId}`);
209
227
  }
210
- session.settingsManager.setWarnings({ ...session.settingsManager.getWarnings(), anthropicExtraUsage: false });
228
+ session.settingsManager.setWarnings({
229
+ ...session.settingsManager.getWarnings(),
230
+ anthropicExtraUsage: false,
231
+ });
211
232
  }
212
233
  function defaultCreateAgentRuntime(createRuntime, options) {
213
234
  if (!(options.sessionManager instanceof SessionManager))
@@ -235,31 +256,53 @@ function createRuntimeWithOneShotSessionOptions(createRuntime, initialModel, ini
235
256
  return createRuntime({
236
257
  ...options,
237
258
  ...(model === undefined ? {} : { initialModel: model }),
238
- ...(thinkingLevel === undefined ? {} : { initialThinkingLevel: thinkingLevel }),
239
- ...(toolsEnabled === undefined ? {} : { delegationToolsEnabled: toolsEnabled }),
259
+ ...(thinkingLevel === undefined
260
+ ? {}
261
+ : { initialThinkingLevel: thinkingLevel }),
262
+ ...(toolsEnabled === undefined
263
+ ? {}
264
+ : { delegationToolsEnabled: toolsEnabled }),
240
265
  });
241
266
  };
242
267
  }
243
- export function createPiWebUiCustomToolDefinitions(cwd, delegationEnabled, spawn, subsessions) {
268
+ export function createPiWebUiCustomToolDefinitions(cwd, delegationEnabled, spawn, subsessions, modelPolicy) {
244
269
  return [
245
270
  createPiWebUiEditToolDefinition(cwd),
246
- ...(delegationEnabled && spawn !== undefined ? [createSpawnSessionToolDefinition(cwd, { spawn })] : []),
247
- ...(delegationEnabled && subsessions !== undefined ? createSubsessionToolDefinitions(cwd, subsessions) : []),
271
+ ...(delegationEnabled && spawn !== undefined
272
+ ? [createSpawnSessionToolDefinition(cwd, { spawn })]
273
+ : []),
274
+ ...(delegationEnabled && subsessions !== undefined
275
+ ? createSubsessionToolDefinitions(cwd, subsessions)
276
+ : []),
277
+ // Paired with the subsession tools: the policy view exists so a dispatching
278
+ // agent can verify tier dispatch before using it, so it is pointless without
279
+ // the dispatch capability and misleading if advertised separately.
280
+ ...(delegationEnabled &&
281
+ subsessions !== undefined &&
282
+ modelPolicy !== undefined
283
+ ? [createModelPolicyToolDefinition(modelPolicy)]
284
+ : []),
248
285
  ];
249
286
  }
250
- function createDefaultRuntimeFactory(modelRuntime, sessionManagers, spawn, subsessions) {
251
- return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled }) => {
252
- const services = await createAgentSessionServices({ cwd, agentDir, modelRuntime });
253
- const resolvedDelegationToolsEnabled = delegationToolsEnabled
254
- ?? await sessionAllowsDelegationTools(sessionManager, sessionManagers);
255
- const customTools = createPiWebUiCustomToolDefinitions(cwd, resolvedDelegationToolsEnabled, spawn, subsessions);
287
+ function createDefaultRuntimeFactory(modelRuntime, sessionManagers, spawn, subsessions, modelPolicy) {
288
+ return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, initialThinkingLevel, delegationToolsEnabled, }) => {
289
+ const services = await createAgentSessionServices({
290
+ cwd,
291
+ agentDir,
292
+ modelRuntime,
293
+ });
294
+ const resolvedDelegationToolsEnabled = delegationToolsEnabled ??
295
+ (await sessionAllowsDelegationTools(sessionManager, sessionManagers));
296
+ const customTools = createPiWebUiCustomToolDefinitions(cwd, resolvedDelegationToolsEnabled, spawn, subsessions, modelPolicy);
256
297
  const result = await createAgentSessionFromServices({
257
298
  services,
258
299
  sessionManager,
259
300
  customTools,
260
301
  ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
261
302
  ...(initialModel === undefined ? {} : { model: initialModel }),
262
- ...(initialThinkingLevel === undefined ? {} : { thinkingLevel: initialThinkingLevel }),
303
+ ...(initialThinkingLevel === undefined
304
+ ? {}
305
+ : { thinkingLevel: initialThinkingLevel }),
263
306
  });
264
307
  return { ...result, services, diagnostics: services.diagnostics };
265
308
  };
@@ -270,16 +313,29 @@ function createPiWebUiEditToolDefinition(cwd) {
270
313
  name: editTool.name,
271
314
  label: editTool.label,
272
315
  description: editTool.description,
273
- ...(editTool.promptSnippet === undefined ? {} : { promptSnippet: editTool.promptSnippet }),
274
- ...(editTool.promptGuidelines === undefined ? {} : { promptGuidelines: editTool.promptGuidelines }),
316
+ ...(editTool.promptSnippet === undefined
317
+ ? {}
318
+ : { promptSnippet: editTool.promptSnippet }),
319
+ ...(editTool.promptGuidelines === undefined
320
+ ? {}
321
+ : { promptGuidelines: editTool.promptGuidelines }),
275
322
  parameters: editTool.parameters,
276
- ...(editTool.renderShell === undefined ? {} : { renderShell: editTool.renderShell }),
277
- ...(editTool.prepareArguments === undefined ? {} : { prepareArguments: editTool.prepareArguments }),
278
- ...(editTool.executionMode === undefined ? {} : { executionMode: editTool.executionMode }),
323
+ ...(editTool.renderShell === undefined
324
+ ? {}
325
+ : { renderShell: editTool.renderShell }),
326
+ ...(editTool.prepareArguments === undefined
327
+ ? {}
328
+ : { prepareArguments: editTool.prepareArguments }),
329
+ ...(editTool.executionMode === undefined
330
+ ? {}
331
+ : { executionMode: editTool.executionMode }),
279
332
  async execute(toolCallId, params, signal, onUpdate, ctx) {
280
333
  const preview = await computeEditPreview(params.path, params.edits, cwd);
281
334
  if (signal?.aborted !== true) {
282
- onUpdate?.({ content: [{ type: "text", text: "Edit preview computed." }], details: { preview } });
335
+ onUpdate?.({
336
+ content: [{ type: "text", text: "Edit preview computed." }],
337
+ details: { preview },
338
+ });
283
339
  }
284
340
  return editTool.execute(toolCallId, params, signal, onUpdate, ctx);
285
341
  },
@@ -367,34 +423,56 @@ export class PiSessionService {
367
423
  this.spawnTargets = deps.spawnTargets;
368
424
  this.logger = deps.logger ?? noopLogger;
369
425
  this.now = deps.now ?? (() => new Date());
370
- this.notificationStore = deps.notificationStore ?? new SessionNotificationStore();
426
+ this.notificationStore =
427
+ deps.notificationStore ?? new SessionNotificationStore();
371
428
  this.unreadStore = deps.unreadStore ?? new SessionUnreadStore();
372
429
  this.unreadPublicationRetryInitialMs = Math.max(0, deps.unreadPublicationRetryDelayMs ?? DEFAULT_UNREAD_PUBLICATION_RETRY_MS);
373
430
  this.unreadPublicationRetryDelayMs = this.unreadPublicationRetryInitialMs;
374
431
  // Subsessions are a beta capability gated behind their own flag, and they
375
432
  // also require the spawn capability (they share its project-scope resolver).
376
433
  const subsessionsActive = this.spawnTargets !== undefined && deps.subsessionsEnabled === true;
377
- this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.modelRuntime, this.sessionManager, this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
378
- spawn: (input) => this.spawnSubsession(input),
379
- list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
380
- check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
381
- read: (parentSessionId, sessionId, query, parentSessionFile) => this.readSubsession(parentSessionId, sessionId, query, parentSessionFile),
382
- });
383
- this.createAgentRuntime = deps.createAgentRuntime ?? defaultCreateAgentRuntime;
384
- this.modelTierRegistry = deps.modelTierRegistry ?? createModelTierRegistry({
385
- loadConfig: () => {
386
- const loaded = loadPiWebUiConfig();
387
- return {
388
- ...(loaded.config.modelTiers === undefined ? {} : { modelTiers: loaded.config.modelTiers }),
389
- ...(loaded.modelTiersError === undefined ? {} : { modelTiersError: loaded.modelTiersError }),
390
- };
391
- },
392
- models: () => this.modelRuntime.getAvailableSnapshot(),
393
- supportedThinkingLevels: runtimeThinkingLevels,
394
- });
434
+ this.createRuntime =
435
+ deps.createRuntime ??
436
+ createDefaultRuntimeFactory(this.modelRuntime, this.sessionManager, this.spawnTargets === undefined
437
+ ? undefined
438
+ : (input) => this.spawnSession(input), !subsessionsActive
439
+ ? undefined
440
+ : {
441
+ spawn: (input) => this.spawnSubsession(input),
442
+ list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
443
+ check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
444
+ read: (parentSessionId, sessionId, query, parentSessionFile) => this.readSubsession(parentSessionId, sessionId, query, parentSessionFile),
445
+ }, !subsessionsActive
446
+ ? undefined
447
+ : {
448
+ inspect: (sessionId) => this.modelPolicyCapability(sessionId),
449
+ });
450
+ this.createAgentRuntime =
451
+ deps.createAgentRuntime ?? defaultCreateAgentRuntime;
452
+ this.modelTierRegistry =
453
+ deps.modelTierRegistry ??
454
+ createModelTierRegistry({
455
+ loadConfig: () => {
456
+ const loaded = loadPiWebUiConfig();
457
+ return {
458
+ ...(loaded.config.modelTiers === undefined
459
+ ? {}
460
+ : { modelTiers: loaded.config.modelTiers }),
461
+ ...(loaded.modelTiersError === undefined
462
+ ? {}
463
+ : { modelTiersError: loaded.modelTiersError }),
464
+ };
465
+ },
466
+ models: () => this.modelRuntime.getAvailableSnapshot(),
467
+ supportedThinkingLevels: runtimeThinkingLevels,
468
+ });
395
469
  this.workspaceActivity = deps.workspaceActivity;
396
- this.heartbeat = setInterval(() => { this.publishHeartbeats(); }, deps.heartbeatIntervalMs ?? 2000);
397
- this.commandService = new SessionCommandService((sessionId) => this.getActive(sessionId), (sessionId, text) => this.prompt(sessionId, text, undefined, undefined, { echoUserMessage: false }), events, {
470
+ this.heartbeat = setInterval(() => {
471
+ this.publishHeartbeats();
472
+ }, deps.heartbeatIntervalMs ?? 2000);
473
+ this.commandService = new SessionCommandService((sessionId) => this.getActive(sessionId), (sessionId, text) => this.prompt(sessionId, text, undefined, undefined, {
474
+ echoUserMessage: false,
475
+ }), events, {
398
476
  onCompactionStart: (session) => {
399
477
  this.beginSessionEntryMutation(session, "compact the session");
400
478
  this.publishActivity(session, "compacting", "active");
@@ -407,7 +485,8 @@ export class PiSessionService {
407
485
  },
408
486
  reloadSession: (session) => this.reloadSessionRuntime(session),
409
487
  getSessionTree: (session) => {
410
- if (typeof session.sessionManager.getTree !== "function" || typeof session.navigateTree !== "function")
488
+ if (typeof session.sessionManager.getTree !== "function" ||
489
+ typeof session.navigateTree !== "function")
411
490
  return undefined;
412
491
  return projectSessionTree(session.sessionManager.getTree(), session.sessionManager.getLeafId());
413
492
  },
@@ -452,7 +531,9 @@ export class PiSessionService {
452
531
  }
453
532
  async cleanup(request) {
454
533
  const plan = await this.cleanupPlan(request);
455
- if (plan.deleteRecords.length > 0 && this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
534
+ if (plan.deleteRecords.length > 0 &&
535
+ this.archiveStore.deleteArchived === undefined &&
536
+ this.archiveStore.deleteArchivedMany === undefined)
456
537
  throw new Error("Archive store does not support deletion");
457
538
  const archiveInputs = [];
458
539
  const readyArchiveInputs = [];
@@ -464,7 +545,10 @@ export class PiSessionService {
464
545
  skippedBusySessionIds.add(input.sessionId);
465
546
  continue;
466
547
  }
467
- await this.closeActive(input.sessionId, { kind: "clear", reason: "archive" });
548
+ await this.closeActive(input.sessionId, {
549
+ kind: "clear",
550
+ reason: "archive",
551
+ });
468
552
  readyArchiveInputs.push(input);
469
553
  }
470
554
  await this.archiveStoreArchiveMany(readyArchiveInputs);
@@ -475,7 +559,10 @@ export class PiSessionService {
475
559
  skippedBusySessionIds.add(record.sessionId);
476
560
  continue;
477
561
  }
478
- await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" });
562
+ await this.closeActive(record.sessionId, {
563
+ kind: "clear",
564
+ reason: "delete",
565
+ });
479
566
  readyDeleteRecords.push(record);
480
567
  }
481
568
  await this.ensureArchivedRecordsMoved(readyDeleteRecords);
@@ -491,7 +578,8 @@ export class PiSessionService {
491
578
  });
492
579
  }
493
580
  async forceCleanup() {
494
- if (this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
581
+ if (this.archiveStore.deleteArchived === undefined &&
582
+ this.archiveStore.deleteArchivedMany === undefined)
495
583
  throw new Error("Archive store does not support deletion");
496
584
  const archivedRecords = await this.archiveStore.list();
497
585
  const plan = planForceCleanup({
@@ -506,7 +594,10 @@ export class PiSessionService {
506
594
  skippedBusySessionIds.add(record.sessionId);
507
595
  continue;
508
596
  }
509
- await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" });
597
+ await this.closeActive(record.sessionId, {
598
+ kind: "clear",
599
+ reason: "delete",
600
+ });
510
601
  readyDeleteRecords.push(record);
511
602
  }
512
603
  await this.ensureArchivedRecordsMoved(readyDeleteRecords);
@@ -587,14 +678,26 @@ export class PiSessionService {
587
678
  }
588
679
  async start(cwd, options) {
589
680
  const modelPolicy = options?.modelPolicy;
590
- return this.startSession(cwd, { initializeModelPolicy: modelPolicy ?? true });
681
+ return this.startSession(cwd, {
682
+ initializeModelPolicy: modelPolicy ?? true,
683
+ });
591
684
  }
592
685
  async startSession(cwd, options) {
593
- const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined ? undefined : { parentSession: options.parentSession }), cwd, {
594
- ...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
595
- ...(options.initialThinkingLevel === undefined ? {} : { initialThinkingLevel: options.initialThinkingLevel }),
596
- ...(options.creationProvenance === undefined ? {} : { creationProvenance: options.creationProvenance }),
597
- ...(options.initializeModelPolicy === undefined ? {} : { initializeModelPolicy: options.initializeModelPolicy }),
686
+ const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined
687
+ ? undefined
688
+ : { parentSession: options.parentSession }), cwd, {
689
+ ...(options.initialModel === undefined
690
+ ? {}
691
+ : { initialModel: options.initialModel }),
692
+ ...(options.initialThinkingLevel === undefined
693
+ ? {}
694
+ : { initialThinkingLevel: options.initialThinkingLevel }),
695
+ ...(options.creationProvenance === undefined
696
+ ? {}
697
+ : { creationProvenance: options.creationProvenance }),
698
+ ...(options.initializeModelPolicy === undefined
699
+ ? {}
700
+ : { initializeModelPolicy: options.initializeModelPolicy }),
598
701
  });
599
702
  const { session } = active.runtime;
600
703
  const created = {
@@ -608,7 +711,9 @@ export class PiSessionService {
608
711
  firstMessage: "",
609
712
  // Include the parent so listeners can nest the new session in the tree
610
713
  // immediately, instead of showing it flat until the next reload.
611
- ...(options.parentSession === undefined ? {} : { parentSessionPath: options.parentSession }),
714
+ ...(options.parentSession === undefined
715
+ ? {}
716
+ : { parentSessionPath: options.parentSession }),
612
717
  };
613
718
  // Broadcast so other clients (and the spawning agent's UI) can add the new
614
719
  // session to their list without a manual reload.
@@ -628,7 +733,12 @@ export class PiSessionService {
628
733
  throw spawnTargetError(decision);
629
734
  const created = await this.startSession(decision.cwd, input.model === undefined ? {} : { initialModel: input.model });
630
735
  await this.prompt(created.id, input.prompt);
631
- this.logger.info({ spawningCwd: input.spawningCwd, sessionId: created.id, cwd: decision.cwd, promptLength: input.prompt.length }, "spawn_session started a new session");
736
+ this.logger.info({
737
+ spawningCwd: input.spawningCwd,
738
+ sessionId: created.id,
739
+ cwd: decision.cwd,
740
+ promptLength: input.prompt.length,
741
+ }, "spawn_session started a new session");
632
742
  return { sessionId: created.id, cwd: decision.cwd };
633
743
  }
634
744
  /**
@@ -646,9 +756,9 @@ export class PiSessionService {
646
756
  let initialModel = input.model;
647
757
  let initialThinkingLevel;
648
758
  if (input.tier !== undefined) {
649
- const echoedTier = leadingTierDirective(input.prompt);
759
+ const echoedTier = leadingTierEcho(input.prompt);
650
760
  if (echoedTier !== undefined && echoedTier !== input.tier) {
651
- throw new Error(`Leading tier directive /tier-${echoedTier} disagrees with typed tier ${input.tier}`);
761
+ throw new Error(`Leading Model tier: ${echoedTier} label disagrees with typed tier ${input.tier}`);
652
762
  }
653
763
  const resolved = this.modelTierRegistry.resolve(input.tier);
654
764
  if (!isKnownThinkingLevel(resolved.thinkingLevel)) {
@@ -658,7 +768,9 @@ export class PiSessionService {
658
768
  initialThinkingLevel = resolved.thinkingLevel;
659
769
  }
660
770
  const created = await this.startSession(decision.cwd, {
661
- ...(input.parentSessionFile === undefined ? {} : { parentSession: input.parentSessionFile }),
771
+ ...(input.parentSessionFile === undefined
772
+ ? {}
773
+ : { parentSession: input.parentSessionFile }),
662
774
  ...(initialModel === undefined ? {} : { initialModel }),
663
775
  ...(initialThinkingLevel === undefined ? {} : { initialThinkingLevel }),
664
776
  creationProvenance: "tracked-subsession",
@@ -675,7 +787,12 @@ export class PiSessionService {
675
787
  this.persistSubsessionLink(link);
676
788
  this.persistSubsessionChildMarker(input.parentSessionId, created.id);
677
789
  await this.prompt(created.id, input.prompt);
678
- this.logger.info({ parentSessionId: input.parentSessionId, sessionId: created.id, cwd: decision.cwd, promptLength: input.prompt.length }, "spawn_subsession started a tracked child session");
790
+ this.logger.info({
791
+ parentSessionId: input.parentSessionId,
792
+ sessionId: created.id,
793
+ cwd: decision.cwd,
794
+ promptLength: input.prompt.length,
795
+ }, "spawn_subsession started a tracked child session");
679
796
  return { sessionId: created.id, cwd: decision.cwd };
680
797
  }
681
798
  /** Summaries of the tracked subsessions spawned by `parentSessionId`. */
@@ -686,7 +803,10 @@ export class PiSessionService {
686
803
  if (childIds === undefined)
687
804
  return [];
688
805
  const authorizedChildIds = [...childIds].filter((childId) => this.subsessionLinkBelongsToParent(parentSessionId, parentFile, childId));
689
- return Promise.all(authorizedChildIds.map(async (childId) => ({ sessionId: childId, ...(await this.subsessionSummaryFields(childId)) })));
806
+ return Promise.all(authorizedChildIds.map(async (childId) => ({
807
+ sessionId: childId,
808
+ ...(await this.subsessionSummaryFields(childId)),
809
+ })));
690
810
  }
691
811
  /** Status and final result of a subsession, scoped to the caller's children. */
692
812
  async checkSubsession(parentSessionId, sessionId, parentSessionFile) {
@@ -715,7 +835,8 @@ export class PiSessionService {
715
835
  async openSubsession(parentSessionId, sessionId, parentSessionFile) {
716
836
  const parentFile = nonEmptyString(parentSessionFile);
717
837
  await this.hydrateSubsessionsForParent(parentSessionId, parentFile);
718
- if (this.subsessionParents.get(sessionId) !== parentSessionId || !this.subsessionLinkBelongsToParent(parentSessionId, parentFile, sessionId)) {
838
+ if (this.subsessionParents.get(sessionId) !== parentSessionId ||
839
+ !this.subsessionLinkBelongsToParent(parentSessionId, parentFile, sessionId)) {
719
840
  throw new Error(`Session ${sessionId} is not one of your subsessions`);
720
841
  }
721
842
  return this.getOrOpenTrackedSubsession(sessionId);
@@ -724,19 +845,24 @@ export class PiSessionService {
724
845
  const link = this.subsessionLinks.get(childSessionId);
725
846
  if (link?.parentSessionId !== parentSessionId)
726
847
  return false;
727
- return parentSessionFile === undefined || trackedLinkParentFileMatches(link, parentSessionFile);
848
+ return (parentSessionFile === undefined ||
849
+ trackedLinkParentFileMatches(link, parentSessionFile));
728
850
  }
729
851
  activeChildForSubsessionLink(link) {
730
852
  const active = this.active.get(link.childSessionId);
731
853
  if (active === undefined)
732
854
  return undefined;
733
- return activeSessionFileMatches(active, link.childSessionFile) ? active : undefined;
855
+ return activeSessionFileMatches(active, link.childSessionFile)
856
+ ? active
857
+ : undefined;
734
858
  }
735
859
  activeParentForSubsessionLink(link) {
736
860
  const active = this.active.get(link.parentSessionId);
737
861
  if (active === undefined)
738
862
  return undefined;
739
- return activeSessionFileMatches(active, link.parentSessionFile) ? active : undefined;
863
+ return activeSessionFileMatches(active, link.parentSessionFile)
864
+ ? active
865
+ : undefined;
740
866
  }
741
867
  subsessionLinkForActiveChild(session) {
742
868
  const childId = session.sessionId;
@@ -744,12 +870,15 @@ export class PiSessionService {
744
870
  const link = this.subsessionLinks.get(childId);
745
871
  if (parentId === undefined || link?.parentSessionId !== parentId)
746
872
  return undefined;
747
- return sessionFileMatches(session, link.childSessionFile) ? link : undefined;
873
+ return sessionFileMatches(session, link.childSessionFile)
874
+ ? link
875
+ : undefined;
748
876
  }
749
877
  async registerVerifiedSubsession(link) {
750
878
  const { childSessionId, parentSessionId } = link;
751
879
  const previousParentId = this.subsessionParents.get(childSessionId);
752
- if (previousParentId !== undefined && previousParentId !== parentSessionId) {
880
+ if (previousParentId !== undefined &&
881
+ previousParentId !== parentSessionId) {
753
882
  const previousChildren = this.subsessionChildren.get(previousParentId);
754
883
  previousChildren?.delete(childSessionId);
755
884
  if (previousChildren?.size === 0)
@@ -803,7 +932,11 @@ export class PiSessionService {
803
932
  parent.sessionManager.appendCustomEntry(SUBSESSION_LINK_CUSTOM_TYPE, persistedParentSubsessionLinkData(link));
804
933
  }
805
934
  catch (error) {
806
- this.logger.info({ parentSessionId: link.parentSessionId, sessionId: link.childSessionId, error: error instanceof Error ? error.message : String(error) }, "failed to persist subsession link");
935
+ this.logger.info({
936
+ parentSessionId: link.parentSessionId,
937
+ sessionId: link.childSessionId,
938
+ error: error instanceof Error ? error.message : String(error),
939
+ }, "failed to persist subsession link");
807
940
  }
808
941
  }
809
942
  persistSubsessionChildMarker(parentSessionId, childSessionId) {
@@ -816,7 +949,11 @@ export class PiSessionService {
816
949
  child.sessionManager.appendCustomEntry(SUBSESSION_CHILD_LINK_CUSTOM_TYPE, persistedChildSubsessionLinkData(parentSessionId, childSessionId));
817
950
  }
818
951
  catch (error) {
819
- this.logger.info({ parentSessionId, sessionId: childSessionId, error: error instanceof Error ? error.message : String(error) }, "failed to persist subsession child marker");
952
+ this.logger.info({
953
+ parentSessionId,
954
+ sessionId: childSessionId,
955
+ error: error instanceof Error ? error.message : String(error),
956
+ }, "failed to persist subsession child marker");
820
957
  }
821
958
  }
822
959
  async hydrateSubsessionsForParent(parentSessionId, parentSessionFile) {
@@ -824,7 +961,9 @@ export class PiSessionService {
824
961
  if (this.subsessionHydratedParents.has(hydrationKey))
825
962
  return;
826
963
  const activeParent = this.active.get(parentSessionId);
827
- if (activeParent !== undefined && (parentSessionFile === undefined || activeSessionFileMatches(activeParent, parentSessionFile))) {
964
+ if (activeParent !== undefined &&
965
+ (parentSessionFile === undefined ||
966
+ activeSessionFileMatches(activeParent, parentSessionFile))) {
828
967
  const activeParentFile = nonEmptyString(activeParent.runtime.session.sessionFile);
829
968
  const complete = await this.registerPersistedSubsessionLinks(parentSessionId, activeParent.runtime.session.sessionManager, activeParentFile);
830
969
  if (complete)
@@ -833,7 +972,8 @@ export class PiSessionService {
833
972
  }
834
973
  if (parentSessionFile === undefined)
835
974
  return;
836
- if ((await readSessionHeaderSummary(parentSessionFile))?.id !== parentSessionId)
975
+ if ((await readSessionHeaderSummary(parentSessionFile))?.id !==
976
+ parentSessionId)
837
977
  return;
838
978
  let parentManager;
839
979
  try {
@@ -875,8 +1015,11 @@ export class PiSessionService {
875
1015
  return trackedSubsessionLinkFromParentLink(parentSessionId, link, parentSessionFile);
876
1016
  }
877
1017
  async parentLinkHasValidChildTarget(parentSessionFile, link) {
878
- return link.spawnedSessionFile !== undefined
879
- && await sessionFileHeaderMatches(link.spawnedSessionFile, { sessionId: link.spawnedSessionId, parentSessionFile });
1018
+ return (link.spawnedSessionFile !== undefined &&
1019
+ (await sessionFileHeaderMatches(link.spawnedSessionFile, {
1020
+ sessionId: link.spawnedSessionId,
1021
+ parentSessionFile,
1022
+ })));
880
1023
  }
881
1024
  async recoverSubsessionTrackingForOpenedSession(session) {
882
1025
  const link = await this.verifiedSubsessionLinkFromOpenedChild(session);
@@ -900,7 +1043,10 @@ export class PiSessionService {
900
1043
  if (active !== undefined)
901
1044
  return active.runtime.session;
902
1045
  if (link.childSessionFile !== undefined) {
903
- if (!(await sessionFileHeaderMatches(link.childSessionFile, { sessionId, parentSessionFile: link.parentSessionFile })))
1046
+ if (!(await sessionFileHeaderMatches(link.childSessionFile, {
1047
+ sessionId,
1048
+ parentSessionFile: link.parentSessionFile,
1049
+ })))
904
1050
  throw new Error("Session not found");
905
1051
  const sessionManager = this.sessionManager.open(link.childSessionFile);
906
1052
  return (await this.create(sessionManager, link.cwd ?? sessionManager.getCwd())).runtime.session;
@@ -911,9 +1057,16 @@ export class PiSessionService {
911
1057
  const link = this.subsessionLinks.get(childSessionId);
912
1058
  const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
913
1059
  if (active !== undefined) {
914
- return { cwd: active.runtime.cwd, status: this.subsessionStatus(active.runtime.session) };
1060
+ return {
1061
+ cwd: active.runtime.cwd,
1062
+ status: this.subsessionStatus(active.runtime.session),
1063
+ };
915
1064
  }
916
- if (link?.childSessionFile !== undefined && (await sessionFileHeaderMatches(link.childSessionFile, { sessionId: childSessionId, parentSessionFile: link.parentSessionFile }))) {
1065
+ if (link?.childSessionFile !== undefined &&
1066
+ (await sessionFileHeaderMatches(link.childSessionFile, {
1067
+ sessionId: childSessionId,
1068
+ parentSessionFile: link.parentSessionFile,
1069
+ }))) {
917
1070
  return { cwd: link.cwd ?? "", status: "idle" };
918
1071
  }
919
1072
  if (link?.cwd !== undefined)
@@ -933,7 +1086,9 @@ export class PiSessionService {
933
1086
  return [];
934
1087
  return [...childIds].filter((childId) => {
935
1088
  const link = this.subsessionLinks.get(childId);
936
- const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
1089
+ const active = link === undefined
1090
+ ? undefined
1091
+ : this.activeChildForSubsessionLink(link);
937
1092
  return active !== undefined && this.hasActiveWork(active.runtime.session);
938
1093
  });
939
1094
  }
@@ -975,11 +1130,13 @@ export class PiSessionService {
975
1130
  const parentSessionFile = link.parentSessionFile;
976
1131
  if (parentSessionFile === undefined)
977
1132
  throw new Error(`Parent session ${parentSessionId} is not available for subsession notification`);
978
- if ((await readSessionHeaderSummary(parentSessionFile))?.id !== parentSessionId) {
1133
+ if ((await readSessionHeaderSummary(parentSessionFile))?.id !==
1134
+ parentSessionId) {
979
1135
  throw new Error(`Parent session ${parentSessionId} is not available for subsession notification`);
980
1136
  }
981
1137
  const sessionManager = this.sessionManager.open(parentSessionFile);
982
- return (await this.create(sessionManager, sessionManager.getCwd())).runtime.session;
1138
+ return (await this.create(sessionManager, sessionManager.getCwd())).runtime
1139
+ .session;
983
1140
  }
984
1141
  /**
985
1142
  * Deliver a subsession-completion notice to the parent as a system-authored
@@ -997,18 +1154,31 @@ export class PiSessionService {
997
1154
  this.deferredSubsessionNotifications.set(session, pending);
998
1155
  return;
999
1156
  }
1000
- await this.deliverSubsessionNotification(session, { parentId, childId, text });
1157
+ await this.deliverSubsessionNotification(session, {
1158
+ parentId,
1159
+ childId,
1160
+ text,
1161
+ });
1001
1162
  }
1002
1163
  catch (error) {
1003
1164
  this.logSubsessionNotificationFailure(parentId, childId, error);
1004
1165
  }
1005
1166
  }
1006
1167
  async deliverSubsessionNotification(session, notification) {
1007
- await this.runSessionEntryMutation(session, "deliver a subsession notification", () => session.sendCustomMessage({ customType: SUBSESSION_NOTIFICATION_CUSTOM_TYPE, content: notification.text, display: true, details: { sessionId: notification.childId } }, { triggerTurn: true, deliverAs: "followUp" }));
1168
+ await this.runSessionEntryMutation(session, "deliver a subsession notification", () => session.sendCustomMessage({
1169
+ customType: SUBSESSION_NOTIFICATION_CUSTOM_TYPE,
1170
+ content: notification.text,
1171
+ display: true,
1172
+ details: { sessionId: notification.childId },
1173
+ }, { triggerTurn: true, deliverAs: "followUp" }));
1008
1174
  this.publishStatus(session);
1009
1175
  }
1010
1176
  logSubsessionNotificationFailure(parentId, childId, error) {
1011
- this.logger.info({ parentSessionId: parentId, sessionId: childId, error: error instanceof Error ? error.message : String(error) }, "failed to notify parent of subsession completion");
1177
+ this.logger.info({
1178
+ parentSessionId: parentId,
1179
+ sessionId: childId,
1180
+ error: error instanceof Error ? error.message : String(error),
1181
+ }, "failed to notify parent of subsession completion");
1012
1182
  }
1013
1183
  async exportHistory(ref) {
1014
1184
  return exportSessionHistoryHtml(await this.sessionFileForHistory(ref));
@@ -1048,7 +1218,9 @@ export class PiSessionService {
1048
1218
  const current = inspection.kind === "invalid" ? inspection.fallback : inspection.policy;
1049
1219
  const policy = await this.applySessionModelPolicy(session, current, update, {
1050
1220
  action,
1051
- onTransitionFailure: (reason) => { this.publishModelPolicyTransitionFailure(session, reason); },
1221
+ onTransitionFailure: (reason) => {
1222
+ this.publishModelPolicyTransitionFailure(session, reason);
1223
+ },
1052
1224
  });
1053
1225
  const resolved = this.exactSelectionFromSession(session);
1054
1226
  this.publishActivity(session, `model policy: ${policy.mode}`, "idle", policy.mode === "tiered" ? policy.tier : resolved.model.id);
@@ -1095,8 +1267,7 @@ export class PiSessionService {
1095
1267
  const candidates = session.scopedModels.length > 0
1096
1268
  ? session.scopedModels.map((scoped) => scoped.model)
1097
1269
  : session.modelRuntime.getAvailableSnapshot();
1098
- const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId)
1099
- ?? session.modelRuntime.getModel(provider, modelId);
1270
+ const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId) ?? session.modelRuntime.getModel(provider, modelId);
1100
1271
  if (model === undefined)
1101
1272
  throw new Error(`Model not found: ${provider}/${modelId}`);
1102
1273
  await this.runExactModelPolicyMutation(session, "change models", currentPolicy, () => session.setModel(model));
@@ -1111,7 +1282,9 @@ export class PiSessionService {
1111
1282
  const result = await this.runExactModelPolicyMutation(session, "change models", currentPolicy, async () => {
1112
1283
  const cycled = await session.cycleModel(direction);
1113
1284
  if (cycled === undefined) {
1114
- throw new Error(session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available");
1285
+ throw new Error(session.scopedModels.length > 0
1286
+ ? "Only one model in scope"
1287
+ : "Only one model available");
1115
1288
  }
1116
1289
  return cycled;
1117
1290
  });
@@ -1159,13 +1332,31 @@ export class PiSessionService {
1159
1332
  const session = await this.getOrOpen(ref);
1160
1333
  const commands = [...BUILTIN_COMMANDS];
1161
1334
  for (const command of session.extensionRunner.getRegisteredCommands()) {
1162
- commands.push({ name: command.invocationName, ...(command.description === undefined ? {} : { description: command.description }), source: "extension" });
1335
+ commands.push({
1336
+ name: command.invocationName,
1337
+ ...(command.description === undefined
1338
+ ? {}
1339
+ : { description: command.description }),
1340
+ source: "extension",
1341
+ });
1163
1342
  }
1164
1343
  for (const template of session.promptTemplates) {
1165
- commands.push({ name: template.name, ...(template.description === undefined ? {} : { description: template.description }), source: "prompt" });
1344
+ commands.push({
1345
+ name: template.name,
1346
+ ...(template.description === undefined
1347
+ ? {}
1348
+ : { description: template.description }),
1349
+ source: "prompt",
1350
+ });
1166
1351
  }
1167
1352
  for (const skill of session.resourceLoader.getSkills().skills) {
1168
- commands.push({ name: `skill:${skill.name}`, ...(skill.description === undefined ? {} : { description: skill.description }), source: "skill" });
1353
+ commands.push({
1354
+ name: `skill:${skill.name}`,
1355
+ ...(skill.description === undefined
1356
+ ? {}
1357
+ : { description: skill.description }),
1358
+ source: "skill",
1359
+ });
1169
1360
  }
1170
1361
  return commands.sort((a, b) => a.name.localeCompare(b.name));
1171
1362
  }
@@ -1177,7 +1368,9 @@ export class PiSessionService {
1177
1368
  // as a transient line that vanishes on reload.
1178
1369
  const echoUserMessage = options?.echoUserMessage !== false;
1179
1370
  const requestedBehavior = parsePromptStreamingBehavior(streamingBehavior);
1180
- const parsedAttachments = parsePromptAttachments(attachments, { enforceInlineSizeLimit: false });
1371
+ const parsedAttachments = parsePromptAttachments(attachments, {
1372
+ enforceInlineSizeLimit: false,
1373
+ });
1181
1374
  const images = (await attachmentsToInlineImages(parsedAttachments)).map((entry) => entry.image);
1182
1375
  await this.assertWritable(ref);
1183
1376
  const session = await this.getOrOpen(ref);
@@ -1186,7 +1379,9 @@ export class PiSessionService {
1186
1379
  this.maybeGenerateSessionName(session, promptText);
1187
1380
  const isQueued = session.isStreaming || session.isCompacting;
1188
1381
  const behavior = isQueued ? requestedBehavior ?? "followUp" : undefined;
1189
- if (isQueued && images.length === 0 && this.hasQueuedMessageText(session, promptText)) {
1382
+ if (isQueued &&
1383
+ images.length === 0 &&
1384
+ this.hasQueuedMessageText(session, promptText)) {
1190
1385
  this.publishActivity(session, "duplicate queued message ignored", "active");
1191
1386
  this.publishStatus(session);
1192
1387
  return;
@@ -1217,24 +1412,42 @@ export class PiSessionService {
1217
1412
  catch (error) {
1218
1413
  const message = error instanceof Error ? error.message : String(error);
1219
1414
  this.publishActivity(session, "error", "error", message);
1220
- this.events.publish(session.sessionId, { type: "session.error", message });
1415
+ this.events.publish(session.sessionId, {
1416
+ type: "session.error",
1417
+ message,
1418
+ });
1221
1419
  return Promise.resolve();
1222
1420
  }
1223
- this.publishActivity(session, behavior === "steer" ? "steering queued" : behavior === "followUp" ? "message queued" : "prompt accepted", "active");
1421
+ this.publishActivity(session, behavior === "steer"
1422
+ ? "steering queued"
1423
+ : behavior === "followUp"
1424
+ ? "message queued"
1425
+ : "prompt accepted", "active");
1224
1426
  if (behavior === undefined && echoUserMessage)
1225
- this.events.publish(session.sessionId, { type: "message.append", message: userMessage(text, images) });
1427
+ this.events.publish(session.sessionId, {
1428
+ type: "message.append",
1429
+ message: userMessage(text, images),
1430
+ });
1226
1431
  const promptOptions = buildPromptOptions(behavior, images);
1227
1432
  const promptPromise = this.runSessionEntryMutation(session, "send a prompt", () => session.prompt(text, promptOptions)).catch((error) => {
1228
1433
  const message = error instanceof Error ? error.message : String(error);
1229
1434
  this.publishActivity(session, "error", "error", message);
1230
- this.events.publish(session.sessionId, { type: "session.error", message });
1435
+ this.events.publish(session.sessionId, {
1436
+ type: "session.error",
1437
+ message,
1438
+ });
1231
1439
  });
1232
1440
  void promptPromise;
1233
1441
  return promptPromise;
1234
1442
  }
1235
1443
  enqueuePromptDuringCompaction(session, text, kind, images = [], echoUserMessage = true) {
1236
1444
  const queue = this.compactionPromptQueues.get(session.sessionId) ?? [];
1237
- queue.push({ kind, text, ...(images.length > 0 ? { images } : {}), ...(echoUserMessage ? {} : { echoUserMessage: false }) });
1445
+ queue.push({
1446
+ kind,
1447
+ text,
1448
+ ...(images.length > 0 ? { images } : {}),
1449
+ ...(echoUserMessage ? {} : { echoUserMessage: false }),
1450
+ });
1238
1451
  this.compactionPromptQueues.set(session.sessionId, queue);
1239
1452
  this.publishActivity(session, "message queued during compaction", "active");
1240
1453
  this.publishStatus(session);
@@ -1247,7 +1460,12 @@ export class PiSessionService {
1247
1460
  */
1248
1461
  retainPromptDuringModelPolicyMutation(session, text, kind, images = [], echoUserMessage = true, position = "back") {
1249
1462
  const queue = this.compactionPromptQueues.get(session.sessionId) ?? [];
1250
- const queued = { kind, text, ...(images.length > 0 ? { images } : {}), ...(echoUserMessage ? {} : { echoUserMessage: false }) };
1463
+ const queued = {
1464
+ kind,
1465
+ text,
1466
+ ...(images.length > 0 ? { images } : {}),
1467
+ ...(echoUserMessage ? {} : { echoUserMessage: false }),
1468
+ };
1251
1469
  if (position === "front")
1252
1470
  queue.unshift(queued);
1253
1471
  else
@@ -1257,7 +1475,10 @@ export class PiSessionService {
1257
1475
  this.publishStatus(session);
1258
1476
  }
1259
1477
  async saveAttachments(ref, attachments, folder) {
1260
- const parsed = parsePromptAttachments(attachments, { enforceInlineSizeLimit: false, allowFileAttachments: true });
1478
+ const parsed = parsePromptAttachments(attachments, {
1479
+ enforceInlineSizeLimit: false,
1480
+ allowFileAttachments: true,
1481
+ });
1261
1482
  if (parsed.length === 0)
1262
1483
  return [];
1263
1484
  await this.assertWritable(ref);
@@ -1276,26 +1497,46 @@ export class PiSessionService {
1276
1497
  if (session.isBashRunning)
1277
1498
  throw new Error("A bash command is already running");
1278
1499
  this.publishActivity(session, "running bash", "active", command);
1279
- this.events.publish(session.sessionId, { type: "shell.start", command, excludeFromContext: isExcluded });
1500
+ this.events.publish(session.sessionId, {
1501
+ type: "shell.start",
1502
+ command,
1503
+ excludeFromContext: isExcluded,
1504
+ });
1280
1505
  void this.runSessionEntryMutation(session, "run a shell command", () => session.executeBash(command, (chunk) => {
1281
- this.events.publish(session.sessionId, { type: "shell.chunk", chunk });
1506
+ this.events.publish(session.sessionId, {
1507
+ type: "shell.chunk",
1508
+ chunk,
1509
+ });
1282
1510
  this.publishActivity(session, "running bash", "active", command);
1283
1511
  this.publishStatus(session);
1284
- }, { excludeFromContext: isExcluded })).then((result) => {
1512
+ }, { excludeFromContext: isExcluded }))
1513
+ .then((result) => {
1285
1514
  this.events.publish(session.sessionId, {
1286
1515
  type: "shell.end",
1287
1516
  output: result.output,
1288
- ...(result.exitCode === undefined ? {} : { exitCode: result.exitCode }),
1517
+ ...(result.exitCode === undefined
1518
+ ? {}
1519
+ : { exitCode: result.exitCode }),
1289
1520
  cancelled: result.cancelled,
1290
1521
  truncated: result.truncated,
1291
- ...(result.fullOutputPath === undefined ? {} : { fullOutputPath: result.fullOutputPath }),
1522
+ ...(result.fullOutputPath === undefined
1523
+ ? {}
1524
+ : { fullOutputPath: result.fullOutputPath }),
1292
1525
  });
1293
1526
  this.publishActivity(session, "bash complete", result.exitCode === 0 ? "idle" : "error", command);
1294
1527
  this.publishStatus(session);
1295
- }).catch((error) => {
1528
+ })
1529
+ .catch((error) => {
1296
1530
  const message = error instanceof Error ? error.message : String(error);
1297
- this.events.publish(session.sessionId, { type: "shell.end", output: message, isError: true });
1298
- this.events.publish(session.sessionId, { type: "session.error", message });
1531
+ this.events.publish(session.sessionId, {
1532
+ type: "shell.end",
1533
+ output: message,
1534
+ isError: true,
1535
+ });
1536
+ this.events.publish(session.sessionId, {
1537
+ type: "session.error",
1538
+ message,
1539
+ });
1299
1540
  this.publishActivity(session, "bash failed", "error", message);
1300
1541
  this.publishStatus(session);
1301
1542
  });
@@ -1327,7 +1568,13 @@ export class PiSessionService {
1327
1568
  await this.assertWritable(ref);
1328
1569
  const active = await this.getActive(ref);
1329
1570
  const sourceSession = active.runtime.session;
1330
- const result = await this.runTreeExclusiveOperation([{ sessionId: sourceSession.sessionId, session: sourceSession, runtime: active.runtime }], "Stop current session activity before replacing the session", () => active.runtime.fork(entryId));
1571
+ const result = await this.runTreeExclusiveOperation([
1572
+ {
1573
+ sessionId: sourceSession.sessionId,
1574
+ session: sourceSession,
1575
+ runtime: active.runtime,
1576
+ },
1577
+ ], "Stop current session activity before replacing the session", () => active.runtime.fork(entryId));
1331
1578
  if (result.cancelled)
1332
1579
  return { cancelled: true };
1333
1580
  const session = clientSessionFromRuntime(active.runtime);
@@ -1350,7 +1597,8 @@ export class PiSessionService {
1350
1597
  // may enter this runtime until Pi's potentially asynchronous summary settles.
1351
1598
  this.treeNavigations.add(session);
1352
1599
  try {
1353
- if (expectedLeafId !== undefined && session.sessionManager.getLeafId() !== expectedLeafId) {
1600
+ if (expectedLeafId !== undefined &&
1601
+ session.sessionManager.getLeafId() !== expectedLeafId) {
1354
1602
  throw new Error("The session changed since /tree was opened. Reopen /tree and try again.");
1355
1603
  }
1356
1604
  this.publishActivity(session, options.summarize ? "summarizing branch" : "navigating session tree", "active");
@@ -1358,19 +1606,32 @@ export class PiSessionService {
1358
1606
  const result = await session.navigateTree(targetId, options);
1359
1607
  if (result.cancelled) {
1360
1608
  if (this.isCurrentActiveSession(session)) {
1361
- this.publishActivity(session, result.aborted === true ? "branch summary aborted" : "tree navigation cancelled", "idle");
1609
+ this.publishActivity(session, result.aborted === true
1610
+ ? "branch summary aborted"
1611
+ : "tree navigation cancelled", "idle");
1362
1612
  }
1363
- return { cancelled: true, ...(result.aborted === undefined ? {} : { aborted: result.aborted }) };
1613
+ return {
1614
+ cancelled: true,
1615
+ ...(result.aborted === undefined ? {} : { aborted: result.aborted }),
1616
+ };
1364
1617
  }
1365
1618
  if (this.isCurrentActiveSession(session))
1366
1619
  this.publishActivity(session, "session tree navigated", "idle");
1367
- return { cancelled: false, ...(result.editorText === undefined ? {} : { editorText: result.editorText }) };
1620
+ return {
1621
+ cancelled: false,
1622
+ ...(result.editorText === undefined
1623
+ ? {}
1624
+ : { editorText: result.editorText }),
1625
+ };
1368
1626
  }
1369
1627
  catch (error) {
1370
1628
  const message = error instanceof Error ? error.message : String(error);
1371
1629
  if (this.isCurrentActiveSession(session)) {
1372
1630
  this.publishActivity(session, "tree navigation failed", "error", message);
1373
- this.events.publish(session.sessionId, { type: "session.error", message });
1631
+ this.events.publish(session.sessionId, {
1632
+ type: "session.error",
1633
+ message,
1634
+ });
1374
1635
  }
1375
1636
  throw error;
1376
1637
  }
@@ -1394,13 +1655,16 @@ export class PiSessionService {
1394
1655
  const priorGeneration = this.notificationGenerationBySession.get(session);
1395
1656
  let candidateGeneration;
1396
1657
  try {
1397
- await session.reload(priorGeneration === undefined ? undefined : {
1398
- beforeSessionStart: () => {
1399
- candidateGeneration = this.notificationStore.beginReplacement(priorGeneration, notificationIdentityForSession(session));
1400
- this.notificationGenerationBySession.set(session, candidateGeneration);
1401
- this.replaceSessionNotificationContext(session, candidateGeneration);
1402
- },
1403
- });
1658
+ await session.reload(priorGeneration === undefined
1659
+ ? undefined
1660
+ : {
1661
+ beforeSessionStart: () => {
1662
+ candidateGeneration =
1663
+ this.notificationStore.beginReplacement(priorGeneration, notificationIdentityForSession(session));
1664
+ this.notificationGenerationBySession.set(session, candidateGeneration);
1665
+ this.replaceSessionNotificationContext(session, candidateGeneration);
1666
+ },
1667
+ });
1404
1668
  if (candidateGeneration !== undefined) {
1405
1669
  this.publishNotificationMutations(this.notificationStore.commitReplacement(candidateGeneration));
1406
1670
  }
@@ -1414,7 +1678,10 @@ export class PiSessionService {
1414
1678
  }
1415
1679
  const message = error instanceof Error ? error.message : String(error);
1416
1680
  this.publishActivity(session, "reload failed", "error", message);
1417
- this.events.publish(session.sessionId, { type: "session.error", message });
1681
+ this.events.publish(session.sessionId, {
1682
+ type: "session.error",
1683
+ message,
1684
+ });
1418
1685
  this.publishStatus(session);
1419
1686
  throw error;
1420
1687
  }
@@ -1426,7 +1693,10 @@ export class PiSessionService {
1426
1693
  throw new Error("Stop current session activity before archiving");
1427
1694
  await this.runTreeExclusiveOperation([{ sessionId: session.sessionId, session }], "Stop current session activity before archiving", async () => {
1428
1695
  const archiveInput = await this.archiveInputForSession(session);
1429
- await this.closeActive(session.sessionId, { kind: "clear", reason: "archive" });
1696
+ await this.closeActive(session.sessionId, {
1697
+ kind: "clear",
1698
+ reason: "archive",
1699
+ });
1430
1700
  await this.archiveStore.archive(archiveInput);
1431
1701
  await this.forgetUnreadSessions([archiveInput]);
1432
1702
  });
@@ -1453,7 +1723,10 @@ export class PiSessionService {
1453
1723
  const listed = findListedSessionForBulkRef(sessionContext, ref);
1454
1724
  const resolvedSessionId = active?.runtime.session.sessionId ?? listed?.id ?? ref.id;
1455
1725
  if (active !== undefined && this.hasActiveWork(active.runtime.session)) {
1456
- failures.push({ sessionId: resolvedSessionId, error: "Stop current session activity before archiving" });
1726
+ failures.push({
1727
+ sessionId: resolvedSessionId,
1728
+ error: "Stop current session activity before archiving",
1729
+ });
1457
1730
  continue;
1458
1731
  }
1459
1732
  try {
@@ -1461,21 +1734,32 @@ export class PiSessionService {
1461
1734
  planItems.push({ input: archiveInputFromListEntry(listed) });
1462
1735
  }
1463
1736
  else if (active !== undefined) {
1464
- planItems.push({ input: archiveInputFromActiveSession(active.runtime.session) });
1737
+ planItems.push({
1738
+ input: archiveInputFromActiveSession(active.runtime.session),
1739
+ });
1465
1740
  }
1466
1741
  else {
1467
1742
  failures.push({ sessionId: ref.id, error: "Session not found" });
1468
1743
  }
1469
1744
  }
1470
1745
  catch (error) {
1471
- failures.push({ sessionId: resolvedSessionId, error: errorMessage(error) });
1746
+ failures.push({
1747
+ sessionId: resolvedSessionId,
1748
+ error: errorMessage(error),
1749
+ });
1472
1750
  }
1473
1751
  }
1474
1752
  const readyPlanItems = [];
1475
1753
  for (const item of planItems) {
1476
- const active = this.activeForLookup({ id: item.input.sessionId, cwd: item.input.cwd });
1754
+ const active = this.activeForLookup({
1755
+ id: item.input.sessionId,
1756
+ cwd: item.input.cwd,
1757
+ });
1477
1758
  if (active !== undefined && this.hasActiveWork(active.runtime.session)) {
1478
- failures.push({ sessionId: item.input.sessionId, error: "Stop current session activity before archiving" });
1759
+ failures.push({
1760
+ sessionId: item.input.sessionId,
1761
+ error: "Stop current session activity before archiving",
1762
+ });
1479
1763
  continue;
1480
1764
  }
1481
1765
  readyPlanItems.push(active === undefined ? item : { ...item, active });
@@ -1484,15 +1768,23 @@ export class PiSessionService {
1484
1768
  const archivedSessionIds = [...alreadyArchivedSessionIds];
1485
1769
  await this.runTreeExclusiveOperation(readyPlanItems.map(({ input, active }) => ({
1486
1770
  sessionId: input.sessionId,
1487
- ...(active === undefined ? {} : { session: active.runtime.session, runtime: active.runtime }),
1771
+ ...(active === undefined
1772
+ ? {}
1773
+ : { session: active.runtime.session, runtime: active.runtime }),
1488
1774
  })), "Stop current session activity before archiving", async () => {
1489
1775
  for (const item of readyPlanItems) {
1490
1776
  try {
1491
- await this.closeActive(item.input.sessionId, { kind: "clear", reason: "archive" });
1777
+ await this.closeActive(item.input.sessionId, {
1778
+ kind: "clear",
1779
+ reason: "archive",
1780
+ });
1492
1781
  readyInputs.push(item.input);
1493
1782
  }
1494
1783
  catch (error) {
1495
- failures.push({ sessionId: item.input.sessionId, error: errorMessage(error) });
1784
+ failures.push({
1785
+ sessionId: item.input.sessionId,
1786
+ error: errorMessage(error),
1787
+ });
1496
1788
  }
1497
1789
  }
1498
1790
  try {
@@ -1502,7 +1794,10 @@ export class PiSessionService {
1502
1794
  }
1503
1795
  catch (error) {
1504
1796
  for (const input of readyInputs)
1505
- failures.push({ sessionId: input.sessionId, error: errorMessage(error) });
1797
+ failures.push({
1798
+ sessionId: input.sessionId,
1799
+ error: errorMessage(error),
1800
+ });
1506
1801
  }
1507
1802
  });
1508
1803
  await this.forgetUnreadSessions(unreadArchivedIdentities);
@@ -1516,22 +1811,30 @@ export class PiSessionService {
1516
1811
  async archiveTree(ref) {
1517
1812
  const session = await this.getOrOpen(ref);
1518
1813
  const catalog = await this.workspaceArchiveCandidates(session.sessionManager.getCwd());
1519
- const root = findArchiveCandidateByIdOrPrefix(catalog, session.sessionId) ?? archiveCandidateFromActiveSession(session, false);
1814
+ const root = findArchiveCandidateByIdOrPrefix(catalog, session.sessionId) ??
1815
+ archiveCandidateFromActiveSession(session, false);
1520
1816
  const plan = planSessionArchiveTree(root, catalog);
1521
- const busy = plan.targets.map((target) => target.activeSession).find((target) => target !== undefined && this.hasActiveWork(target));
1817
+ const busy = plan.targets
1818
+ .map((target) => target.activeSession)
1819
+ .find((target) => target !== undefined && this.hasActiveWork(target));
1522
1820
  if (busy !== undefined)
1523
1821
  throw new Error(`Stop current session activity before archiving ${sessionDisplayName(busy)}`);
1524
1822
  const archiveInputs = plan.unarchivedTargets.map((target) => archiveInputFromCandidate(target));
1525
1823
  await this.runTreeExclusiveOperation(plan.unarchivedTargets.map((target) => ({
1526
1824
  sessionId: target.id,
1527
- ...(target.activeSession === undefined ? {} : { session: target.activeSession }),
1825
+ ...(target.activeSession === undefined
1826
+ ? {}
1827
+ : { session: target.activeSession }),
1528
1828
  })), `Stop current session activity before archiving ${sessionDisplayName(session)}`, async () => {
1529
1829
  for (const target of plan.targets) {
1530
1830
  if (target.archived)
1531
1831
  this.publishNotificationMutations(this.notificationStore.clearSession(target.id, "archive"));
1532
1832
  }
1533
1833
  for (const input of archiveInputs)
1534
- await this.closeActive(input.sessionId, { kind: "clear", reason: "archive" });
1834
+ await this.closeActive(input.sessionId, {
1835
+ kind: "clear",
1836
+ reason: "archive",
1837
+ });
1535
1838
  await this.archiveStoreArchiveMany(archiveInputs);
1536
1839
  });
1537
1840
  await this.forgetUnreadSessions(plan.targets.map((target) => ({ sessionId: target.id, cwd: target.cwd })));
@@ -1546,7 +1849,10 @@ export class PiSessionService {
1546
1849
  const archived = await this.getArchived(ref);
1547
1850
  if (archived === undefined)
1548
1851
  throw new Error("Session not found");
1549
- await this.closeActive(archived.sessionId, { kind: "clear", reason: "restore" });
1852
+ await this.closeActive(archived.sessionId, {
1853
+ kind: "clear",
1854
+ reason: "restore",
1855
+ });
1550
1856
  await this.archiveStore.restore(archived.sessionId);
1551
1857
  await this.forgetUnreadSessions([archived]);
1552
1858
  }
@@ -1556,14 +1862,18 @@ export class PiSessionService {
1556
1862
  throw new Error("Archived session not found");
1557
1863
  if (this.archiveStore.deleteArchived === undefined)
1558
1864
  throw new Error("Archive store does not support deletion");
1559
- await this.closeActive(record.sessionId, { kind: "clear", reason: "delete" });
1865
+ await this.closeActive(record.sessionId, {
1866
+ kind: "clear",
1867
+ reason: "delete",
1868
+ });
1560
1869
  if (record.archivePath === undefined)
1561
1870
  await this.ensureArchivedRecordMoved(record);
1562
1871
  await this.archiveStore.deleteArchived(record.sessionId);
1563
1872
  await this.forgetUnreadSessions([record]);
1564
1873
  }
1565
1874
  async deleteArchivedMany(refs) {
1566
- if (this.archiveStore.deleteArchived === undefined && this.archiveStore.deleteArchivedMany === undefined)
1875
+ if (this.archiveStore.deleteArchived === undefined &&
1876
+ this.archiveStore.deleteArchivedMany === undefined)
1567
1877
  throw new Error("Archive store does not support deletion");
1568
1878
  const uniqueRefs = uniqueBulkSessionRefs(refs);
1569
1879
  const archivedRecords = await this.archiveStore.list();
@@ -1572,12 +1882,21 @@ export class PiSessionService {
1572
1882
  for (const ref of uniqueRefs) {
1573
1883
  const record = findArchivedRecordForBulkRef(archivedRecords, ref);
1574
1884
  if (record === undefined) {
1575
- failures.push({ sessionId: ref.id, error: "Archived session not found" });
1885
+ failures.push({
1886
+ sessionId: ref.id,
1887
+ error: "Archived session not found",
1888
+ });
1576
1889
  continue;
1577
1890
  }
1578
- const active = this.activeForLookup({ id: record.sessionId, cwd: record.cwd });
1891
+ const active = this.activeForLookup({
1892
+ id: record.sessionId,
1893
+ cwd: record.cwd,
1894
+ });
1579
1895
  if (active !== undefined && this.hasActiveWork(active.runtime.session)) {
1580
- failures.push({ sessionId: record.sessionId, error: "Stop current session activity before deleting archived session" });
1896
+ failures.push({
1897
+ sessionId: record.sessionId,
1898
+ error: "Stop current session activity before deleting archived session",
1899
+ });
1581
1900
  continue;
1582
1901
  }
1583
1902
  planItems.push({ record });
@@ -1585,11 +1904,17 @@ export class PiSessionService {
1585
1904
  const readyRecords = [];
1586
1905
  for (const item of planItems) {
1587
1906
  try {
1588
- await this.closeActive(item.record.sessionId, { kind: "clear", reason: "delete" });
1907
+ await this.closeActive(item.record.sessionId, {
1908
+ kind: "clear",
1909
+ reason: "delete",
1910
+ });
1589
1911
  readyRecords.push(item.record);
1590
1912
  }
1591
1913
  catch (error) {
1592
- failures.push({ sessionId: item.record.sessionId, error: errorMessage(error) });
1914
+ failures.push({
1915
+ sessionId: item.record.sessionId,
1916
+ error: errorMessage(error),
1917
+ });
1593
1918
  }
1594
1919
  }
1595
1920
  const moveFailures = await this.moveLegacyArchivedRecordsForDelete(readyRecords);
@@ -1625,11 +1950,19 @@ export class PiSessionService {
1625
1950
  const { sessionId, cwd } = notificationIdentityForSession(session);
1626
1951
  let candidateGeneration;
1627
1952
  try {
1628
- await this.closeActive(sessionId, priorGeneration === undefined ? CLEAR_RUNTIME_NOTIFICATIONS : DEFER_RUNTIME_NOTIFICATIONS);
1629
- candidateGeneration = priorGeneration === undefined
1630
- ? undefined
1631
- : this.notificationStore.beginReplacement(priorGeneration, { sessionId, cwd });
1632
- const reopened = await this.getActive(ref, candidateGeneration === undefined ? {} : { notificationGeneration: candidateGeneration });
1953
+ await this.closeActive(sessionId, priorGeneration === undefined
1954
+ ? CLEAR_RUNTIME_NOTIFICATIONS
1955
+ : DEFER_RUNTIME_NOTIFICATIONS);
1956
+ candidateGeneration =
1957
+ priorGeneration === undefined
1958
+ ? undefined
1959
+ : this.notificationStore.beginReplacement(priorGeneration, {
1960
+ sessionId,
1961
+ cwd,
1962
+ });
1963
+ const reopened = await this.getActive(ref, candidateGeneration === undefined
1964
+ ? {}
1965
+ : { notificationGeneration: candidateGeneration });
1633
1966
  if (candidateGeneration !== undefined) {
1634
1967
  this.publishNotificationMutations(this.notificationStore.commitReplacement(candidateGeneration));
1635
1968
  }
@@ -1652,7 +1985,9 @@ export class PiSessionService {
1652
1985
  await clearParentSession(sessionFile);
1653
1986
  clearParentSessionHeader(session.sessionManager);
1654
1987
  this.unregisterSubsession(session.sessionId);
1655
- await this.forgetUnreadSessions([{ sessionId: session.sessionId, cwd: session.sessionManager.getCwd() }]);
1988
+ await this.forgetUnreadSessions([
1989
+ { sessionId: session.sessionId, cwd: session.sessionManager.getCwd() },
1990
+ ]);
1656
1991
  }
1657
1992
  async pin(ref) {
1658
1993
  const session = await this.getOrOpen(ref);
@@ -1681,7 +2016,9 @@ export class PiSessionService {
1681
2016
  modified: new Date().toISOString(),
1682
2017
  messageCount: session.messages.length,
1683
2018
  firstMessage: "",
1684
- ...(session.sessionName === undefined ? {} : { name: session.sessionName }),
2019
+ ...(session.sessionName === undefined
2020
+ ? {}
2021
+ : { name: session.sessionName }),
1685
2022
  ...(parentSessionPath === undefined ? {} : { parentSessionPath }),
1686
2023
  ...overrides,
1687
2024
  };
@@ -1743,9 +2080,13 @@ export class PiSessionService {
1743
2080
  }
1744
2081
  const [sessionsByCwd, allSessions] = await Promise.all([
1745
2082
  this.listSessionsByCwd([...cwdSet]),
1746
- needsAllSessions ? this.sessionManager.listAll?.() ?? Promise.resolve([]) : Promise.resolve(undefined),
2083
+ needsAllSessions
2084
+ ? this.sessionManager.listAll?.() ?? Promise.resolve([])
2085
+ : Promise.resolve(undefined),
1747
2086
  ]);
1748
- return allSessions === undefined ? { sessionsByCwd } : { sessionsByCwd, allSessions };
2087
+ return allSessions === undefined
2088
+ ? { sessionsByCwd }
2089
+ : { sessionsByCwd, allSessions };
1749
2090
  }
1750
2091
  async listSessionsByCwd(cwds) {
1751
2092
  const uniqueCwds = uniqueStrings(cwds);
@@ -1782,7 +2123,10 @@ export class PiSessionService {
1782
2123
  sessionsByCwd = await this.listSessionsByCwd(legacyRecords.map((record) => record.cwd));
1783
2124
  }
1784
2125
  catch (error) {
1785
- return legacyRecords.map((record) => ({ sessionId: record.sessionId, error: errorMessage(error) }));
2126
+ return legacyRecords.map((record) => ({
2127
+ sessionId: record.sessionId,
2128
+ error: errorMessage(error),
2129
+ }));
1786
2130
  }
1787
2131
  const moveInputs = legacyRecords
1788
2132
  .map((record) => findSessionByIdOrPrefix(sessionsByCwd.get(record.cwd) ?? [], record.sessionId))
@@ -1798,17 +2142,25 @@ export class PiSessionService {
1798
2142
  const failedIds = new Set(moveInputs.map((input) => input.sessionId));
1799
2143
  return legacyRecords
1800
2144
  .filter((record) => failedIds.has(record.sessionId))
1801
- .map((record) => ({ sessionId: record.sessionId, error: errorMessage(error) }));
2145
+ .map((record) => ({
2146
+ sessionId: record.sessionId,
2147
+ error: errorMessage(error),
2148
+ }));
1802
2149
  }
1803
2150
  }
1804
2151
  async cleanupPlan(request) {
1805
- const [sessions, archivedRecords] = await Promise.all([this.sessionManager.listAll?.() ?? [], this.archiveStore.list()]);
2152
+ const [sessions, archivedRecords] = await Promise.all([
2153
+ this.sessionManager.listAll?.() ?? [],
2154
+ this.archiveStore.list(),
2155
+ ]);
1806
2156
  return planSessionCleanup({
1807
2157
  sessions,
1808
2158
  archivedRecords,
1809
2159
  activeSessions: this.cleanupActiveSessionStatuses(),
1810
2160
  thresholds: request.thresholds,
1811
- ...(request.projectCwds === undefined ? {} : { projectCwds: request.projectCwds }),
2161
+ ...(request.projectCwds === undefined
2162
+ ? {}
2163
+ : { projectCwds: request.projectCwds }),
1812
2164
  now: this.now(),
1813
2165
  });
1814
2166
  }
@@ -1826,7 +2178,8 @@ export class PiSessionService {
1826
2178
  const sessionIds = new Set(listedSessionIds);
1827
2179
  for (const active of new Set(this.active.values())) {
1828
2180
  const session = active.runtime.session;
1829
- if (session.sessionManager.getCwd() === cwd && !archivedById.has(session.sessionId))
2181
+ if (session.sessionManager.getCwd() === cwd &&
2182
+ !archivedById.has(session.sessionId))
1830
2183
  sessionIds.add(session.sessionId);
1831
2184
  }
1832
2185
  return [...sessionIds];
@@ -1845,7 +2198,9 @@ export class PiSessionService {
1845
2198
  const session = (await this.sessionManager.list(record.cwd)).find((candidate) => candidate.id === record.sessionId);
1846
2199
  if (session === undefined)
1847
2200
  return record;
1848
- const [moved] = await this.archiveStoreArchiveMany([archiveInputFromListEntry(session)]);
2201
+ const [moved] = await this.archiveStoreArchiveMany([
2202
+ archiveInputFromListEntry(session),
2203
+ ]);
1849
2204
  return moved ?? record;
1850
2205
  }
1851
2206
  async ensureArchivedRecordsMoved(records) {
@@ -1854,7 +2209,9 @@ export class PiSessionService {
1854
2209
  return;
1855
2210
  const sessionsByCwd = await this.listSessionsByCwd(legacyRecords.map((record) => record.cwd));
1856
2211
  const moveInputs = legacyRecords
1857
- .map((record) => sessionsByCwd.get(record.cwd)?.find((candidate) => candidate.id === record.sessionId))
2212
+ .map((record) => sessionsByCwd
2213
+ .get(record.cwd)
2214
+ ?.find((candidate) => candidate.id === record.sessionId))
1858
2215
  .filter(isDefined)
1859
2216
  .map(archiveInputFromListEntry);
1860
2217
  await this.archiveStoreArchiveMany(moveInputs);
@@ -1870,7 +2227,10 @@ export class PiSessionService {
1870
2227
  return archiveInputFromActiveSession(session);
1871
2228
  }
1872
2229
  async workspaceArchiveCandidates(cwd) {
1873
- const [sessions, archivedRecords] = await Promise.all([this.sessionManager.list(cwd), this.archiveStore.list()]);
2230
+ const [sessions, archivedRecords] = await Promise.all([
2231
+ this.sessionManager.list(cwd),
2232
+ this.archiveStore.list(),
2233
+ ]);
1874
2234
  const candidates = new Map();
1875
2235
  const archivedById = new Map();
1876
2236
  for (const record of archivedRecords) {
@@ -1896,15 +2256,22 @@ export class PiSessionService {
1896
2256
  }
1897
2257
  for (const active of new Set(this.active.values())) {
1898
2258
  const session = active.runtime.session;
1899
- if (session.sessionManager.getCwd() !== cwd || archivedById.has(session.sessionId))
2259
+ if (session.sessionManager.getCwd() !== cwd ||
2260
+ archivedById.has(session.sessionId))
1900
2261
  continue;
1901
2262
  const existing = candidates.get(session.sessionId);
1902
- candidates.set(session.sessionId, { ...(existing ?? archiveCandidateFromActiveSession(session, false)), activeSession: session });
2263
+ candidates.set(session.sessionId, {
2264
+ ...(existing ?? archiveCandidateFromActiveSession(session, false)),
2265
+ activeSession: session,
2266
+ });
1903
2267
  }
1904
2268
  return [...candidates.values()];
1905
2269
  }
1906
2270
  async listSessionNames(cwd) {
1907
- const [sessions, archivedRecords] = await Promise.all([this.sessionManager.list(cwd), this.archiveStore.list()]);
2271
+ const [sessions, archivedRecords] = await Promise.all([
2272
+ this.sessionManager.list(cwd),
2273
+ this.archiveStore.list(),
2274
+ ]);
1908
2275
  const names = new Set();
1909
2276
  for (const session of sessions)
1910
2277
  addSessionName(names, session.name);
@@ -1925,7 +2292,9 @@ export class PiSessionService {
1925
2292
  await Promise.allSettled(pendingOpens);
1926
2293
  const active = this.active.get(sessionId);
1927
2294
  if (notificationPolicy.kind === "clear") {
1928
- const generation = active === undefined ? undefined : this.notificationGenerationBySession.get(active.runtime.session);
2295
+ const generation = active === undefined
2296
+ ? undefined
2297
+ : this.notificationGenerationBySession.get(active.runtime.session);
1929
2298
  const mutations = generation === undefined
1930
2299
  ? this.notificationStore.clearSession(sessionId, notificationPolicy.reason)
1931
2300
  : this.notificationStore.clearGeneration(generation, notificationPolicy.reason);
@@ -1977,7 +2346,7 @@ export class PiSessionService {
1977
2346
  throw branchSummaryAbortError;
1978
2347
  }
1979
2348
  async assertWritable(ref) {
1980
- if (await this.getArchived(ref) !== undefined)
2349
+ if ((await this.getArchived(ref)) !== undefined)
1981
2350
  throw new Error("Archived sessions are read-only. Restore the session to continue.");
1982
2351
  }
1983
2352
  async getOrOpen(ref) {
@@ -1996,7 +2365,7 @@ export class PiSessionService {
1996
2365
  return archived.archivePath;
1997
2366
  const match = isPiSessionRef(ref)
1998
2367
  ? (await this.sessionManager.list(ref.cwd)).find((session) => session.id === ref.id || session.id.startsWith(ref.id))
1999
- : (await this.sessionManager.listAll?.() ?? []).find((session) => session.id === ref || session.id.startsWith(ref));
2368
+ : ((await this.sessionManager.listAll?.()) ?? []).find((session) => session.id === ref || session.id.startsWith(ref));
2000
2369
  if (match === undefined || match.path === "")
2001
2370
  throw new Error("Session not found");
2002
2371
  return match.path;
@@ -2012,7 +2381,7 @@ export class PiSessionService {
2012
2381
  }
2013
2382
  const match = isPiSessionRef(ref)
2014
2383
  ? (await this.sessionManager.list(ref.cwd)).find((s) => s.id === ref.id || s.id.startsWith(ref.id))
2015
- : (await this.sessionManager.listAll?.() ?? []).find((s) => s.id === ref || s.id.startsWith(ref));
2384
+ : ((await this.sessionManager.listAll?.()) ?? []).find((s) => s.id === ref || s.id.startsWith(ref));
2016
2385
  if (!match)
2017
2386
  throw new Error("Session not found");
2018
2387
  return this.openExistingSession(match.id, match.cwd, () => this.sessionManager.open(match.path), options);
@@ -2058,23 +2427,31 @@ export class PiSessionService {
2058
2427
  if (exact !== undefined && lookupMatchesActiveSession(ref, exact))
2059
2428
  return exact;
2060
2429
  for (const [candidateId, active] of this.active.entries()) {
2061
- if (candidateId.startsWith(sessionId) && lookupMatchesActiveSession(ref, active))
2430
+ if (candidateId.startsWith(sessionId) &&
2431
+ lookupMatchesActiveSession(ref, active))
2062
2432
  return active;
2063
2433
  }
2064
2434
  return undefined;
2065
2435
  }
2066
2436
  async create(sessionManager, cwd, options = {}) {
2067
- const delegationToolsEnabled = options.creationProvenance !== "tracked-subsession"
2068
- && await sessionAllowsDelegationTools(sessionManager, this.sessionManager);
2437
+ const delegationToolsEnabled = options.creationProvenance !== "tracked-subsession" &&
2438
+ (await sessionAllowsDelegationTools(sessionManager, this.sessionManager));
2069
2439
  const runtime = await this.createAgentRuntime(this.createRuntime, {
2070
2440
  cwd,
2071
2441
  agentDir: this.agentDir,
2072
2442
  sessionManager,
2073
2443
  delegationToolsEnabled,
2074
- ...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
2075
- ...(options.initialThinkingLevel === undefined ? {} : { initialThinkingLevel: options.initialThinkingLevel }),
2444
+ ...(options.initialModel === undefined
2445
+ ? {}
2446
+ : { initialModel: options.initialModel }),
2447
+ ...(options.initialThinkingLevel === undefined
2448
+ ? {}
2449
+ : { initialThinkingLevel: options.initialThinkingLevel }),
2076
2450
  });
2077
- const active = { runtime, unsubscribe: noop };
2451
+ const active = {
2452
+ runtime,
2453
+ unsubscribe: noop,
2454
+ };
2078
2455
  let boundSession = runtime.session;
2079
2456
  let notificationGeneration = options.notificationGeneration;
2080
2457
  let notificationOwnership = options.notifications === "disabled"
@@ -2139,7 +2516,8 @@ export class PiSessionService {
2139
2516
  if (options.initializeModelPolicy !== undefined) {
2140
2517
  await this.initializeSessionModelPolicy(runtime.session, options.initializeModelPolicy);
2141
2518
  }
2142
- if (notificationOwnership === "replacement" && notificationGeneration !== undefined) {
2519
+ if (notificationOwnership === "replacement" &&
2520
+ notificationGeneration !== undefined) {
2143
2521
  this.publishNotificationMutations(this.notificationStore.commitReplacement(notificationGeneration));
2144
2522
  notificationOwnership = "external";
2145
2523
  }
@@ -2187,7 +2565,10 @@ export class PiSessionService {
2187
2565
  onError: (error) => {
2188
2566
  const message = `${error.extensionPath}: ${error.error}`;
2189
2567
  this.publishActivity(session, "extension error", "error", message);
2190
- this.events.publish(session.sessionId, { type: "session.error", message });
2568
+ this.events.publish(session.sessionId, {
2569
+ type: "session.error",
2570
+ message,
2571
+ });
2191
2572
  },
2192
2573
  });
2193
2574
  }
@@ -2240,7 +2621,8 @@ export class PiSessionService {
2240
2621
  const previousCwd = canonicalizeStoredCwd(previous.sessionManager.getCwd());
2241
2622
  this.unreadStore.forgetActivity(previous.sessionId, previousCwd);
2242
2623
  const nextCwd = canonicalizeStoredCwd(next.sessionManager.getCwd());
2243
- if (previous.sessionId === next.sessionId && cwdPathsEqual(previousCwd, nextCwd))
2624
+ if (previous.sessionId === next.sessionId &&
2625
+ cwdPathsEqual(previousCwd, nextCwd))
2244
2626
  return;
2245
2627
  await this.publishUnreadMutations(this.unreadStore.forgetSession(previous.sessionId, previousCwd));
2246
2628
  }
@@ -2263,11 +2645,14 @@ export class PiSessionService {
2263
2645
  publishUnreadMutations(mutations) {
2264
2646
  this.enqueueUnreadMutations(mutations);
2265
2647
  this.unreadPublicationFlushRequested = true;
2266
- if (this.unreadPublication === undefined && this.unreadPublicationRetryTimer !== undefined) {
2648
+ if (this.unreadPublication === undefined &&
2649
+ this.unreadPublicationRetryTimer !== undefined) {
2267
2650
  const failure = this.unreadPublicationFailure;
2268
2651
  return Promise.reject(failure instanceof Error
2269
2652
  ? failure
2270
- : new Error("Session unread publication is awaiting retry", { cause: failure }));
2653
+ : new Error("Session unread publication is awaiting retry", {
2654
+ cause: failure,
2655
+ }));
2271
2656
  }
2272
2657
  return this.ensureUnreadPublication();
2273
2658
  }
@@ -2290,7 +2675,8 @@ export class PiSessionService {
2290
2675
  return publication;
2291
2676
  }
2292
2677
  async drainUnreadPublication() {
2293
- while (this.unreadPublicationFlushRequested || this.pendingUnreadMutations.length > 0) {
2678
+ while (this.unreadPublicationFlushRequested ||
2679
+ this.pendingUnreadMutations.length > 0) {
2294
2680
  this.unreadPublicationFlushRequested = false;
2295
2681
  const batch = this.pendingUnreadMutations.splice(0);
2296
2682
  let publishedCount = 0;
@@ -2324,7 +2710,8 @@ export class PiSessionService {
2324
2710
  this.pendingUnreadMutations.splice(0, excess);
2325
2711
  }
2326
2712
  scheduleUnreadPublicationRetry() {
2327
- if (this.unreadPublicationStopped || this.unreadPublicationRetryTimer !== undefined)
2713
+ if (this.unreadPublicationStopped ||
2714
+ this.unreadPublicationRetryTimer !== undefined)
2328
2715
  return;
2329
2716
  const delay = this.unreadPublicationRetryDelayMs;
2330
2717
  this.unreadPublicationRetryDelayMs = Math.min(Math.max(delay * 2, this.unreadPublicationRetryInitialMs), Math.max(MAX_UNREAD_PUBLICATION_RETRY_MS, this.unreadPublicationRetryInitialMs));
@@ -2393,8 +2780,11 @@ export class PiSessionService {
2393
2780
  recordGenerationProgress(session, message) {
2394
2781
  const current = this.generationMetrics.get(session);
2395
2782
  const providerTokens = providerOutputTokens(message);
2396
- const estimatedTokens = providerTokens === undefined && current?.estimated !== false ? estimatedOutputTokens(message) : undefined;
2397
- const outputTokens = providerTokens ?? (current?.estimated === false ? current.outputTokens : estimatedTokens);
2783
+ const estimatedTokens = providerTokens === undefined && current?.estimated !== false
2784
+ ? estimatedOutputTokens(message)
2785
+ : undefined;
2786
+ const outputTokens = providerTokens ??
2787
+ (current?.estimated === false ? current.outputTokens : estimatedTokens);
2398
2788
  if (outputTokens === undefined)
2399
2789
  return;
2400
2790
  this.generationMetrics.set(session, {
@@ -2408,15 +2798,20 @@ export class PiSessionService {
2408
2798
  if (metrics === undefined)
2409
2799
  return undefined;
2410
2800
  const elapsedMs = this.now().getTime() - metrics.startedAt;
2411
- const tokensPerSecond = elapsedMs >= MIN_GENERATION_RATE_ELAPSED_MS ? metrics.outputTokens * 1_000 / elapsedMs : undefined;
2801
+ const tokensPerSecond = elapsedMs >= MIN_GENERATION_RATE_ELAPSED_MS
2802
+ ? (metrics.outputTokens * 1_000) / elapsedMs
2803
+ : undefined;
2412
2804
  return {
2413
2805
  outputTokens: metrics.outputTokens,
2414
- ...(tokensPerSecond === undefined || !Number.isFinite(tokensPerSecond) ? {} : { tokensPerSecond }),
2806
+ ...(tokensPerSecond === undefined || !Number.isFinite(tokensPerSecond)
2807
+ ? {}
2808
+ : { tokensPerSecond }),
2415
2809
  ...(metrics.estimated ? { estimated: true } : {}),
2416
2810
  };
2417
2811
  }
2418
2812
  scheduleCompactionQueueDrain(sessionId, delayMs = 0) {
2419
- if (!this.compactionPromptQueues.has(sessionId) || this.compactionDrainTimers.has(sessionId))
2813
+ if (!this.compactionPromptQueues.has(sessionId) ||
2814
+ this.compactionDrainTimers.has(sessionId))
2420
2815
  return;
2421
2816
  const timer = setTimeout(() => {
2422
2817
  this.compactionDrainTimers.delete(sessionId);
@@ -2447,7 +2842,9 @@ export class PiSessionService {
2447
2842
  return;
2448
2843
  this.publishStatus(session);
2449
2844
  const submitted = this.submitPrompt(session, prompt.text, undefined, prompt.images, prompt.echoUserMessage ?? true);
2450
- void submitted.finally(() => { this.scheduleCompactionQueueDrain(sessionId); });
2845
+ void submitted.finally(() => {
2846
+ this.scheduleCompactionQueueDrain(sessionId);
2847
+ });
2451
2848
  }
2452
2849
  takeCompactionPromptQueue(sessionId) {
2453
2850
  const queued = this.compactionPromptQueues.get(sessionId) ?? [];
@@ -2475,7 +2872,10 @@ export class PiSessionService {
2475
2872
  this.compactionDrainTimers.clear();
2476
2873
  }
2477
2874
  maybeGenerateSessionName(session, firstMessage) {
2478
- if (session.sessionName !== undefined || session.messages.length !== 0 || session.isStreaming || session.isCompacting)
2875
+ if (session.sessionName !== undefined ||
2876
+ session.messages.length !== 0 ||
2877
+ session.isStreaming ||
2878
+ session.isCompacting)
2479
2879
  return;
2480
2880
  const deterministicName = deterministicSessionName(firstMessage);
2481
2881
  if (deterministicName !== undefined) {
@@ -2485,9 +2885,11 @@ export class PiSessionService {
2485
2885
  const model = session.model;
2486
2886
  if (model === undefined)
2487
2887
  return;
2488
- void generateShortSessionName(session.agent.streamFunction, model, firstMessage).then((name) => {
2888
+ void generateShortSessionName(session.agent.streamFunction, model, firstMessage)
2889
+ .then((name) => {
2489
2890
  this.applyGeneratedSessionName(session, name ?? fallbackSessionName(firstMessage));
2490
- }).catch(() => {
2891
+ })
2892
+ .catch(() => {
2491
2893
  this.applyGeneratedSessionName(session, fallbackSessionName(firstMessage));
2492
2894
  });
2493
2895
  }
@@ -2509,7 +2911,10 @@ export class PiSessionService {
2509
2911
  this.applyGeneratedSessionName(session, generatedName);
2510
2912
  }
2511
2913
  catch (error) {
2512
- this.logger.info({ sessionId: session.sessionId, error: error instanceof Error ? error.message : String(error) }, "failed to apply deferred session name");
2914
+ this.logger.info({
2915
+ sessionId: session.sessionId,
2916
+ error: error instanceof Error ? error.message : String(error),
2917
+ }, "failed to apply deferred session name");
2513
2918
  }
2514
2919
  }
2515
2920
  const notifications = this.deferredSubsessionNotifications.get(session) ?? [];
@@ -2544,7 +2949,9 @@ export class PiSessionService {
2544
2949
  this.authLossWarnings.delete(warningKey);
2545
2950
  return;
2546
2951
  }
2547
- if (removedProviderId === undefined || model.provider !== removedProviderId || this.authLossWarnings.has(warningKey))
2952
+ if (removedProviderId === undefined ||
2953
+ model.provider !== removedProviderId ||
2954
+ this.authLossWarnings.has(warningKey))
2548
2955
  return;
2549
2956
  this.authLossWarnings.add(warningKey);
2550
2957
  this.events.publish(session.sessionId, {
@@ -2563,7 +2970,11 @@ export class PiSessionService {
2563
2970
  publishSessionName(session) {
2564
2971
  const event = session.sessionName === undefined
2565
2972
  ? { type: "session.name", sessionId: session.sessionId }
2566
- : { type: "session.name", sessionId: session.sessionId, name: session.sessionName };
2973
+ : {
2974
+ type: "session.name",
2975
+ sessionId: session.sessionId,
2976
+ name: session.sessionName,
2977
+ };
2567
2978
  this.events.publish(session.sessionId, event);
2568
2979
  this.events.publishGlobal(event);
2569
2980
  }
@@ -2603,17 +3014,20 @@ export class PiSessionService {
2603
3014
  return "active";
2604
3015
  }
2605
3016
  hasActiveWork(session) {
2606
- return this.treeNavigations.has(session)
2607
- || this.isSessionEntryMutationActive(session)
2608
- || this.isTreeExclusiveOperationActive(session)
2609
- || sessionHasActiveWork(session, this.compactionQueuedMessages(session.sessionId).length);
3017
+ return (this.treeNavigations.has(session) ||
3018
+ this.isSessionEntryMutationActive(session) ||
3019
+ this.isTreeExclusiveOperationActive(session) ||
3020
+ sessionHasActiveWork(session, this.compactionQueuedMessages(session.sessionId).length));
2610
3021
  }
2611
3022
  async runTreeExclusiveOperation(targets, activeError, operation) {
2612
3023
  const sessionIds = new Set();
2613
3024
  const runtimes = new Set();
2614
3025
  const sessions = new Set();
2615
3026
  for (const target of targets) {
2616
- const runtime = target.runtime ?? (target.session === undefined ? undefined : this.activeRuntimeForSession(target.session));
3027
+ const runtime = target.runtime ??
3028
+ (target.session === undefined
3029
+ ? undefined
3030
+ : this.activeRuntimeForSession(target.session));
2617
3031
  const session = target.session ?? runtime?.session;
2618
3032
  if (session !== undefined && this.hasActiveWork(session))
2619
3033
  throw new Error(activeError);
@@ -2652,7 +3066,8 @@ export class PiSessionService {
2652
3066
  if (this.isTreeExclusiveSessionIdentityActive(session.sessionId))
2653
3067
  return true;
2654
3068
  const runtime = this.activeRuntimeForSession(session);
2655
- return runtime !== undefined && (this.treeExclusiveRuntimeOperationCounts.get(runtime) ?? 0) > 0;
3069
+ return (runtime !== undefined &&
3070
+ (this.treeExclusiveRuntimeOperationCounts.get(runtime) ?? 0) > 0);
2656
3071
  }
2657
3072
  activeRuntimeForSession(session) {
2658
3073
  for (const active of new Set(this.active.values())) {
@@ -2744,11 +3159,18 @@ export class PiSessionService {
2744
3159
  }
2745
3160
  publishActivity(session, label, phase, detail) {
2746
3161
  const at = new Date().toISOString();
2747
- const stored = detail === undefined ? { phase, label, at } : { phase, label, detail, at };
3162
+ const stored = detail === undefined
3163
+ ? { phase, label, at }
3164
+ : { phase, label, detail, at };
2748
3165
  this.activities.set(session.sessionId, stored);
2749
- const activity = detail === undefined ? { sessionId: session.sessionId, phase, label, at } : { sessionId: session.sessionId, phase, label, detail, at };
3166
+ const activity = detail === undefined
3167
+ ? { sessionId: session.sessionId, phase, label, at }
3168
+ : { sessionId: session.sessionId, phase, label, detail, at };
2750
3169
  this.workspaceActivity?.applySessionActivity(session.sessionManager.getCwd(), activity);
2751
- this.events.publish(session.sessionId, { type: "activity.update", activity });
3170
+ this.events.publish(session.sessionId, {
3171
+ type: "activity.update",
3172
+ activity,
3173
+ });
2752
3174
  this.events.publishGlobal({ type: "activity.update", activity });
2753
3175
  this.observeUnreadActivityState(session);
2754
3176
  }
@@ -2768,19 +3190,49 @@ export class PiSessionService {
2768
3190
  const stored = { phase: "idle", label: "idle", at };
2769
3191
  this.activities.set(session.sessionId, stored);
2770
3192
  const activity = { sessionId: session.sessionId, ...stored };
2771
- this.events.publish(session.sessionId, { type: "activity.update", activity });
3193
+ this.events.publish(session.sessionId, {
3194
+ type: "activity.update",
3195
+ activity,
3196
+ });
2772
3197
  this.events.publishGlobal({ type: "activity.update", activity });
2773
3198
  }
3199
+ /**
3200
+ * Read-only capability view for `get_model_policy`, built from the session's
3201
+ * own cached inspection so it cannot disagree with what the HTTP route reports.
3202
+ */
3203
+ modelPolicyCapability(sessionId) {
3204
+ // The tool only ever asks about its own session, which is live by definition
3205
+ // while a tool call is executing. A miss means the runtime was torn down
3206
+ // mid-call, which is a terminal read failure rather than a policy answer.
3207
+ const session = this.active.get(sessionId)?.runtime.session;
3208
+ if (session === undefined)
3209
+ throw new Error(`Session ${sessionId} is not active`);
3210
+ const inspection = this.inspectAndCacheSessionModelPolicy(session);
3211
+ return buildModelPolicyCapability({
3212
+ policy: inspection.kind === "invalid" ? undefined : inspection.policy,
3213
+ currentRuntime: this.exactSelectionFromSession(session),
3214
+ ladder: this.modelTierRegistry.validate(),
3215
+ resolveTier: (tier) => {
3216
+ const resolved = this.modelTierRegistry.resolve(tier);
3217
+ return {
3218
+ model: { provider: resolved.model.provider, id: resolved.model.id },
3219
+ thinkingLevel: resolved.thinkingLevel,
3220
+ };
3221
+ },
3222
+ blockedReason: this.modelPolicyBlockedReason(session),
3223
+ });
3224
+ }
2774
3225
  exactSelectionFromSession(session) {
2775
3226
  const model = session.model;
2776
- if (model === undefined
2777
- || typeof model.provider !== "string"
2778
- || model.provider.trim() === ""
2779
- || typeof model.id !== "string"
2780
- || model.id.trim() === "") {
3227
+ if (model === undefined ||
3228
+ typeof model.provider !== "string" ||
3229
+ model.provider.trim() === "" ||
3230
+ typeof model.id !== "string" ||
3231
+ model.id.trim() === "") {
2781
3232
  throw new Error("Session model policy requires a resolved runtime model provider and id");
2782
3233
  }
2783
- if (typeof session.thinkingLevel !== "string" || session.thinkingLevel.trim() === "") {
3234
+ if (typeof session.thinkingLevel !== "string" ||
3235
+ session.thinkingLevel.trim() === "") {
2784
3236
  throw new Error("Session model policy requires a resolved runtime thinking level");
2785
3237
  }
2786
3238
  return {
@@ -2789,7 +3241,8 @@ export class PiSessionService {
2789
3241
  };
2790
3242
  }
2791
3243
  policyEntries(session) {
2792
- return session.sessionManager.getEntries?.() ?? session.sessionManager.getBranch();
3244
+ return (session.sessionManager.getEntries?.() ??
3245
+ session.sessionManager.getBranch());
2793
3246
  }
2794
3247
  inspectAndCacheSessionModelPolicy(session) {
2795
3248
  const inspection = inspectSessionModelPolicy(this.policyEntries(session), this.exactSelectionFromSession(session));
@@ -2804,7 +3257,8 @@ export class PiSessionService {
2804
3257
  }
2805
3258
  /** Effective block: an unproven runtime restoration outranks an entry defect. */
2806
3259
  modelPolicyBlockedReason(session) {
2807
- return this.modelPolicyRuntimeBlocks.get(session.sessionId) ?? this.modelPolicyEntryReasons.get(session);
3260
+ return (this.modelPolicyRuntimeBlocks.get(session.sessionId) ??
3261
+ this.modelPolicyEntryReasons.get(session));
2808
3262
  }
2809
3263
  /**
2810
3264
  * Root-session policy initialization. `true` records the runtime's own tuple;
@@ -2812,7 +3266,10 @@ export class PiSessionService {
2812
3266
  * of `start()` sees the session, so no prompt can precede the policy.
2813
3267
  */
2814
3268
  async initializeSessionModelPolicy(session, initializer) {
2815
- const current = { mode: "exact", exact: this.exactSelectionFromSession(session) };
3269
+ const current = {
3270
+ mode: "exact",
3271
+ exact: this.exactSelectionFromSession(session),
3272
+ };
2816
3273
  if (initializer === true) {
2817
3274
  this.appendSessionModelPolicy(session, current);
2818
3275
  this.inspectAndCacheSessionModelPolicy(session);
@@ -2820,7 +3277,9 @@ export class PiSessionService {
2820
3277
  }
2821
3278
  // Root creation reports failure through its own abort/dispose cleanup rather
2822
3279
  // than publishing a failed transition for a session no client has seen.
2823
- await this.applySessionModelPolicy(session, current, initializer, { action: "initialize the session model policy" });
3280
+ await this.applySessionModelPolicy(session, current, initializer, {
3281
+ action: "initialize the session model policy",
3282
+ });
2824
3283
  }
2825
3284
  /**
2826
3285
  * The single policy transition path. Validates the complete target before any
@@ -2831,7 +3290,10 @@ export class PiSessionService {
2831
3290
  async applySessionModelPolicy(session, current, update, options) {
2832
3291
  const plan = planSessionModelPolicyUpdate(current, update, (tier) => {
2833
3292
  const resolved = this.modelTierRegistry.resolve(tier);
2834
- return { model: { provider: resolved.model.provider, id: resolved.model.id }, thinkingLevel: resolved.thinkingLevel };
3293
+ return {
3294
+ model: { provider: resolved.model.provider, id: resolved.model.id },
3295
+ thinkingLevel: resolved.thinkingLevel,
3296
+ };
2835
3297
  });
2836
3298
  // Validation happens before the mutation opens, so a rejected target leaves
2837
3299
  // the runtime and the persisted policy untouched.
@@ -2849,7 +3311,7 @@ export class PiSessionService {
2849
3311
  }
2850
3312
  catch (error) {
2851
3313
  const reason = error instanceof Error ? error.message : String(error);
2852
- if (!await this.restoreExactSelection(session, previous)) {
3314
+ if (!(await this.restoreExactSelection(session, previous))) {
2853
3315
  this.modelPolicyRuntimeBlocks.set(session.sessionId, `MODEL_POLICY_BLOCKED: ${reason}; the previous model and thinking pair could not be restored`);
2854
3316
  }
2855
3317
  this.inspectAndCacheSessionModelPolicy(session);
@@ -2956,14 +3418,16 @@ export class PiSessionService {
2956
3418
  const policy = {
2957
3419
  mode: "exact",
2958
3420
  exact: this.exactSelectionFromSession(session),
2959
- ...(currentPolicy.tier === undefined ? {} : { tier: currentPolicy.tier }),
3421
+ ...(currentPolicy.tier === undefined
3422
+ ? {}
3423
+ : { tier: currentPolicy.tier }),
2960
3424
  };
2961
3425
  try {
2962
3426
  this.appendSessionModelPolicy(session, policy);
2963
3427
  }
2964
3428
  catch (error) {
2965
3429
  const reason = error instanceof Error ? error.message : String(error);
2966
- if (!await this.restoreExactSelection(session, previous)) {
3430
+ if (!(await this.restoreExactSelection(session, previous))) {
2967
3431
  this.modelPolicyRuntimeBlocks.set(session.sessionId, `MODEL_POLICY_BLOCKED: ${reason}; the previous model and thinking pair could not be restored`);
2968
3432
  }
2969
3433
  this.inspectAndCacheSessionModelPolicy(session);
@@ -3009,7 +3473,8 @@ export class PiSessionService {
3009
3473
  const candidates = session.scopedModels.length > 0
3010
3474
  ? session.scopedModels.map((scoped) => scoped.model)
3011
3475
  : session.modelRuntime.getAvailableSnapshot();
3012
- const model = candidates.find((candidate) => candidate.provider === selection.model.provider && candidate.id === selection.model.id);
3476
+ const model = candidates.find((candidate) => candidate.provider === selection.model.provider &&
3477
+ candidate.id === selection.model.id);
3013
3478
  const described = `${selection.model.provider}/${selection.model.id}`;
3014
3479
  if (model === undefined)
3015
3480
  throw new Error(`Model not found: ${described}`);
@@ -3019,7 +3484,13 @@ export class PiSessionService {
3019
3484
  if (!runtimeThinkingLevels(model).includes(selection.thinkingLevel)) {
3020
3485
  throw new Error(`Thinking level ${selection.thinkingLevel} is unsupported by ${described}`);
3021
3486
  }
3022
- return { model, selection: { model: { provider: model.provider, id: model.id }, thinkingLevel: selection.thinkingLevel } };
3487
+ return {
3488
+ model,
3489
+ selection: {
3490
+ model: { provider: model.provider, id: model.id },
3491
+ thinkingLevel: selection.thinkingLevel,
3492
+ },
3493
+ };
3023
3494
  }
3024
3495
  /**
3025
3496
  * Model first, then thinking: `setThinkingLevel` clamps against the currently
@@ -3034,11 +3505,11 @@ export class PiSessionService {
3034
3505
  await session.setModel(target.model);
3035
3506
  session.setThinkingLevel(level);
3036
3507
  const effective = this.exactSelectionFromSession(session);
3037
- if (effective.model.provider !== target.selection.model.provider
3038
- || effective.model.id !== target.selection.model.id
3039
- || effective.thinkingLevel !== level) {
3040
- throw new Error(`Session model policy could not activate ${target.selection.model.provider}/${target.selection.model.id} at thinking level ${level}`
3041
- + `; the runtime reports ${effective.model.provider}/${effective.model.id} at thinking level ${effective.thinkingLevel}`);
3508
+ if (effective.model.provider !== target.selection.model.provider ||
3509
+ effective.model.id !== target.selection.model.id ||
3510
+ effective.thinkingLevel !== level) {
3511
+ throw new Error(`Session model policy could not activate ${target.selection.model.provider}/${target.selection.model.id} at thinking level ${level}` +
3512
+ `; the runtime reports ${effective.model.provider}/${effective.model.id} at thinking level ${effective.thinkingLevel}`);
3042
3513
  }
3043
3514
  }
3044
3515
  /**
@@ -3049,9 +3520,9 @@ export class PiSessionService {
3049
3520
  async restoreExactSelection(session, previous) {
3050
3521
  try {
3051
3522
  const current = this.exactSelectionFromSession(session);
3052
- if (current.model.provider === previous.model.provider
3053
- && current.model.id === previous.model.id
3054
- && current.thinkingLevel === previous.thinkingLevel) {
3523
+ if (current.model.provider === previous.model.provider &&
3524
+ current.model.id === previous.model.id &&
3525
+ current.thinkingLevel === previous.thinkingLevel) {
3055
3526
  return true;
3056
3527
  }
3057
3528
  const target = await this.resolveAvailableExactSelection(session, previous);
@@ -3093,7 +3564,9 @@ export class PiSessionService {
3093
3564
  }
3094
3565
  return {
3095
3566
  mode: inspection.policy.mode,
3096
- ...(inspection.policy.tier === undefined ? {} : { tier: inspection.policy.tier }),
3567
+ ...(inspection.policy.tier === undefined
3568
+ ? {}
3569
+ : { tier: inspection.policy.tier }),
3097
3570
  resolved,
3098
3571
  ladderValid: ladderValidation.valid,
3099
3572
  ...(blockedReason === undefined ? {} : { blockedReason }),
@@ -3101,7 +3574,9 @@ export class PiSessionService {
3101
3574
  }
3102
3575
  statusFromSession(session) {
3103
3576
  const stats = session.getSessionStats();
3104
- const model = session.model === undefined ? undefined : modelToClientModel(session.model);
3577
+ const model = session.model === undefined
3578
+ ? undefined
3579
+ : modelToClientModel(session.model);
3105
3580
  const generation = this.generationForStatus(session);
3106
3581
  const contextUsage = session.getContextUsage();
3107
3582
  const warnings = this.warningsForSession(session);
@@ -3139,7 +3614,8 @@ export class PiSessionService {
3139
3614
  return warnings;
3140
3615
  }
3141
3616
  pendingMessageCount(session) {
3142
- return session.pendingMessageCount + this.compactionQueuedMessages(session.sessionId).length;
3617
+ return (session.pendingMessageCount +
3618
+ this.compactionQueuedMessages(session.sessionId).length);
3143
3619
  }
3144
3620
  compactionQueuedMessages(sessionId) {
3145
3621
  return this.compactionPromptQueues.get(sessionId) ?? [];
@@ -3154,7 +3630,9 @@ function previewResponseFromPlan(plan) {
3154
3630
  thresholds: plan.thresholds,
3155
3631
  projects: plan.projects,
3156
3632
  totals: plan.totals,
3157
- ...(plan.skippedBusySessionIds.length === 0 ? {} : { skippedBusySessionIds: plan.skippedBusySessionIds }),
3633
+ ...(plan.skippedBusySessionIds.length === 0
3634
+ ? {}
3635
+ : { skippedBusySessionIds: plan.skippedBusySessionIds }),
3158
3636
  };
3159
3637
  }
3160
3638
  function uniqueBulkSessionRefs(refs) {
@@ -3173,15 +3651,19 @@ function bulkRefToLookup(ref) {
3173
3651
  return ref.cwd === undefined ? ref.id : { id: ref.id, cwd: ref.cwd };
3174
3652
  }
3175
3653
  function findArchivedRecordForBulkRef(records, ref) {
3176
- return records.find((record) => (ref.cwd === undefined || record.cwd === ref.cwd) && (record.sessionId === ref.id || record.sessionId.startsWith(ref.id)));
3654
+ return records.find((record) => (ref.cwd === undefined || record.cwd === ref.cwd) &&
3655
+ (record.sessionId === ref.id || record.sessionId.startsWith(ref.id)));
3177
3656
  }
3178
3657
  function findListedSessionForBulkRef(context, ref) {
3179
3658
  if (ref.cwd !== undefined)
3180
3659
  return findSessionByIdOrPrefix(context.sessionsByCwd.get(ref.cwd) ?? [], ref.id);
3181
- return context.allSessions === undefined ? undefined : findSessionByIdOrPrefix(context.allSessions, ref.id);
3660
+ return context.allSessions === undefined
3661
+ ? undefined
3662
+ : findSessionByIdOrPrefix(context.allSessions, ref.id);
3182
3663
  }
3183
3664
  function findSessionByIdOrPrefix(sessions, sessionId) {
3184
- return sessions.find((session) => session.id === sessionId) ?? sessions.find((session) => session.id.startsWith(sessionId));
3665
+ return (sessions.find((session) => session.id === sessionId) ??
3666
+ sessions.find((session) => session.id.startsWith(sessionId)));
3185
3667
  }
3186
3668
  function uniqueStrings(values) {
3187
3669
  return [...new Set(values)];
@@ -3219,7 +3701,9 @@ function clientSessionFromListEntry(session) {
3219
3701
  modified: session.modified.toISOString(),
3220
3702
  messageCount: session.messageCount,
3221
3703
  firstMessage: session.firstMessage,
3222
- ...(session.parentSessionPath === undefined ? {} : { parentSessionPath: session.parentSessionPath }),
3704
+ ...(session.parentSessionPath === undefined
3705
+ ? {}
3706
+ : { parentSessionPath: session.parentSessionPath }),
3223
3707
  };
3224
3708
  }
3225
3709
  function archiveInputFromListEntry(session) {
@@ -3232,7 +3716,9 @@ function archiveInputFromListEntry(session) {
3232
3716
  messageCount: session.messageCount,
3233
3717
  firstMessage: session.firstMessage,
3234
3718
  ...(session.name === undefined ? {} : { name: session.name }),
3235
- ...(session.parentSessionPath === undefined ? {} : { parentSessionPath: session.parentSessionPath }),
3719
+ ...(session.parentSessionPath === undefined
3720
+ ? {}
3721
+ : { parentSessionPath: session.parentSessionPath }),
3236
3722
  };
3237
3723
  }
3238
3724
  function archiveInputFromActiveSession(session) {
@@ -3259,7 +3745,9 @@ function archiveCandidateFromListEntry(session) {
3259
3745
  cwd: session.cwd,
3260
3746
  archived: false,
3261
3747
  listEntry: session,
3262
- ...(session.parentSessionPath === undefined ? {} : { parentSessionPath: session.parentSessionPath }),
3748
+ ...(session.parentSessionPath === undefined
3749
+ ? {}
3750
+ : { parentSessionPath: session.parentSessionPath }),
3263
3751
  };
3264
3752
  }
3265
3753
  function archiveCandidateFromArchivedRecord(record, fallback) {
@@ -3298,7 +3786,10 @@ function archiveInputFromCandidate(candidate) {
3298
3786
  throw new Error(`Session is not available for archiving: ${candidate.id}`);
3299
3787
  }
3300
3788
  function sessionHasActiveWork(session, extraQueuedMessageCount = 0) {
3301
- return session.isStreaming || session.isCompacting || session.isBashRunning || session.pendingMessageCount + extraQueuedMessageCount > 0;
3789
+ return (session.isStreaming ||
3790
+ session.isCompacting ||
3791
+ session.isBashRunning ||
3792
+ session.pendingMessageCount + extraQueuedMessageCount > 0);
3302
3793
  }
3303
3794
  function sessionDisplayName(session) {
3304
3795
  return session.sessionName ?? session.sessionId;
@@ -3309,7 +3800,11 @@ function clientSessionFromArchivedRecord(record, fallback) {
3309
3800
  const modified = record.modified ?? fallback?.modified.toISOString();
3310
3801
  const messageCount = record.messageCount ?? fallback?.messageCount;
3311
3802
  const firstMessage = record.firstMessage ?? fallback?.firstMessage;
3312
- if (path === undefined || created === undefined || modified === undefined || messageCount === undefined || firstMessage === undefined)
3803
+ if (path === undefined ||
3804
+ created === undefined ||
3805
+ modified === undefined ||
3806
+ messageCount === undefined ||
3807
+ firstMessage === undefined)
3313
3808
  return undefined;
3314
3809
  const name = record.name ?? fallback?.name;
3315
3810
  const parentSessionPath = record.parentSessionPath ?? fallback?.parentSessionPath;
@@ -3409,9 +3904,11 @@ function findReciprocalParentSubsessionLink(managers, parentSessionFile, parentS
3409
3904
  const link = parsePersistedParentSubsessionLink(entry);
3410
3905
  if (link === undefined)
3411
3906
  continue;
3412
- if (link.spawnedBySessionId !== parentSessionId || link.spawnedSessionId !== childSessionId)
3907
+ if (link.spawnedBySessionId !== parentSessionId ||
3908
+ link.spawnedSessionId !== childSessionId)
3413
3909
  continue;
3414
- if (link.spawnedSessionFile === undefined || !sessionPathsEqual(link.spawnedSessionFile, childSessionFile))
3910
+ if (link.spawnedSessionFile === undefined ||
3911
+ !sessionPathsEqual(link.spawnedSessionFile, childSessionFile))
3415
3912
  continue;
3416
3913
  return link;
3417
3914
  }
@@ -3421,7 +3918,9 @@ function trackedSubsessionLinkFromParentLink(parentSessionId, link, parentSessio
3421
3918
  return {
3422
3919
  parentSessionId,
3423
3920
  childSessionId: link.spawnedSessionId,
3424
- ...(link.spawnedSessionFile === undefined ? {} : { childSessionFile: link.spawnedSessionFile }),
3921
+ ...(link.spawnedSessionFile === undefined
3922
+ ? {}
3923
+ : { childSessionFile: link.spawnedSessionFile }),
3425
3924
  parentSessionFile,
3426
3925
  ...(link.cwd === undefined ? {} : { cwd: link.cwd }),
3427
3926
  };
@@ -3431,7 +3930,9 @@ function persistedParentSubsessionLinkData(link) {
3431
3930
  version: 1,
3432
3931
  spawnedBySessionId: link.parentSessionId,
3433
3932
  spawnedSessionId: link.childSessionId,
3434
- ...(link.childSessionFile === undefined ? {} : { spawnedSessionFile: link.childSessionFile }),
3933
+ ...(link.childSessionFile === undefined
3934
+ ? {}
3935
+ : { spawnedSessionFile: link.childSessionFile }),
3435
3936
  ...(link.cwd === undefined ? {} : { cwd: link.cwd }),
3436
3937
  };
3437
3938
  }
@@ -3443,33 +3944,45 @@ function persistedChildSubsessionLinkData(parentSessionId, childSessionId) {
3443
3944
  };
3444
3945
  }
3445
3946
  function parsePersistedParentSubsessionLink(entry) {
3446
- if (!isRecord(entry) || entry["type"] !== "custom" || entry["customType"] !== SUBSESSION_LINK_CUSTOM_TYPE)
3947
+ if (!isRecord(entry) ||
3948
+ entry["type"] !== "custom" ||
3949
+ entry["customType"] !== SUBSESSION_LINK_CUSTOM_TYPE)
3447
3950
  return undefined;
3448
3951
  const data = entry["data"];
3449
3952
  if (!isRecord(data))
3450
3953
  return undefined;
3451
3954
  const spawnedBySessionId = getString(data, "spawnedBySessionId");
3452
3955
  const spawnedSessionId = getString(data, "spawnedSessionId");
3453
- if (spawnedBySessionId === undefined || spawnedBySessionId === "" || spawnedSessionId === undefined || spawnedSessionId === "")
3956
+ if (spawnedBySessionId === undefined ||
3957
+ spawnedBySessionId === "" ||
3958
+ spawnedSessionId === undefined ||
3959
+ spawnedSessionId === "")
3454
3960
  return undefined;
3455
3961
  const spawnedSessionFile = getString(data, "spawnedSessionFile");
3456
3962
  const cwd = getString(data, "cwd");
3457
3963
  return {
3458
3964
  spawnedBySessionId,
3459
3965
  spawnedSessionId,
3460
- ...(spawnedSessionFile === undefined || spawnedSessionFile === "" ? {} : { spawnedSessionFile }),
3966
+ ...(spawnedSessionFile === undefined || spawnedSessionFile === ""
3967
+ ? {}
3968
+ : { spawnedSessionFile }),
3461
3969
  ...(cwd === undefined || cwd === "" ? {} : { cwd }),
3462
3970
  };
3463
3971
  }
3464
3972
  function parsePersistedChildSubsessionLink(entry) {
3465
- if (!isRecord(entry) || entry["type"] !== "custom" || entry["customType"] !== SUBSESSION_CHILD_LINK_CUSTOM_TYPE)
3973
+ if (!isRecord(entry) ||
3974
+ entry["type"] !== "custom" ||
3975
+ entry["customType"] !== SUBSESSION_CHILD_LINK_CUSTOM_TYPE)
3466
3976
  return undefined;
3467
3977
  const data = entry["data"];
3468
3978
  if (!isRecord(data))
3469
3979
  return undefined;
3470
3980
  const spawnedBySessionId = getString(data, "spawnedBySessionId");
3471
3981
  const spawnedSessionId = getString(data, "spawnedSessionId");
3472
- if (spawnedBySessionId === undefined || spawnedBySessionId === "" || spawnedSessionId === undefined || spawnedSessionId === "")
3982
+ if (spawnedBySessionId === undefined ||
3983
+ spawnedBySessionId === "" ||
3984
+ spawnedSessionId === undefined ||
3985
+ spawnedSessionId === "")
3473
3986
  return undefined;
3474
3987
  return { spawnedBySessionId, spawnedSessionId };
3475
3988
  }
@@ -3494,13 +4007,16 @@ function sessionFileExists(sessionFile) {
3494
4007
  }
3495
4008
  function sessionFileMatches(session, expectedSessionFile) {
3496
4009
  const sessionFile = nonEmptyString(session.sessionFile);
3497
- return sessionFile !== undefined && expectedSessionFile !== undefined && sessionPathsEqual(sessionFile, expectedSessionFile);
4010
+ return (sessionFile !== undefined &&
4011
+ expectedSessionFile !== undefined &&
4012
+ sessionPathsEqual(sessionFile, expectedSessionFile));
3498
4013
  }
3499
4014
  function activeSessionFileMatches(active, expectedSessionFile) {
3500
4015
  return sessionFileMatches(active.runtime.session, expectedSessionFile);
3501
4016
  }
3502
4017
  function trackedLinkParentFileMatches(link, parentSessionFile) {
3503
- return link.parentSessionFile !== undefined && sessionPathsEqual(link.parentSessionFile, parentSessionFile);
4018
+ return (link.parentSessionFile !== undefined &&
4019
+ sessionPathsEqual(link.parentSessionFile, parentSessionFile));
3504
4020
  }
3505
4021
  async function readSessionHeaderSummary(sessionFile) {
3506
4022
  let file;
@@ -3512,10 +4028,15 @@ async function readSessionHeaderSummary(sessionFile) {
3512
4028
  if (firstLine === undefined || firstLine === "")
3513
4029
  return undefined;
3514
4030
  const header = JSON.parse(firstLine);
3515
- if (!isRecord(header) || header["type"] !== "session" || typeof header["id"] !== "string")
4031
+ if (!isRecord(header) ||
4032
+ header["type"] !== "session" ||
4033
+ typeof header["id"] !== "string")
3516
4034
  return undefined;
3517
4035
  const parentSession = getString(header, "parentSession");
3518
- return { id: header["id"], ...(parentSession === undefined ? {} : { parentSession }) };
4036
+ return {
4037
+ id: header["id"],
4038
+ ...(parentSession === undefined ? {} : { parentSession }),
4039
+ };
3519
4040
  }
3520
4041
  catch {
3521
4042
  return undefined;
@@ -3530,7 +4051,8 @@ async function sessionFileHeaderMatches(sessionFile, expected) {
3530
4051
  return false;
3531
4052
  if (expected.parentSessionFile === undefined)
3532
4053
  return true;
3533
- return header.parentSession !== undefined && sessionPathsEqual(header.parentSession, expected.parentSessionFile);
4054
+ return (header.parentSession !== undefined &&
4055
+ sessionPathsEqual(header.parentSession, expected.parentSessionFile));
3534
4056
  }
3535
4057
  async function clearParentSession(sessionFile) {
3536
4058
  const content = await readFile(sessionFile, "utf8");
@@ -3555,8 +4077,12 @@ function clearSessionQueue(session) {
3555
4077
  }
3556
4078
  function queuedMessagesFromSession(session, extraQueuedMessages = []) {
3557
4079
  return [
3558
- ...session.getSteeringMessages().map((text) => ({ kind: "steer", text })),
3559
- ...session.getFollowUpMessages().map((text) => ({ kind: "followUp", text })),
4080
+ ...session
4081
+ .getSteeringMessages()
4082
+ .map((text) => ({ kind: "steer", text })),
4083
+ ...session
4084
+ .getFollowUpMessages()
4085
+ .map((text) => ({ kind: "followUp", text })),
3560
4086
  ...extraQueuedMessages,
3561
4087
  ];
3562
4088
  }
@@ -3608,7 +4134,8 @@ function historyMessages(session) {
3608
4134
  messages.push({
3609
4135
  ...historyMessage,
3610
4136
  entryId,
3611
- ...(previousMessage?.role === "assistant" && previousMessage.entryId !== undefined
4137
+ ...(previousMessage?.role === "assistant" &&
4138
+ previousMessage.entryId !== undefined
3612
4139
  ? { previousAssistantEntryId: previousMessage.entryId }
3613
4140
  : {}),
3614
4141
  ...(messages.length > 0 ? { canFork: true } : {}),
@@ -3621,17 +4148,30 @@ function historyMessages(session) {
3621
4148
  continue;
3622
4149
  }
3623
4150
  if (entry["type"] === "custom_message" && entry["display"] === true) {
3624
- messages.push({ role: "custom", content: entry["content"], customType: entry["customType"], details: entry["details"] });
4151
+ messages.push({
4152
+ role: "custom",
4153
+ content: entry["content"],
4154
+ customType: entry["customType"],
4155
+ details: entry["details"],
4156
+ });
3625
4157
  previousMessage = { role: "custom", entryId: undefined };
3626
4158
  continue;
3627
4159
  }
3628
4160
  if (entry["type"] === "compaction") {
3629
- messages.push({ role: "system", source: "compaction", content: `Compacted history:\n\n${stringValue(entry["summary"])}` });
4161
+ messages.push({
4162
+ role: "system",
4163
+ source: "compaction",
4164
+ content: `Compacted history:\n\n${stringValue(entry["summary"])}`,
4165
+ });
3630
4166
  previousMessage = { role: "system", entryId: undefined };
3631
4167
  continue;
3632
4168
  }
3633
4169
  if (entry["type"] === "branch_summary") {
3634
- messages.push({ role: "system", source: "branch_summary", content: `Branch summary:\n\n${stringValue(entry["summary"])}` });
4170
+ messages.push({
4171
+ role: "system",
4172
+ source: "branch_summary",
4173
+ content: `Branch summary:\n\n${stringValue(entry["summary"])}`,
4174
+ });
3635
4175
  previousMessage = { role: "system", entryId: undefined };
3636
4176
  }
3637
4177
  }
@@ -3664,7 +4204,9 @@ function finalAssistantText(messages) {
3664
4204
  continue;
3665
4205
  const texts = [];
3666
4206
  for (const part of content) {
3667
- if (isRecord(part) && part["type"] === "text" && typeof part["text"] === "string")
4207
+ if (isRecord(part) &&
4208
+ part["type"] === "text" &&
4209
+ typeof part["text"] === "string")
3668
4210
  texts.push(part["text"]);
3669
4211
  }
3670
4212
  if (texts.length > 0)
@@ -3675,23 +4217,52 @@ function finalAssistantText(messages) {
3675
4217
  function toClientEvent(event) {
3676
4218
  const eventType = getString(event, "type");
3677
4219
  const assistantMessageEvent = getProperty(event, "assistantMessageEvent");
3678
- if (eventType === "message_update" && getString(assistantMessageEvent, "type") === "text_delta") {
3679
- return { type: "assistant.delta", text: getString(assistantMessageEvent, "delta") ?? "" };
4220
+ if (eventType === "message_update" &&
4221
+ getString(assistantMessageEvent, "type") === "text_delta") {
4222
+ return {
4223
+ type: "assistant.delta",
4224
+ text: getString(assistantMessageEvent, "delta") ?? "",
4225
+ };
3680
4226
  }
3681
- if (eventType === "message_update" && getString(assistantMessageEvent, "type") === "thinking_delta") {
3682
- return { type: "assistant.thinking.delta", text: getString(assistantMessageEvent, "delta") ?? "" };
4227
+ if (eventType === "message_update" &&
4228
+ getString(assistantMessageEvent, "type") === "thinking_delta") {
4229
+ return {
4230
+ type: "assistant.thinking.delta",
4231
+ text: getString(assistantMessageEvent, "delta") ?? "",
4232
+ };
3683
4233
  }
3684
4234
  if (eventType === "tool_execution_start") {
3685
4235
  const args = getProperty(event, "args");
3686
- return { type: "tool.start", toolName: getString(event, "toolName") ?? "", toolCallId: getString(event, "toolCallId") ?? "", summary: summarizeToolArgs(args), args };
4236
+ return {
4237
+ type: "tool.start",
4238
+ toolName: getString(event, "toolName") ?? "",
4239
+ toolCallId: getString(event, "toolCallId") ?? "",
4240
+ summary: summarizeToolArgs(args),
4241
+ args,
4242
+ };
3687
4243
  }
3688
4244
  if (eventType === "tool_execution_update") {
3689
4245
  const partialResult = getProperty(event, "partialResult");
3690
- return { type: "tool.update", toolName: getString(event, "toolName") ?? "", toolCallId: getString(event, "toolCallId") ?? "", text: stringifyToolResult(partialResult), content: toolResultContent(partialResult), details: toolResultDetails(partialResult) };
4246
+ return {
4247
+ type: "tool.update",
4248
+ toolName: getString(event, "toolName") ?? "",
4249
+ toolCallId: getString(event, "toolCallId") ?? "",
4250
+ text: stringifyToolResult(partialResult),
4251
+ content: toolResultContent(partialResult),
4252
+ details: toolResultDetails(partialResult),
4253
+ };
3691
4254
  }
3692
4255
  if (eventType === "tool_execution_end") {
3693
4256
  const result = getProperty(event, "result");
3694
- return { type: "tool.end", toolName: getString(event, "toolName") ?? "", toolCallId: getString(event, "toolCallId") ?? "", text: stringifyToolResult(result), content: toolResultContent(result), details: toolResultDetails(result), isError: getBoolean(event, "isError") === true };
4257
+ return {
4258
+ type: "tool.end",
4259
+ toolName: getString(event, "toolName") ?? "",
4260
+ toolCallId: getString(event, "toolCallId") ?? "",
4261
+ text: stringifyToolResult(result),
4262
+ content: toolResultContent(result),
4263
+ details: toolResultDetails(result),
4264
+ isError: getBoolean(event, "isError") === true,
4265
+ };
3695
4266
  }
3696
4267
  if (eventType === "agent_start")
3697
4268
  return { type: "agent.start" };
@@ -3699,7 +4270,9 @@ function toClientEvent(event) {
3699
4270
  return { type: "agent.end" };
3700
4271
  if (eventType === "message_end") {
3701
4272
  const message = getProperty(event, "message");
3702
- return message === undefined ? { type: "message.end" } : { type: "message.end", message };
4273
+ return message === undefined
4274
+ ? { type: "message.end" }
4275
+ : { type: "message.end", message };
3703
4276
  }
3704
4277
  return { type: "pi.event", eventType: eventType ?? "unknown" };
3705
4278
  }
@@ -3712,13 +4285,18 @@ function summarizeToolArgs(args) {
3712
4285
  const path = getString(args, "path");
3713
4286
  if (path !== undefined)
3714
4287
  return path;
3715
- if (typeof args["oldText"] === "string" && typeof args["newText"] === "string")
4288
+ if (typeof args["oldText"] === "string" &&
4289
+ typeof args["newText"] === "string")
3716
4290
  return "edit text replacement";
3717
4291
  const edits = args["edits"];
3718
4292
  if (Array.isArray(edits))
3719
4293
  return `${String(edits.length)} edit${edits.length === 1 ? "" : "s"}`;
3720
- const entries = Object.entries(args).filter(([, value]) => value != null).slice(0, 3);
3721
- return entries.map(([key, value]) => `${key}: ${shortToolValue(value)}`).join(" · ");
4294
+ const entries = Object.entries(args)
4295
+ .filter(([, value]) => value != null)
4296
+ .slice(0, 3);
4297
+ return entries
4298
+ .map(([key, value]) => `${key}: ${shortToolValue(value)}`)
4299
+ .join(" · ");
3722
4300
  }
3723
4301
  function shortToolValue(value) {
3724
4302
  if (typeof value === "string")
@@ -3751,11 +4329,16 @@ function stringifyToolResult(result) {
3751
4329
  if (typeof result === "string")
3752
4330
  return result;
3753
4331
  if (Array.isArray(result))
3754
- return result.map(stringifyToolResult).filter((text) => text !== "").join("\n");
4332
+ return result
4333
+ .map(stringifyToolResult)
4334
+ .filter((text) => text !== "")
4335
+ .join("\n");
3755
4336
  if (isRecord(result)) {
3756
4337
  if (getString(result, "type") === "image")
3757
4338
  return "[image]";
3758
- const text = getString(result, "text") ?? getString(result, "content") ?? getString(result, "output");
4339
+ const text = getString(result, "text") ??
4340
+ getString(result, "content") ??
4341
+ getString(result, "output");
3759
4342
  if (text !== undefined)
3760
4343
  return text;
3761
4344
  const content = getProperty(result, "content");
@@ -3781,7 +4364,9 @@ function getBoolean(value, key) {
3781
4364
  }
3782
4365
  function providerOutputTokens(message) {
3783
4366
  const output = getProperty(getProperty(message, "usage"), "output");
3784
- return typeof output === "number" && Number.isFinite(output) && output > 0 ? output : undefined;
4367
+ return typeof output === "number" && Number.isFinite(output) && output > 0
4368
+ ? output
4369
+ : undefined;
3785
4370
  }
3786
4371
  /** Match Pi Web's 4-characters-per-token live approximation. */
3787
4372
  function estimatedOutputTokens(message) {
@@ -3816,7 +4401,9 @@ function stringifyPrimitive(value) {
3816
4401
  return "";
3817
4402
  if (typeof value === "string")
3818
4403
  return value;
3819
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
4404
+ if (typeof value === "number" ||
4405
+ typeof value === "boolean" ||
4406
+ typeof value === "bigint")
3820
4407
  return String(value);
3821
4408
  return "";
3822
4409
  }