@jmfederico/pi-web 1.202607.0 → 1.202607.1

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 (97) hide show
  1. package/README.md +5 -2
  2. package/dist/cli.js +291 -320
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/{CodeViewer-tXF3UTsY.js → CodeViewer-uASb28Y1.js} +1 -1
  5. package/dist/client/assets/{TerminalPanel-D_ihiWAt.js → TerminalPanel-DfTmykFw.js} +1 -1
  6. package/dist/client/assets/{UnifiedDiffViewer-BziirXBJ.js → UnifiedDiffViewer-laQN1x7P.js} +1 -1
  7. package/dist/client/assets/{index-Brfk1lwl.js → index-Dhapi0oR.js} +396 -253
  8. package/dist/client/index.html +6 -6
  9. package/dist/client/manifest.webmanifest +4 -4
  10. package/dist/config.js +144 -2
  11. package/dist/config.js.map +1 -1
  12. package/dist/nativeServices/serviceDoctor.js +480 -0
  13. package/dist/nativeServices/serviceDoctor.js.map +1 -0
  14. package/dist/nativeServices/serviceInstall.js +35 -0
  15. package/dist/nativeServices/serviceInstall.js.map +1 -0
  16. package/dist/nativeServices/servicePlan.js +358 -0
  17. package/dist/nativeServices/servicePlan.js.map +1 -0
  18. package/dist/nativeServices/serviceProbe.js +447 -0
  19. package/dist/nativeServices/serviceProbe.js.map +1 -0
  20. package/dist/nativeServices/serviceRendering.js +122 -0
  21. package/dist/nativeServices/serviceRendering.js.map +1 -0
  22. package/dist/pi-web-plugins/updates/pi-web-plugin.js +12 -0
  23. package/dist/plugin-api.d.ts +2 -0
  24. package/dist/server/activeAgentProfileProvider.js +25 -0
  25. package/dist/server/activeAgentProfileProvider.js.map +1 -0
  26. package/dist/server/app.js +57 -16
  27. package/dist/server/app.js.map +1 -1
  28. package/dist/server/configRoutes.js +43 -9
  29. package/dist/server/configRoutes.js.map +1 -1
  30. package/dist/server/machines/machinePluginProxyRoutes.js +7 -4
  31. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
  32. package/dist/server/machines/machineProxyRoutes.js +16 -4
  33. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  34. package/dist/server/machines/machineRoutes.js +1 -1
  35. package/dist/server/machines/machineRoutes.js.map +1 -1
  36. package/dist/server/machines/machineService.js +2 -2
  37. package/dist/server/machines/machineService.js.map +1 -1
  38. package/dist/server/piPackageRoutes.js +7 -3
  39. package/dist/server/piPackageRoutes.js.map +1 -1
  40. package/dist/server/piPackageService.js +34 -2
  41. package/dist/server/piPackageService.js.map +1 -1
  42. package/dist/server/piWebPluginService.js +48 -12
  43. package/dist/server/piWebPluginService.js.map +1 -1
  44. package/dist/server/piWebReleaseLookupCache.js +37 -0
  45. package/dist/server/piWebReleaseLookupCache.js.map +1 -0
  46. package/dist/server/piWebStatus.js +33 -37
  47. package/dist/server/piWebStatus.js.map +1 -1
  48. package/dist/server/piWebStatusCache.js +19 -6
  49. package/dist/server/piWebStatusCache.js.map +1 -1
  50. package/dist/server/realtime/sessionEventHub.js +33 -7
  51. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  52. package/dist/server/sessiond/sessionDaemonStartup.js +49 -0
  53. package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -0
  54. package/dist/server/sessiond.js +88 -62
  55. package/dist/server/sessiond.js.map +1 -1
  56. package/dist/server/sessions/authProviderOptions.js +30 -32
  57. package/dist/server/sessions/authProviderOptions.js.map +1 -1
  58. package/dist/server/sessions/authRoutes.js +14 -4
  59. package/dist/server/sessions/authRoutes.js.map +1 -1
  60. package/dist/server/sessions/authService.js +89 -29
  61. package/dist/server/sessions/authService.js.map +1 -1
  62. package/dist/server/sessions/oauthLoginFlowService.js +176 -53
  63. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
  64. package/dist/server/sessions/piSessionManagerGateway.js +22 -21
  65. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  66. package/dist/server/sessions/piSessionService.js +214 -25
  67. package/dist/server/sessions/piSessionService.js.map +1 -1
  68. package/dist/server/sessions/sessionArchiveMigration.js +590 -0
  69. package/dist/server/sessions/sessionArchiveMigration.js.map +1 -0
  70. package/dist/server/sessions/sessionArchiveStore.js +7 -4
  71. package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
  72. package/dist/server/sessions/sessionRoutes.js +27 -2
  73. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  74. package/dist/server/sessions/sessionService.js +2 -0
  75. package/dist/server/sessions/sessionService.js.map +1 -0
  76. package/dist/server/sessions/spawnSubsessionTool.js +66 -26
  77. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  78. package/dist/server/terminals/terminalService.js +10 -4
  79. package/dist/server/terminals/terminalService.js.map +1 -1
  80. package/dist/sessiond/activeAgentProfile.js +31 -0
  81. package/dist/sessiond/activeAgentProfile.js.map +1 -0
  82. package/dist/sessiond/sessionDaemonClient.js +38 -0
  83. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  84. package/dist/shared/activeAgentProfile.js +76 -0
  85. package/dist/shared/activeAgentProfile.js.map +1 -0
  86. package/dist/shared/apiTypes.d.ts +97 -2
  87. package/dist/shared/apiTypes.js +2 -0
  88. package/dist/shared/apiTypes.js.map +1 -1
  89. package/dist/shared/capabilities.js +5 -0
  90. package/dist/shared/capabilities.js.map +1 -1
  91. package/dist/shared/federatedRoutes.js +4 -0
  92. package/dist/shared/federatedRoutes.js.map +1 -1
  93. package/dist/shared/piWebStatusParsing.js +6 -0
  94. package/dist/shared/piWebStatusParsing.js.map +1 -1
  95. package/docs/config.md +56 -11
  96. package/docs/plugins.md +19 -5
  97. package/package.json +8 -8
@@ -1,37 +1,38 @@
1
1
  import { readdir } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
4
- import { getAgentDir, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
4
+ import { SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
5
5
  import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
6
- export const PI_SESSION_DIR_ENV = "PI_CODING_AGENT_SESSION_DIR";
7
6
  export class SessionDirResolver {
8
- constructor(options = {}) {
9
- this.agentDir = options.agentDir ?? getAgentDir();
10
- this.env = options.env ?? process.env;
7
+ constructor(options) {
8
+ this.agentDir = options.agentDir;
9
+ this.envSessionDir = options.sessionDirEnvKeys
10
+ .map((key) => options.env[key])
11
+ .find((value) => value !== undefined && value !== "");
12
+ const configuredHome = options.env["HOME"];
13
+ this.homeDir = configuredHome !== undefined && configuredHome !== "" && isAbsolute(configuredHome) ? configuredHome : homedir();
11
14
  }
12
15
  defaultSessionsRoot() {
13
16
  return defaultPiSessionsRoot(this.agentDir);
14
17
  }
15
18
  globalEnvSessionDir() {
16
- const envSessionDir = this.env[PI_SESSION_DIR_ENV];
17
- if (envSessionDir === undefined || envSessionDir === "")
19
+ if (this.envSessionDir === undefined)
18
20
  return undefined;
19
- const expanded = expandTildePath(envSessionDir);
21
+ const expanded = expandTildePath(this.envSessionDir, this.homeDir);
20
22
  return isAbsolute(expanded) ? expanded : undefined;
21
23
  }
22
24
  resolve(cwd) {
23
- const envSessionDir = this.env[PI_SESSION_DIR_ENV];
24
- if (envSessionDir !== undefined && envSessionDir !== "") {
25
- return { source: "env", sessionDir: resolveConfiguredPath(envSessionDir, cwd), usesConfiguredSessionDir: true };
25
+ if (this.envSessionDir !== undefined) {
26
+ return { source: "env", sessionDir: resolveConfiguredPath(this.envSessionDir, cwd, this.homeDir), usesConfiguredSessionDir: true };
26
27
  }
27
28
  const settingsSessionDir = SettingsManager.create(cwd, this.agentDir).getSessionDir();
28
29
  if (settingsSessionDir !== undefined && settingsSessionDir !== "") {
29
- return { source: "settings", sessionDir: resolveConfiguredPath(settingsSessionDir, cwd), usesConfiguredSessionDir: true };
30
+ return { source: "settings", sessionDir: resolveConfiguredPath(settingsSessionDir, cwd, this.homeDir), usesConfiguredSessionDir: true };
30
31
  }
31
32
  return { source: "pi-default", sessionDir: defaultPiSessionDir(cwd, this.agentDir), usesConfiguredSessionDir: false };
32
33
  }
33
34
  }
34
- export function createPiSessionManagerGateway(options = {}) {
35
+ export function createPiSessionManagerGateway(options) {
35
36
  return new SettingsAwarePiSessionManagerGateway(new SessionDirResolver(options));
36
37
  }
37
38
  class SettingsAwarePiSessionManagerGateway {
@@ -67,7 +68,7 @@ export async function listSessionsInDir(sessionDir) {
67
68
  const sessions = await SessionManager.listAll(sessionDir);
68
69
  return sessions.map((session) => ({ ...session, cwd: canonicalizeStoredCwd(session.cwd) }));
69
70
  }
70
- export async function listSessionsInDefaultPiStore(storeRoot = defaultPiSessionsRoot()) {
71
+ export async function listSessionsInDefaultPiStore(storeRoot) {
71
72
  let entries;
72
73
  try {
73
74
  entries = await readdir(storeRoot, { withFileTypes: true });
@@ -90,25 +91,25 @@ function uniqueSessionsByPath(sessions) {
90
91
  byPath.set(session.path, session);
91
92
  return [...byPath.values()].sort((a, b) => b.modified.getTime() - a.modified.getTime());
92
93
  }
93
- export function defaultPiSessionsRoot(agentDir = getAgentDir()) {
94
+ export function defaultPiSessionsRoot(agentDir) {
94
95
  return join(agentDir, "sessions");
95
96
  }
96
- export function defaultPiSessionDir(cwd, agentDir = getAgentDir()) {
97
+ export function defaultPiSessionDir(cwd, agentDir) {
97
98
  return sessionDirInDefaultPiStore(defaultPiSessionsRoot(agentDir), cwd);
98
99
  }
99
100
  export function sessionDirInDefaultPiStore(storeRoot, cwd) {
100
101
  const safePath = `--${cwd.replace(/^[/\\]/u, "").replace(/[/\\:]/gu, "-")}--`;
101
102
  return join(storeRoot, safePath);
102
103
  }
103
- export function resolveConfiguredPath(path, cwd) {
104
- const expanded = expandTildePath(path);
104
+ export function resolveConfiguredPath(path, cwd, homeDir) {
105
+ const expanded = expandTildePath(path, homeDir);
105
106
  return isAbsolute(expanded) ? expanded : resolve(cwd, expanded);
106
107
  }
107
- function expandTildePath(path) {
108
+ function expandTildePath(path, homeDir) {
108
109
  if (path === "~")
109
- return homedir();
110
+ return homeDir;
110
111
  if (path.startsWith("~/"))
111
- return join(homedir(), path.slice(2));
112
+ return join(homeDir, path.slice(2));
112
113
  return path;
113
114
  }
114
115
  //# sourceMappingURL=piSessionManagerGateway.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"piSessionManagerGateway.js","sourceRoot":"","sources":["../../../src/server/sessions/piSessionManagerGateway.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"piSessionManagerGateway.js","sourceRoot":"","sources":["../../../src/server/sessions/piSessionManagerGateway.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import { statSync } from "node:fs";
2
+ import { join } from "node:path";
2
3
  import { open, readFile, writeFile } from "node:fs/promises";
3
- import { AuthStorage, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, defineTool, getAgentDir, ModelRegistry, SessionManager, } from "@earendil-works/pi-coding-agent";
4
+ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createEditToolDefinition, defineTool, readStoredCredential, SessionManager, } from "@earendil-works/pi-coding-agent";
5
+ import { projectBrowserMessage } from "../browserMessageProjection.js";
4
6
  import { pageMessagesAtSafeBoundary } from "./messagePaging.js";
5
7
  import { BUILTIN_COMMANDS } from "./builtinCommands.js";
6
8
  import { SessionCommandService } from "./sessionCommandService.js";
@@ -8,7 +10,6 @@ import { SessionArchiveStore } from "./sessionArchiveStore.js";
8
10
  import { findArchiveCandidateByIdOrPrefix, planSessionArchiveTree } from "./sessionArchiveTree.js";
9
11
  import { deterministicSessionName, fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
10
12
  import { computeEditPreview } from "./editPreview.js";
11
- import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
12
13
  import { attachmentsToInlineImages, saveAttachmentsToWorkspace } from "./attachmentService.js";
13
14
  import { parsePromptAttachments } from "../../shared/promptAttachments.js";
14
15
  import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
@@ -49,6 +50,107 @@ function parsePromptStreamingBehavior(value) {
49
50
  return value;
50
51
  throw new Error('Prompt streamingBehavior must be "steer" or "followUp"');
51
52
  }
53
+ function resourceDiagnosticToWarning(diagnostic, source) {
54
+ return {
55
+ severity: diagnostic.type === "error" ? "error" : "warning",
56
+ message: diagnostic.message,
57
+ source,
58
+ ...(diagnostic.path === undefined ? {} : { path: diagnostic.path }),
59
+ };
60
+ }
61
+ function runtimeDiagnosticToWarning(diagnostic) {
62
+ return { severity: diagnostic.type, message: diagnostic.message, source: "runtime" };
63
+ }
64
+ /**
65
+ * Compute the live warnings for a runtime by re-reading its current resource
66
+ * loader diagnostics, extension load errors, and runtime setup diagnostics.
67
+ *
68
+ * This mimics the TUI recomputing warnings on every (re)bind: it reads the
69
+ * runtime's current state rather than a cached snapshot, so a rebuilt runtime
70
+ * yields fresh warnings. Runtimes without SDK services (e.g. test fakes)
71
+ * contribute no warnings.
72
+ */
73
+ export function collectRuntimeWarnings(runtime) {
74
+ const warnings = [];
75
+ for (const diagnostic of runtime.diagnostics ?? [])
76
+ warnings.push(runtimeDiagnosticToWarning(diagnostic));
77
+ const resourceLoader = runtime.services?.resourceLoader;
78
+ if (resourceLoader !== undefined) {
79
+ for (const diagnostic of resourceLoader.getSkills().diagnostics)
80
+ warnings.push(resourceDiagnosticToWarning(diagnostic, "skill"));
81
+ for (const diagnostic of resourceLoader.getPrompts().diagnostics)
82
+ warnings.push(resourceDiagnosticToWarning(diagnostic, "prompt"));
83
+ for (const diagnostic of resourceLoader.getThemes().diagnostics)
84
+ warnings.push(resourceDiagnosticToWarning(diagnostic, "theme"));
85
+ for (const error of resourceLoader.getExtensions().errors) {
86
+ warnings.push({ severity: "error", message: `${error.path}: ${error.error}`, source: "extension", path: error.path });
87
+ }
88
+ }
89
+ return warnings;
90
+ }
91
+ /**
92
+ * Verbatim TUI wording for the Anthropic subscription-auth billing notice. Kept
93
+ * character-for-character in sync with `ANTHROPIC_SUBSCRIPTION_AUTH_WARNING` in
94
+ * the SDK's interactive mode so the browser shows the same message the TUI does.
95
+ */
96
+ const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
97
+ /** Mirror of the SDK TUI `isAnthropicSubscriptionAuthKey` (subscription API keys start with `sk-ant-oat`). */
98
+ function isAnthropicSubscriptionAuthKey(apiKey) {
99
+ return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
100
+ }
101
+ /**
102
+ * Dismiss id for the Anthropic subscription-auth billing notice. This is `pi`'s
103
+ * own `WarningSettings` key verbatim (`anthropicExtraUsage`): we carry the
104
+ * coupling `pi` already defines rather than inventing a parallel vocabulary, and
105
+ * {@link dismissSessionWarning} maps it back to `setWarnings`.
106
+ */
107
+ const ANTHROPIC_EXTRA_USAGE_DISMISS_ID = "anthropicExtraUsage";
108
+ /**
109
+ * Port of the TUI `maybeWarnAboutAnthropicSubscriptionAuth` gate/trigger, computed
110
+ * live from the session's current model, stored Anthropic credential, and warning
111
+ * settings. Returns the billing warning when the active provider is `anthropic`
112
+ * and auth is a subscription credential (stored `oauth`, or an `sk-ant-oat` API
113
+ * key), unless suppressed via `getWarnings().anthropicExtraUsage === false`.
114
+ *
115
+ * The stored credential is read synchronously (matching the TUI's `oauth` branch
116
+ * and the documented `sk-ant-oat` key trigger) so warnings stay part of the
117
+ * synchronous live status computation.
118
+ */
119
+ export function anthropicSubscriptionWarning(session, authPath) {
120
+ if (session.settingsManager.getWarnings().anthropicExtraUsage === false)
121
+ return undefined;
122
+ if (session.model?.provider !== "anthropic")
123
+ return undefined;
124
+ const credential = readStoredCredential("anthropic", authPath);
125
+ if (credential === undefined)
126
+ return undefined;
127
+ const isSubscriptionAuth = credential.type === "oauth"
128
+ ? true
129
+ : isAnthropicSubscriptionAuthKey(credential.key);
130
+ if (!isSubscriptionAuth)
131
+ return undefined;
132
+ return {
133
+ severity: "warning",
134
+ message: ANTHROPIC_SUBSCRIPTION_AUTH_WARNING,
135
+ source: "anthropic",
136
+ dismiss: { id: ANTHROPIC_EXTRA_USAGE_DISMISS_ID },
137
+ };
138
+ }
139
+ /**
140
+ * Durably suppress a dismissable session warning by mapping its opaque dismiss
141
+ * id back to the concrete `pi` suppression it represents. Only known ids are
142
+ * honored; unknown ids throw so a stale/forged client cannot silently no-op.
143
+ *
144
+ * This is the single place provider-specific suppression lives: the wire type,
145
+ * parser, and UI stay agnostic. Adding a future dismissable warning is a
146
+ * server-only change here plus a `dismiss` id on its producer.
147
+ */
148
+ export function dismissSessionWarning(session, dismissId) {
149
+ if (dismissId !== ANTHROPIC_EXTRA_USAGE_DISMISS_ID) {
150
+ throw new Error(`Unknown session warning dismiss id: ${dismissId}`);
151
+ }
152
+ session.settingsManager.setWarnings({ ...session.settingsManager.getWarnings(), anthropicExtraUsage: false });
153
+ }
52
154
  function defaultCreateAgentRuntime(createRuntime, options) {
53
155
  if (!(options.sessionManager instanceof SessionManager))
54
156
  throw new Error("Default runtime creation requires an SDK SessionManager");
@@ -83,9 +185,9 @@ export function createPiWebCustomToolDefinitions(cwd, delegationEnabled, spawn,
83
185
  ...(delegationEnabled && subsessions !== undefined ? createSubsessionToolDefinitions(cwd, subsessions) : []),
84
186
  ];
85
187
  }
86
- function createDefaultRuntimeFactory(authStorage, modelRegistry, sessionManagers, spawn, subsessions) {
188
+ function createDefaultRuntimeFactory(modelRuntime, sessionManagers, spawn, subsessions) {
87
189
  return async ({ cwd, agentDir, sessionManager, sessionStartEvent, initialModel, delegationToolsEnabled }) => {
88
- const services = await createAgentSessionServices({ cwd, agentDir, authStorage, modelRegistry });
190
+ const services = await createAgentSessionServices({ cwd, agentDir, modelRuntime });
89
191
  const resolvedDelegationToolsEnabled = delegationToolsEnabled
90
192
  ?? await sessionAllowsDelegationTools(sessionManager, sessionManagers);
91
193
  const customTools = createPiWebCustomToolDefinitions(cwd, resolvedDelegationToolsEnabled, spawn, subsessions);
@@ -121,7 +223,7 @@ function createPiWebEditToolDefinition(cwd) {
121
223
  });
122
224
  }
123
225
  export class PiSessionService {
124
- constructor(events, deps = {}) {
226
+ constructor(events, deps) {
125
227
  this.events = events;
126
228
  this.active = new Map();
127
229
  this.pendingSessionOpens = new Map();
@@ -144,16 +246,16 @@ export class PiSessionService {
144
246
  */
145
247
  this.subsessionNotifyArmed = new Map();
146
248
  this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
147
- this.agentDir = deps.agentDir ?? getAgentDir();
148
- this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({ agentDir: this.agentDir });
149
- this.modelRegistry = deps.modelRegistry ?? ModelRegistry.create(AuthStorage.create());
249
+ this.agentDir = deps.agentDir;
250
+ this.sessionManager = deps.sessionManager;
251
+ this.modelRuntime = deps.modelRuntime;
150
252
  this.spawnTargets = deps.spawnTargets;
151
253
  this.logger = deps.logger ?? noopLogger;
152
254
  this.now = deps.now ?? (() => new Date());
153
255
  // Subsessions are a beta capability gated behind their own flag, and they
154
256
  // also require the spawn capability (they share its project-scope resolver).
155
257
  const subsessionsActive = this.spawnTargets !== undefined && deps.subsessionsEnabled === true;
156
- this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.modelRegistry.authStorage, this.modelRegistry, this.sessionManager, this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
258
+ this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.modelRuntime, this.sessionManager, this.spawnTargets === undefined ? undefined : (input) => this.spawnSession(input), !subsessionsActive ? undefined : {
157
259
  spawn: (input) => this.spawnSubsession(input),
158
260
  list: (parentSessionId, parentSessionFile) => this.listSubsessions(parentSessionId, parentSessionFile),
159
261
  check: (parentSessionId, sessionId, parentSessionFile) => this.checkSubsession(parentSessionId, sessionId, parentSessionFile),
@@ -563,6 +665,16 @@ export class PiSessionService {
563
665
  return "error";
564
666
  return "idle";
565
667
  }
668
+ workingSubsessionIds(parentSessionId) {
669
+ const childIds = this.subsessionChildren.get(parentSessionId);
670
+ if (childIds === undefined)
671
+ return [];
672
+ return [...childIds].filter((childId) => {
673
+ const link = this.subsessionLinks.get(childId);
674
+ const active = link === undefined ? undefined : this.activeChildForSubsessionLink(link);
675
+ return active !== undefined && this.hasActiveWork(active.runtime.session);
676
+ });
677
+ }
566
678
  /**
567
679
  * Drive parent notifications from a tracked child's status. Arms a pending
568
680
  * notification while the child is working, and when it stops fires a single
@@ -583,8 +695,12 @@ export class PiSessionService {
583
695
  this.subsessionNotifyArmed.set(childId, false);
584
696
  const status = this.activities.get(childId)?.phase === "error" ? "error" : "idle";
585
697
  const finalText = finalAssistantText(historyMessages(session));
586
- const preview = finalText === "" ? "(no output)" : truncateForNotification(finalText);
587
- const text = `Subsession ${childId} stopped working (status: ${status}). Latest output:\n\n${preview}\n\nStatus and latest output are available through check_subsession with sessionId "${childId}"; its full transcript is available through read_subsession.`;
698
+ const outputSection = formatSubsessionNotificationOutput(childId, finalText);
699
+ const workingIds = this.workingSubsessionIds(link.parentSessionId);
700
+ const next = workingIds.length === 0
701
+ ? "No other tracked subsessions are working."
702
+ : `Still working: ${workingIds.join(", ")}. Continue working, or call yield_to_subsessions alone and last at the next join point. Further completion notices arrive automatically; do not poll.`;
703
+ const text = `Subsession ${childId} stopped working (${status}).\n${next}\n\n${outputSection}`;
588
704
  void this.notifyParentOfSubsession(link.parentSessionId, childId, text);
589
705
  }
590
706
  async getOrOpenParentForSubsession(parentSessionId, childSessionId) {
@@ -627,23 +743,42 @@ export class PiSessionService {
627
743
  async status(ref) {
628
744
  return this.statusFromSession(await this.getOrOpen(ref));
629
745
  }
746
+ /**
747
+ * Join-time snapshot of the in-flight assistant stream. The `seq` watermark and
748
+ * the partial are read together in one synchronous tick (no await between the
749
+ * `currentSeq` read and the `state.streamingMessage` read) so a joining client
750
+ * can seed the partial and then apply only buffered live events with
751
+ * `seq > snapshot.seq`. The partial is browser-projected to strip thinking
752
+ * signatures; it is `null` when no assistant message is mid-stream.
753
+ */
754
+ async streamSnapshot(ref) {
755
+ const session = await this.getOrOpen(ref);
756
+ // Single consistent tick: capture the watermark and the partial together so
757
+ // the seq matches the partial the client seeds against.
758
+ const seq = this.events.currentSeq(session.sessionId);
759
+ const streamingMessage = session.state.streamingMessage;
760
+ const partial = streamingMessage === undefined || streamingMessage === null
761
+ ? null
762
+ : projectBrowserMessage(streamingMessage);
763
+ return { seq, partial };
764
+ }
630
765
  async availableModels(ref) {
631
766
  const session = await this.getOrOpen(ref);
632
- session.modelRegistry.refresh();
767
+ await session.modelRuntime.reloadConfig();
633
768
  const models = session.scopedModels.length > 0
634
769
  ? session.scopedModels.map((scoped) => scoped.model)
635
- : session.modelRegistry.getAvailable();
770
+ : session.modelRuntime.getAvailableSnapshot();
636
771
  return models.map(modelToClientModel);
637
772
  }
638
773
  async setModel(ref, provider, modelId) {
639
774
  await this.assertWritable(ref);
640
775
  const session = await this.getOrOpen(ref);
641
- session.modelRegistry.refresh();
776
+ await session.modelRuntime.reloadConfig();
642
777
  const candidates = session.scopedModels.length > 0
643
778
  ? session.scopedModels.map((scoped) => scoped.model)
644
- : session.modelRegistry.getAvailable();
779
+ : session.modelRuntime.getAvailableSnapshot();
645
780
  const model = candidates.find((candidate) => candidate.provider === provider && candidate.id === modelId)
646
- ?? session.modelRegistry.find(provider, modelId);
781
+ ?? session.modelRuntime.getModel(provider, modelId);
647
782
  if (model === undefined)
648
783
  throw new Error(`Model not found: ${provider}/${modelId}`);
649
784
  await session.setModel(model);
@@ -996,6 +1131,20 @@ export class PiSessionService {
996
1131
  clearParentSessionHeader(session.sessionManager);
997
1132
  this.unregisterSubsession(session.sessionId);
998
1133
  }
1134
+ async clearQueue(ref) {
1135
+ await this.assertWritable(ref);
1136
+ const session = await this.getOrOpen(ref);
1137
+ this.clearCompactionPromptQueue(session.sessionId);
1138
+ clearSessionQueue(session);
1139
+ this.publishStatus(session);
1140
+ return this.statusFromSession(session);
1141
+ }
1142
+ async dismissWarning(ref, dismissId) {
1143
+ const session = await this.getOrOpen(ref);
1144
+ dismissSessionWarning(session, dismissId);
1145
+ this.publishStatus(session);
1146
+ return this.statusFromSession(session);
1147
+ }
999
1148
  async abort(ref) {
1000
1149
  const active = this.activeForLookup(ref);
1001
1150
  if (active === undefined)
@@ -1343,7 +1492,28 @@ export class PiSessionService {
1343
1492
  }
1344
1493
  }
1345
1494
  async bindSessionExtensions(session) {
1495
+ const baseUiContext = session.extensionRunner.getUIContext();
1496
+ const notify = (message, type) => {
1497
+ this.events.publish(session.sessionId, {
1498
+ type: "command.output",
1499
+ level: type === "error" ? "error" : "info",
1500
+ message,
1501
+ });
1502
+ };
1503
+ // PI WEB is a remote UI host, but currently only extension notifications
1504
+ // cross this boundary. Delegate every other UI method to Pi's headless
1505
+ // defaults so unsupported dialogs cancel safely instead of hanging.
1506
+ const uiContext = new Proxy(baseUiContext, {
1507
+ get(target, property, receiver) {
1508
+ if (property === "notify")
1509
+ return notify;
1510
+ const value = Reflect.get(target, property, receiver);
1511
+ return value;
1512
+ },
1513
+ });
1346
1514
  await session.bindExtensions({
1515
+ uiContext,
1516
+ mode: "rpc",
1347
1517
  onError: (error) => {
1348
1518
  const message = `${error.extensionPath}: ${error.error}`;
1349
1519
  this.publishActivity(session, "extension error", "error", message);
@@ -1457,10 +1627,11 @@ export class PiSessionService {
1457
1627
  this.publishSessionName(session);
1458
1628
  }
1459
1629
  applyAuthChange(change = {}) {
1460
- this.modelRegistry.refresh();
1630
+ // ModelRuntime.login()/logout() refresh the shared runtime before AuthService
1631
+ // emits the change, so no refresh is needed here. Keeping this synchronous
1632
+ // also lets every active session observe the same committed auth snapshot.
1461
1633
  for (const active of this.active.values()) {
1462
1634
  const { session } = active.runtime;
1463
- session.modelRegistry.refresh();
1464
1635
  this.syncCurrentModelAuthWarning(session, change.removedProviderId);
1465
1636
  this.publishStatus(session);
1466
1637
  }
@@ -1472,10 +1643,10 @@ export class PiSessionService {
1472
1643
  if (model.provider === "unknown" && model.id === "unknown")
1473
1644
  return;
1474
1645
  const warningKey = authLossWarningKey(session.sessionId, model.provider, model.id);
1475
- const registered = session.modelRegistry.find(model.provider, model.id);
1646
+ const registered = session.modelRuntime.getModel(model.provider, model.id);
1476
1647
  if (registered === undefined)
1477
1648
  return;
1478
- if (session.modelRegistry.hasConfiguredAuth(registered)) {
1649
+ if (session.modelRuntime.hasConfiguredAuth(model.provider)) {
1479
1650
  this.authLossWarnings.delete(warningKey);
1480
1651
  return;
1481
1652
  }
@@ -1619,6 +1790,7 @@ export class PiSessionService {
1619
1790
  const stats = session.getSessionStats();
1620
1791
  const model = session.model === undefined ? undefined : modelToClientModel(session.model);
1621
1792
  const contextUsage = session.getContextUsage();
1793
+ const warnings = this.warningsForSession(session);
1622
1794
  return {
1623
1795
  sessionId: session.sessionId,
1624
1796
  persisted: sessionFileExists(session.sessionFile),
@@ -1633,8 +1805,23 @@ export class PiSessionService {
1633
1805
  tokens: stats.tokens,
1634
1806
  cost: stats.cost,
1635
1807
  ...(contextUsage === undefined ? {} : { contextUsage }),
1808
+ ...(warnings.length === 0 ? {} : { warnings }),
1636
1809
  };
1637
1810
  }
1811
+ /**
1812
+ * Compute the live warning set for a session: runtime/resource diagnostics from
1813
+ * the active runtime (if any) plus the Anthropic subscription-auth notice. Read
1814
+ * fresh on each status publish so a rebuilt runtime or an auth/model change is
1815
+ * reflected without caching a stale snapshot.
1816
+ */
1817
+ warningsForSession(session) {
1818
+ const runtime = this.active.get(session.sessionId)?.runtime;
1819
+ const warnings = runtime === undefined ? [] : collectRuntimeWarnings(runtime);
1820
+ const anthropic = anthropicSubscriptionWarning(session, join(this.agentDir, "auth.json"));
1821
+ if (anthropic !== undefined)
1822
+ warnings.push(anthropic);
1823
+ return warnings;
1824
+ }
1638
1825
  pendingMessageCount(session) {
1639
1826
  return session.pendingMessageCount + this.compactionQueuedMessages(session.sessionId).length;
1640
1827
  }
@@ -2096,11 +2283,13 @@ const SUBSESSION_LINK_CUSTOM_TYPE = "pi-web.subsession.link";
2096
2283
  const SUBSESSION_CHILD_LINK_CUSTOM_TYPE = "pi-web.subsession.spawned";
2097
2284
  /** customType marking a parent-facing subsession-completion notice. */
2098
2285
  const SUBSESSION_NOTIFICATION_CUSTOM_TYPE = "subsession.completion";
2099
- const SUBSESSION_NOTIFICATION_PREVIEW_CHARS = 2000;
2100
- function truncateForNotification(text) {
2101
- if (text.length <= SUBSESSION_NOTIFICATION_PREVIEW_CHARS)
2102
- return text;
2103
- return `${text.slice(0, SUBSESSION_NOTIFICATION_PREVIEW_CHARS)}…`;
2286
+ const SUBSESSION_NOTIFICATION_MAX_OUTPUT_CHARS = 2000;
2287
+ /** Avoid duplicating a partial result in context when deliberate inspection can return the full output. */
2288
+ function formatSubsessionNotificationOutput(childSessionId, text) {
2289
+ if (text.length > SUBSESSION_NOTIFICATION_MAX_OUTPUT_CHARS) {
2290
+ return `Output from subsession ${childSessionId} was too long for this completion notice and was omitted. Call check_subsession with sessionId "${childSessionId}" to retrieve the final output.`;
2291
+ }
2292
+ return `--- SUBSESSION OUTPUT: ${childSessionId} ---\n${text === "" ? "(no output)" : text}`;
2104
2293
  }
2105
2294
  /** Most recent assistant text from a history message list, or "" if none. */
2106
2295
  function finalAssistantText(messages) {