@chainingintention/pi-web-cn 1.202607.11 → 1.202608.2

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 (104) hide show
  1. package/README.md +9 -9
  2. package/dist/cli.js +27 -12
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/{CodeViewer-WeDkBH1U.js → CodeViewer-j37b7YHz.js} +1 -1
  5. package/dist/client/assets/{TerminalPanel-DgiIksV-.js → TerminalPanel-DqDiXDrk.js} +5 -5
  6. package/dist/client/assets/{UnifiedDiffViewer-FKeWfK-h.js → UnifiedDiffViewer-AQTSNI1y.js} +1 -1
  7. package/dist/client/assets/index-DDuQX9_Y.js +4101 -0
  8. package/dist/client/index.html +1 -1
  9. package/dist/config.js +36 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/nativeServices/servicePlan.js +3 -2
  12. package/dist/nativeServices/servicePlan.js.map +1 -1
  13. package/dist/nativeServices/serviceProbe.js +7 -5
  14. package/dist/nativeServices/serviceProbe.js.map +1 -1
  15. package/dist/pi-web-plugins/info/infoInternals.js +223 -0
  16. package/dist/pi-web-plugins/info/pi-web-plugin.js +19 -22
  17. package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
  18. package/dist/pi-web-plugins/relays/package.json +9 -0
  19. package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
  20. package/dist/pi-web-plugins/relays/relayDiscovery.js +96 -0
  21. package/dist/pi-web-plugins/relays/relaysPanelElement.js +327 -0
  22. package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
  23. package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
  24. package/dist/pi-web-plugins/updates/pi-web-plugin.js +100 -100
  25. package/dist/pi-web-plugins/updates/updatesLogic.js +8 -5
  26. package/dist/plugin-api.d.ts +5 -1
  27. package/dist/server/configRoutes.js +11 -0
  28. package/dist/server/configRoutes.js.map +1 -1
  29. package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
  30. package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
  31. package/dist/server/git/gitService.js +176 -5
  32. package/dist/server/git/gitService.js.map +1 -1
  33. package/dist/server/piWebStatus.js +7 -4
  34. package/dist/server/piWebStatus.js.map +1 -1
  35. package/dist/server/realtime/sessionEventHub.js +16 -2
  36. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  37. package/dist/server/sessiond/sessionDaemonStartup.js +0 -42
  38. package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -1
  39. package/dist/server/sessiond/sessionServiceDependencies.js +31 -0
  40. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
  41. package/dist/server/sessions/askUserTool.js +105 -0
  42. package/dist/server/sessions/askUserTool.js.map +1 -0
  43. package/dist/server/sessions/authProviderOptions.js +26 -29
  44. package/dist/server/sessions/authProviderOptions.js.map +1 -1
  45. package/dist/server/sessions/authRoutes.js +9 -1
  46. package/dist/server/sessions/authRoutes.js.map +1 -1
  47. package/dist/server/sessions/authService.js +143 -33
  48. package/dist/server/sessions/authService.js.map +1 -1
  49. package/dist/server/sessions/globalProviderPolicy.js +167 -0
  50. package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
  51. package/dist/server/sessions/modelCatalogRefresher.js +192 -0
  52. package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
  53. package/dist/server/sessions/oauthLoginFlowService.js +181 -56
  54. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
  55. package/dist/server/sessions/parentSessionLocator.js +75 -0
  56. package/dist/server/sessions/parentSessionLocator.js.map +1 -0
  57. package/dist/server/sessions/pendingAskStore.js +278 -0
  58. package/dist/server/sessions/pendingAskStore.js.map +1 -0
  59. package/dist/server/sessions/piSessionService.js +1264 -199
  60. package/dist/server/sessions/piSessionService.js.map +1 -1
  61. package/dist/server/sessions/plainTextTheme.js +112 -0
  62. package/dist/server/sessions/plainTextTheme.js.map +1 -0
  63. package/dist/server/sessions/projectAuthService.js +3 -3
  64. package/dist/server/sessions/projectAuthService.js.map +1 -1
  65. package/dist/server/sessions/sessionCommandService.js +60 -5
  66. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  67. package/dist/server/sessions/sessionFileHeader.js +45 -0
  68. package/dist/server/sessions/sessionFileHeader.js.map +1 -0
  69. package/dist/server/sessions/sessionNotificationStore.js +484 -0
  70. package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
  71. package/dist/server/sessions/sessionRoutes.js +245 -1
  72. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  73. package/dist/server/sessions/sessionTreeProjection.js +250 -0
  74. package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
  75. package/dist/server/sessions/sessionUnreadStore.js +520 -0
  76. package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
  77. package/dist/server/sessions/spawnSubsessionTool.js +10 -12
  78. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  79. package/dist/server/sessions/spawnTargetResolver.js +3 -16
  80. package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
  81. package/dist/server/terminals/terminalService.js +8 -2
  82. package/dist/server/terminals/terminalService.js.map +1 -1
  83. package/dist/server/workspaces/gitWorktreeDiscovery.js +9 -0
  84. package/dist/server/workspaces/gitWorktreeDiscovery.js.map +1 -1
  85. package/dist/server/workspaces/projectWorkspaceCwds.js +22 -0
  86. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
  87. package/dist/server/workspaces/workspaceService.js +15 -2
  88. package/dist/server/workspaces/workspaceService.js.map +1 -1
  89. package/dist/shared/activity.js +9 -1
  90. package/dist/shared/activity.js.map +1 -1
  91. package/dist/shared/apiTypes.d.ts +403 -8
  92. package/dist/shared/apiTypes.js +26 -0
  93. package/dist/shared/apiTypes.js.map +1 -1
  94. package/dist/shared/capabilities.js +9 -0
  95. package/dist/shared/capabilities.js.map +1 -1
  96. package/dist/shared/federatedRoutes.js +12 -0
  97. package/dist/shared/federatedRoutes.js.map +1 -1
  98. package/docs/config.md +76 -9
  99. package/docs/plugins.md +72 -16
  100. package/install.sh +1 -1
  101. package/package.json +4 -2
  102. package/dist/client/assets/index-kgpXTDyy.js +0 -3247
  103. package/dist/server/sessions/sessionArchiveMigration.js +0 -590
  104. package/dist/server/sessions/sessionArchiveMigration.js.map +0 -1
@@ -0,0 +1,167 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { createAgentSessionServices, } from "@earendil-works/pi-coding-agent";
6
+ const LOG_CONTEXT = "global-provider-bootstrap";
7
+ const MODELS_FIELD = "models";
8
+ /**
9
+ * Snapshot the merged config Pi holds for every config-registered provider.
10
+ * Pi merges defined values over the previous registration, so the runtime's
11
+ * own record — not the extension's last argument — is the accurate baseline.
12
+ * Native providers have no comparable config and are deliberately absent.
13
+ */
14
+ function captureProviderConfigBaseline(runtime) {
15
+ const baseline = new Map();
16
+ for (const providerId of runtime.getRegisteredProviderIds()) {
17
+ const config = runtime.getRegisteredProviderConfig(providerId);
18
+ if (config)
19
+ baseline.set(providerId, config);
20
+ }
21
+ return baseline;
22
+ }
23
+ /**
24
+ * True when `incoming` would change the model catalog of `baseline` and nothing
25
+ * else.
26
+ *
27
+ * Extensions that refresh their catalog re-send a complete provider config, so
28
+ * the test is "equal to the baseline except for models", not "contains only
29
+ * models". Omitted fields are not changes: Pi's merge keeps the previous value,
30
+ * which also makes an unchanged catalog a plain replay rather than an update.
31
+ * Function-valued fields (`streamSimple`, `refreshModels`, `oauth` methods)
32
+ * compare by reference under deep strict equality, so a freshly created closure
33
+ * reads as a mismatch. That conservative direction is intentional: an unclear
34
+ * comparison must fall back to the frozen no-op.
35
+ */
36
+ function isModelsOnlyProviderUpdate(baseline, incoming) {
37
+ const baselineFields = new Map(Object.entries(baseline));
38
+ const otherFieldsMatch = Object.entries(incoming).every(([field, value]) => {
39
+ if (field === MODELS_FIELD || value === undefined)
40
+ return true;
41
+ return isDeepStrictEqual(value, baselineFields.get(field));
42
+ });
43
+ if (!otherFieldsMatch)
44
+ return false;
45
+ return incoming.models !== undefined && !isDeepStrictEqual(incoming.models, baseline.models);
46
+ }
47
+ async function loadGlobalExtensionServices(runtime, agentDir) {
48
+ const scratchCwd = await mkdtemp(join(tmpdir(), "pi-web-global-ext-"));
49
+ try {
50
+ return await createAgentSessionServices({ cwd: scratchCwd, agentDir, modelRuntime: runtime });
51
+ }
52
+ finally {
53
+ await rm(scratchCwd, { recursive: true, force: true });
54
+ }
55
+ }
56
+ function logBootstrapDiagnostic(logger, diagnostic) {
57
+ const details = {
58
+ context: LOG_CONTEXT,
59
+ diagnosticType: diagnostic.type,
60
+ diagnostic: diagnostic.message,
61
+ };
62
+ if (diagnostic.type === "error") {
63
+ logger.error(details, "global extension provider bootstrap diagnostic");
64
+ }
65
+ else if (diagnostic.type === "warning") {
66
+ logger.warn(details, "global extension provider bootstrap diagnostic");
67
+ }
68
+ else {
69
+ logger.info(details, "global extension provider bootstrap diagnostic");
70
+ }
71
+ }
72
+ function freezeProviderMutations(runtime, logger, configBaseline) {
73
+ const originalMethods = {
74
+ registerNativeProvider: runtime.registerNativeProvider.bind(runtime),
75
+ registerProvider: runtime.registerProvider.bind(runtime),
76
+ unregisterProvider: runtime.unregisterProvider.bind(runtime),
77
+ };
78
+ const loggedProviderIds = {
79
+ registerNativeProvider: new Set(),
80
+ registerProvider: new Set(),
81
+ unregisterProvider: new Set(),
82
+ };
83
+ // Logging must never turn a provider mutation into an extension failure.
84
+ const logQuietly = (details, message) => {
85
+ try {
86
+ logger.info(details, message);
87
+ }
88
+ catch {
89
+ // Intentionally ignored; the mutation decision already stands.
90
+ }
91
+ };
92
+ const logIgnoredMutation = (operation, providerId) => {
93
+ const loggedIds = loggedProviderIds[operation];
94
+ if (loggedIds.has(providerId))
95
+ return;
96
+ loggedIds.add(providerId);
97
+ logQuietly({ context: LOG_CONTEXT, operation, providerId }, "ignored provider mutation after global bootstrap");
98
+ };
99
+ const frozenMethods = {
100
+ registerProvider(providerId, config) {
101
+ const baseline = configBaseline.get(providerId);
102
+ if (!baseline || !isModelsOnlyProviderUpdate(baseline, config)) {
103
+ logIgnoredMutation("registerProvider", providerId);
104
+ return;
105
+ }
106
+ // Pi validates the registration and ends in a fire-and-forget local
107
+ // refresh, so this stays synchronous and never reaches the network.
108
+ originalMethods.registerProvider(providerId, config);
109
+ const accepted = runtime.getRegisteredProviderConfig(providerId);
110
+ // Re-read the merged record so the next comparison uses what Pi stored.
111
+ if (accepted)
112
+ configBaseline.set(providerId, accepted);
113
+ logQuietly({
114
+ context: LOG_CONTEXT,
115
+ operation: "registerProvider",
116
+ providerId,
117
+ modelCount: accepted?.models?.length ?? 0,
118
+ }, "applied models-only provider update after global bootstrap");
119
+ },
120
+ registerNativeProvider(provider) {
121
+ logIgnoredMutation("registerNativeProvider", provider.id);
122
+ },
123
+ unregisterProvider(providerId) {
124
+ logIgnoredMutation("unregisterProvider", providerId);
125
+ },
126
+ };
127
+ try {
128
+ Object.assign(runtime, frozenMethods);
129
+ }
130
+ catch (error) {
131
+ Object.assign(runtime, originalMethods);
132
+ throw error;
133
+ }
134
+ }
135
+ /**
136
+ * Load global extensions once against the shared model runtime, then make its
137
+ * extension-provider baseline immutable for the rest of the daemon lifetime.
138
+ * All sessions share this runtime, so accepting project-dependent mutations
139
+ * would leak provider configuration across workspaces. This is an accidental
140
+ * contamination guard, not a sandbox for otherwise trusted extensions.
141
+ *
142
+ * The temporary cwd is guaranteed to be empty, so Pi discovers agent-dir
143
+ * extensions without loading project resources. Documented initialization-time
144
+ * config and native registrations therefore reach the runtime through Pi's
145
+ * public service factory. Pi exposes no provider-freeze hook, so the daemon
146
+ * deliberately shadows the three public instance mutation methods afterward;
147
+ * every registration replay or later call is then a logged no-op.
148
+ *
149
+ * The one exception is a known config provider refreshing its own model
150
+ * catalog: a `registerProvider` call whose config matches the recorded
151
+ * baseline in every field except `models` is applied, because a catalog is a
152
+ * property of the provider rather than of the project. Native registration
153
+ * stays fully frozen — it passes a whole `Provider` object with no comparable
154
+ * config — as does unregistration.
155
+ */
156
+ export async function bootstrapAndFreezeGlobalExtensionProviders(runtime, agentDir, logger) {
157
+ const services = await loadGlobalExtensionServices(runtime, agentDir);
158
+ const providerIds = Object.freeze([...runtime.getRegisteredProviderIds()].sort());
159
+ freezeProviderMutations(runtime, logger, captureProviderConfigBaseline(runtime));
160
+ for (const diagnostic of services.diagnostics)
161
+ logBootstrapDiagnostic(logger, diagnostic);
162
+ for (const extensionError of services.resourceLoader.getExtensions().errors) {
163
+ logger.error({ context: LOG_CONTEXT, error: extensionError.error }, "global extension failed during provider bootstrap");
164
+ }
165
+ logger.info({ context: LOG_CONTEXT, providerIds }, "global extension provider baseline bootstrapped and frozen");
166
+ }
167
+ //# sourceMappingURL=globalProviderPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalProviderPolicy.js","sourceRoot":"","sources":["../../../src/server/sessions/globalProviderPolicy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Tick hourly and let pi decide when a fetch is actually due: pi treats stored
3
+ * catalogs as fresh for REMOTE_CATALOG_REFRESH_INTERVAL_MS (4h) and skips the
4
+ * network entirely on an unforced refresh inside that window. Ticking at
5
+ * exactly 4h would land a few seconds short of the window every time, because
6
+ * pi stamps `checkedAt` only after a fetch completes, so every other tick would
7
+ * be skipped and the real cadence would be ~8h. A shorter interval removes that
8
+ * off-by-one-latency skip, tolerates clock skew, and costs nothing when the
9
+ * cache is fresh, because scheduled runs never set `force`.
10
+ */
11
+ const DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
12
+ /** Give the daemon a moment to finish startup before the first network refresh. */
13
+ const DEFAULT_INITIAL_DELAY_MS = 15_000;
14
+ /**
15
+ * Bound every catalog refresh so a stalled provider fetch cannot run forever.
16
+ * One run covers every refreshable provider under a single signal, so this is a
17
+ * whole-cycle budget for a background job, not pi's 15s startup budget.
18
+ */
19
+ const DEFAULT_TIMEOUT_MS = 60_000;
20
+ /**
21
+ * Wait this long before the single follow-up attempt that a timed-out or failed
22
+ * run earns, so a transient network problem does not cost a whole interval.
23
+ */
24
+ const DEFAULT_RETRY_DELAY_MS = 5 * 60 * 1000;
25
+ /** A queued follow-up must keep the strongest mode requested while a run was in flight. */
26
+ const strongestMode = (left, right) => left === "forced" || right === "forced" ? "forced" : "scheduled";
27
+ const noopLogger = {
28
+ info() { },
29
+ warn() { },
30
+ error() { },
31
+ };
32
+ /**
33
+ * Refreshes provider model catalogs over the network on a background schedule.
34
+ *
35
+ * The shared ModelRuntime is constructed offline (see authService.ts), so its
36
+ * own refreshes never touch the network and stay fast on request paths. This
37
+ * refresher is the single place that deliberately performs network refreshes —
38
+ * bounded by an abort timeout, serialized through one in-flight run, stopped by
39
+ * `dispose()` even mid-flight, and off any request path. `requestRefresh()`
40
+ * additionally asks for a prompt forced refresh after events that change what
41
+ * should be listed, such as provider logins, where the cached catalog is known
42
+ * to be wrong.
43
+ *
44
+ * When the operator asked for offline behavior (`PI_OFFLINE` / `PI_WEB_OFFLINE`),
45
+ * the refresher performs no network I/O at all and the cached catalogs in
46
+ * models-store.json are used as they are.
47
+ */
48
+ export class ModelCatalogRefresher {
49
+ constructor(options) {
50
+ this.started = false;
51
+ this.disposed = false;
52
+ /**
53
+ * Aborted by `dispose()` so a refresh already in flight stops with the
54
+ * refresher instead of holding its fetch open for the rest of the timeout
55
+ * budget, which would delay daemon shutdown.
56
+ */
57
+ this.lifetime = new AbortController();
58
+ this.runtime = options.runtime;
59
+ this.logger = options.logger ?? noopLogger;
60
+ this.offline = options.offline ?? false;
61
+ this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
62
+ this.initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
63
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
64
+ this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
65
+ }
66
+ /** Idempotent: a second call keeps the timers the first call installed. */
67
+ start() {
68
+ if (this.disposed || this.started)
69
+ return;
70
+ this.started = true;
71
+ if (this.offline) {
72
+ this.logger.info({}, "offline mode is enabled; skipping background model catalog refreshes");
73
+ return;
74
+ }
75
+ this.initialTimer = setTimeout(() => { this.queueRefresh("scheduled"); }, this.initialDelayMs);
76
+ this.initialTimer.unref();
77
+ this.intervalTimer = setInterval(() => { this.queueRefresh("scheduled"); }, this.intervalMs);
78
+ this.intervalTimer.unref();
79
+ }
80
+ /**
81
+ * Ask for an immediate refresh that bypasses pi's freshness gate, for callers
82
+ * that know the cached catalog is stale (auth changes). Coalesces with
83
+ * concurrent and overlapping requests, and is a no-op in offline mode so auth
84
+ * changes never trigger network I/O either.
85
+ */
86
+ requestRefresh() {
87
+ this.queueRefresh("forced");
88
+ }
89
+ /**
90
+ * Whether a network catalog refresh is running right now. Read-only: callers
91
+ * that report what a slow operation is concurrent with need this fact, and
92
+ * must not be able to change the refresh schedule by asking for it.
93
+ */
94
+ isRefreshInFlight() {
95
+ return this.inflight !== undefined;
96
+ }
97
+ /** Terminal: stops the schedule, drops any queued follow-up, and aborts an in-flight run. */
98
+ dispose() {
99
+ this.disposed = true;
100
+ if (this.initialTimer !== undefined)
101
+ clearTimeout(this.initialTimer);
102
+ if (this.intervalTimer !== undefined)
103
+ clearInterval(this.intervalTimer);
104
+ this.clearRetryTimer();
105
+ this.lifetime.abort();
106
+ }
107
+ /**
108
+ * Single entry point for every refresh trigger. Only one run is ever in
109
+ * flight; overlapping requests collapse into one follow-up that keeps the
110
+ * strongest mode asked for, so a forced request is never downgraded.
111
+ */
112
+ queueRefresh(mode, isRetry = false) {
113
+ if (this.disposed || this.offline)
114
+ return;
115
+ // Any fresh trigger supersedes a pending retry, which keeps retries from
116
+ // stacking up behind normal activity.
117
+ if (!isRetry)
118
+ this.clearRetryTimer();
119
+ if (this.inflight !== undefined) {
120
+ this.queuedMode = strongestMode(this.queuedMode, mode);
121
+ return;
122
+ }
123
+ // runCycle() reports every failure through the logger and never rejects.
124
+ this.inflight = this.runCycle(mode, isRetry);
125
+ }
126
+ /** One run plus the follow-up it leads to: a queued request, a single retry, or nothing. */
127
+ async runCycle(mode, isRetry) {
128
+ const outcome = await this.run(mode);
129
+ this.inflight = undefined;
130
+ const queued = this.queuedMode;
131
+ this.queuedMode = undefined;
132
+ if (queued !== undefined) {
133
+ // A request that arrived during the run replaces any retry this run would
134
+ // have earned; it runs now instead.
135
+ this.queueRefresh(queued);
136
+ return;
137
+ }
138
+ // Only a first attempt earns a retry, so a failing provider can never turn
139
+ // into a retry loop.
140
+ if (outcome === "incomplete" && !isRetry)
141
+ this.scheduleRetry(mode);
142
+ }
143
+ scheduleRetry(mode) {
144
+ if (this.disposed)
145
+ return;
146
+ this.logger.info({ retryDelayMs: this.retryDelayMs, mode }, "scheduling one model catalog refresh retry");
147
+ this.retryTimer = setTimeout(() => {
148
+ this.retryTimer = undefined;
149
+ this.queueRefresh(mode, true);
150
+ }, this.retryDelayMs);
151
+ this.retryTimer.unref();
152
+ }
153
+ clearRetryTimer() {
154
+ if (this.retryTimer === undefined)
155
+ return;
156
+ clearTimeout(this.retryTimer);
157
+ this.retryTimer = undefined;
158
+ }
159
+ async run(mode) {
160
+ try {
161
+ const result = await this.runtime.refresh({
162
+ allowNetwork: true,
163
+ force: mode === "forced",
164
+ signal: AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(this.timeoutMs)]),
165
+ });
166
+ if (result.aborted && this.lifetime.signal.aborted) {
167
+ this.logStoppedByDispose();
168
+ return "incomplete";
169
+ }
170
+ if (result.aborted) {
171
+ this.logger.warn({ timeoutMs: this.timeoutMs }, "model catalog refresh timed out; keeping cached catalogs");
172
+ }
173
+ if (result.errors.size > 0) {
174
+ const providers = [...result.errors.entries()].map(([providerId, error]) => `${providerId}: ${error.message}`);
175
+ this.logger.warn({ providers }, "model catalog refresh failed for some providers; keeping cached catalogs");
176
+ }
177
+ return result.aborted || result.errors.size > 0 ? "incomplete" : "complete";
178
+ }
179
+ catch (error) {
180
+ if (this.lifetime.signal.aborted)
181
+ this.logStoppedByDispose();
182
+ else
183
+ this.logger.error({ err: error }, "model catalog refresh failed; keeping cached catalogs");
184
+ return "incomplete";
185
+ }
186
+ }
187
+ /** A shutdown abort is expected, so it must not be reported as a timeout or a fault. */
188
+ logStoppedByDispose() {
189
+ this.logger.info({}, "model catalog refresh aborted by dispose; keeping cached catalogs");
190
+ }
191
+ }
192
+ //# sourceMappingURL=modelCatalogRefresher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelCatalogRefresher.js","sourceRoot":"","sources":["../../../src/server/sessions/modelCatalogRefresher.ts"],"names":[],"mappings":"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"}