@jmfederico/pi-web 1.202607.1 → 1.202607.3
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.
- package/README.md +8 -8
- package/dist/cli.js +17 -5
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-uASb28Y1.js → CodeViewer-CDVbMiN9.js} +1 -1
- package/dist/client/assets/{TerminalPanel-DfTmykFw.js → TerminalPanel-CTS1CgqF.js} +2 -2
- package/dist/client/assets/{UnifiedDiffViewer-laQN1x7P.js → UnifiedDiffViewer-Cqke12ZX.js} +1 -1
- package/dist/client/assets/index-LME0LfPb.js +4068 -0
- package/dist/client/index.html +1 -1
- package/dist/config.js +52 -0
- package/dist/config.js.map +1 -1
- package/dist/pi-web-plugins/info/infoInternals.js +208 -0
- package/dist/pi-web-plugins/info/pi-web-plugin.js +12 -15
- package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
- package/dist/pi-web-plugins/relays/package.json +9 -0
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
- package/dist/pi-web-plugins/relays/relayDiscovery.js +96 -0
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +393 -0
- package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
- package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
- package/dist/plugin-api.d.ts +7 -1
- package/dist/server/configRoutes.js +11 -0
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
- package/dist/server/git/gitService.js +176 -5
- package/dist/server/git/gitService.js.map +1 -1
- package/dist/server/piWebStatus.js +6 -3
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +4 -0
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +32 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
- package/dist/server/sessiond.js +127 -79
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/askUserTool.js +105 -0
- package/dist/server/sessions/askUserTool.js.map +1 -0
- package/dist/server/sessions/authService.js +58 -7
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/extensionDialogWaiters.js +76 -0
- package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +167 -0
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +192 -0
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
- package/dist/server/sessions/parentSessionLocator.js +75 -0
- package/dist/server/sessions/parentSessionLocator.js.map +1 -0
- package/dist/server/sessions/pendingAskStore.js +278 -0
- package/dist/server/sessions/pendingAskStore.js.map +1 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +1226 -130
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/plainTextTheme.js +112 -0
- package/dist/server/sessions/plainTextTheme.js.map +1 -0
- package/dist/server/sessions/sessionCommandService.js +68 -9
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionFileHeader.js +45 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -0
- package/dist/server/sessions/sessionNotificationStore.js +484 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +273 -1
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionTreeProjection.js +250 -0
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
- package/dist/server/sessions/sessionUnreadStore.js +520 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
- package/dist/server/sessions/spawnSessionTool.js +2 -2
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +10 -12
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +3 -16
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +9 -0
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +1 -1
- package/dist/server/workspaces/projectWorkspaceCwds.js +22 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
- package/dist/server/workspaces/workspaceService.js +15 -2
- package/dist/server/workspaces/workspaceService.js.map +1 -1
- package/dist/shared/activity.js +9 -1
- package/dist/shared/activity.js.map +1 -1
- package/dist/shared/apiTypes.d.ts +452 -2
- package/dist/shared/apiTypes.js +34 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +9 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +12 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/docs/config.md +84 -7
- package/docs/plugins.md +87 -17
- package/install.sh +1 -1
- package/package.json +9 -7
- package/dist/client/assets/index-Dhapi0oR.js +0 -3093
- package/dist/server/sessiond/sessionDaemonStartup.js +0 -49
- package/dist/server/sessiond/sessionDaemonStartup.js.map +0 -1
- package/dist/server/sessions/sessionArchiveMigration.js +0 -590
- package/dist/server/sessions/sessionArchiveMigration.js.map +0 -1
|
@@ -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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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
2
|
+
/**
|
|
3
|
+
* Locate the parents of `sessions` that live in a different working directory,
|
|
4
|
+
* keyed by the `parentSessionPath` the child recorded.
|
|
5
|
+
*
|
|
6
|
+
* Sessions are listed per working directory, so a child spawned into another
|
|
7
|
+
* worktree of the same project has a `parentSessionPath` that resolves to
|
|
8
|
+
* nothing in its own list. The parent's cwd and id are recorded in the parent
|
|
9
|
+
* session file's header, so one small header read per distinct missing parent is
|
|
10
|
+
* enough to point at it — no cross-workspace listing required.
|
|
11
|
+
*
|
|
12
|
+
* Parents already present in `sessions` are skipped without a read: they are
|
|
13
|
+
* linkable by path already, so the IO would buy nothing. Headers for a given
|
|
14
|
+
* path are immutable in practice, so `readHeader` is free to cache; this
|
|
15
|
+
* function stays stateless.
|
|
16
|
+
*/
|
|
17
|
+
export async function locateOutOfListingParents(sessions, listingCwd, readHeader) {
|
|
18
|
+
const located = new Map();
|
|
19
|
+
for (const parentSessionPath of missingParentPaths(sessions)) {
|
|
20
|
+
const location = await parentLocation(parentSessionPath, readHeader);
|
|
21
|
+
// A parent sharing the listing's cwd is not "elsewhere": it is absent for
|
|
22
|
+
// some other reason (archived and moved, or simply not listed), and offering
|
|
23
|
+
// a jump to this same workspace would not help.
|
|
24
|
+
if (location === undefined || cwdPathsEqual(location.parentSessionCwd, listingCwd))
|
|
25
|
+
continue;
|
|
26
|
+
located.set(parentSessionPath, location);
|
|
27
|
+
}
|
|
28
|
+
return located;
|
|
29
|
+
}
|
|
30
|
+
/** Distinct recorded parent paths that no session in the listing occupies. */
|
|
31
|
+
function missingParentPaths(sessions) {
|
|
32
|
+
const listedPaths = new Set(sessions.map((session) => canonicalizeStoredCwd(session.path)));
|
|
33
|
+
const missing = new Set();
|
|
34
|
+
for (const session of sessions) {
|
|
35
|
+
const parentSessionPath = session.parentSessionPath;
|
|
36
|
+
if (parentSessionPath === undefined || parentSessionPath === "")
|
|
37
|
+
continue;
|
|
38
|
+
if (listedPaths.has(canonicalizeStoredCwd(parentSessionPath)))
|
|
39
|
+
continue;
|
|
40
|
+
missing.add(parentSessionPath);
|
|
41
|
+
}
|
|
42
|
+
return missing;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A parent whose header is unreadable or carries no cwd (very old session files)
|
|
46
|
+
* has no reportable location, so the browser keeps saying only that the parent is
|
|
47
|
+
* unavailable.
|
|
48
|
+
*/
|
|
49
|
+
async function parentLocation(parentSessionPath, readHeader) {
|
|
50
|
+
const header = await readHeader(parentSessionPath);
|
|
51
|
+
if (header?.cwd === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
return { parentSessionId: header.id, parentSessionCwd: canonicalizeStoredCwd(header.cwd) };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Count, per listed session path, how many sessions outside this listing record
|
|
57
|
+
* it as their parent, so a parent row can show that it has children that are not
|
|
58
|
+
* visible beneath it.
|
|
59
|
+
*
|
|
60
|
+
* Children are identified only by the parent session *file* path they recorded,
|
|
61
|
+
* which is exactly the link Pi writes into the child header; neither a header
|
|
62
|
+
* read of the parent nor the child's own location is required.
|
|
63
|
+
*/
|
|
64
|
+
export function countOutOfListingChildren(sessions, childParentSessionPaths) {
|
|
65
|
+
const listedPaths = new Map(sessions.map((session) => [canonicalizeStoredCwd(session.path), session.path]));
|
|
66
|
+
const counts = new Map();
|
|
67
|
+
for (const parentSessionPath of childParentSessionPaths) {
|
|
68
|
+
const listedPath = listedPaths.get(canonicalizeStoredCwd(parentSessionPath));
|
|
69
|
+
if (listedPath === undefined)
|
|
70
|
+
continue;
|
|
71
|
+
counts.set(listedPath, (counts.get(listedPath) ?? 0) + 1);
|
|
72
|
+
}
|
|
73
|
+
return counts;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=parentSessionLocator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parentSessionLocator.js","sourceRoot":"","sources":["../../../src/server/sessions/parentSessionLocator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAkB9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,QAA2C,EAC3C,UAAkB,EAClB,UAA+B;IAE/B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiC,CAAC;IACzD,KAAK,MAAM,iBAAiB,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC;QACrE,0EAA0E;QAC1E,6EAA6E;QAC7E,gDAAgD;QAChD,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,CAAC,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC;YAAE,SAAS;QAC7F,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,SAAS,kBAAkB,CAAC,QAA2C;IACrE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;QACpD,IAAI,iBAAiB,KAAK,SAAS,IAAI,iBAAiB,KAAK,EAAE;YAAE,SAAS;QAC1E,IAAI,WAAW,CAAC,GAAG,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;YAAE,SAAS;QACxE,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,cAAc,CAAC,iBAAyB,EAAE,UAA+B;IACtF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACnD,IAAI,MAAM,EAAE,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAChD,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,EAAE,gBAAgB,EAAE,qBAAqB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AAC7F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CACvC,QAA2C,EAC3C,uBAA0C;IAE1C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,iBAAiB,IAAI,uBAAuB,EAAE,CAAC;QACxD,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC7E,IAAI,UAAU,KAAK,SAAS;YAAE,SAAS;QACvC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ASK_USER_ID_MAX_LENGTH, ASK_USER_OPTION_LIMIT, ASK_USER_OTHER_TEXT_MAX_LENGTH, ASK_USER_QUESTION_LIMIT, ASK_USER_TEXT_MAX_LENGTH, } from "../../shared/apiTypes.js";
|
|
3
|
+
/** Rejected input: a question set is malformed, or an answer does not fit its question. */
|
|
4
|
+
export class PendingAskValidationError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "PendingAskValidationError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Daemon-owned open-ask state: one unanswered question set per session.
|
|
12
|
+
*
|
|
13
|
+
* The store is pure domain logic — no Fastify, no Pi session, no I/O, no timers.
|
|
14
|
+
* It validates asks and answers, owns the open/supersede/submit/cancel
|
|
15
|
+
* transitions, and computes the answered-versus-unanswered outcome that both the
|
|
16
|
+
* model-facing message and the browser record are rendered from. Callers publish
|
|
17
|
+
* the returned asks and outcomes; the store never emits anything itself.
|
|
18
|
+
*
|
|
19
|
+
* State is deliberately daemon-lifetime and in-memory. An open ask is meaningful
|
|
20
|
+
* only while the session runtime that posted it exists, and browsers rehydrate it
|
|
21
|
+
* from `SessionStatus` rather than from disk.
|
|
22
|
+
*/
|
|
23
|
+
export class PendingAskStore {
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.openBySessionId = new Map();
|
|
26
|
+
this.now = options.now ?? (() => new Date());
|
|
27
|
+
this.createAskId = options.createAskId ?? randomUUID;
|
|
28
|
+
}
|
|
29
|
+
/** The session's open ask, for {@link SessionStatus} projection. */
|
|
30
|
+
pendingAsk(sessionId) {
|
|
31
|
+
const ask = this.openBySessionId.get(requireSessionId(sessionId));
|
|
32
|
+
return ask === undefined ? undefined : cloneAsk(ask);
|
|
33
|
+
}
|
|
34
|
+
open(input) {
|
|
35
|
+
const sessionId = requireSessionId(input.sessionId);
|
|
36
|
+
const questions = validateQuestions(input.questions);
|
|
37
|
+
const askedAt = this.timestamp();
|
|
38
|
+
const superseded = this.close(sessionId, "superseded", askedAt, new Map());
|
|
39
|
+
const ask = {
|
|
40
|
+
askId: requireId(this.createAskId(), "askId"),
|
|
41
|
+
askedAt,
|
|
42
|
+
questions,
|
|
43
|
+
};
|
|
44
|
+
this.openBySessionId.set(sessionId, ask);
|
|
45
|
+
return {
|
|
46
|
+
ask: cloneAsk(ask),
|
|
47
|
+
...(superseded === undefined ? {} : { superseded }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Record what the user replied and close the ask. Answers are validated against
|
|
52
|
+
* the open ask, so a submission that does not fit its questions is rejected
|
|
53
|
+
* rather than silently truncated; the ask stays open in that case.
|
|
54
|
+
*/
|
|
55
|
+
submit(sessionId, askId, submission) {
|
|
56
|
+
const ask = this.openBySessionId.get(requireSessionId(sessionId));
|
|
57
|
+
if (ask?.askId !== askId)
|
|
58
|
+
return { status: "stale" };
|
|
59
|
+
// Validate before closing so a submission that does not fit its questions
|
|
60
|
+
// leaves the ask open for the browser to correct.
|
|
61
|
+
const answers = validateSubmission(ask, submission);
|
|
62
|
+
return { status: "closed", outcome: this.requireClose(sessionId, "submitted", answers) };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Close the session's open ask without a submission. The outcome reports every question as
|
|
66
|
+
* unanswered, because answers only ever reach the daemon through a submit.
|
|
67
|
+
*/
|
|
68
|
+
cancel(sessionId, askId) {
|
|
69
|
+
const ask = this.openBySessionId.get(requireSessionId(sessionId));
|
|
70
|
+
if (ask?.askId !== askId)
|
|
71
|
+
return { status: "stale" };
|
|
72
|
+
return { status: "closed", outcome: this.requireClose(sessionId, "cancelled", new Map()) };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Close whatever ask the session currently has open, e.g. because the user sent
|
|
76
|
+
* an ordinary chat message instead of answering the form. Returns the outcome,
|
|
77
|
+
* or `undefined` when the session has no open ask.
|
|
78
|
+
*/
|
|
79
|
+
cancelOpen(sessionId) {
|
|
80
|
+
return this.close(requireSessionId(sessionId), "cancelled", this.timestamp(), new Map());
|
|
81
|
+
}
|
|
82
|
+
/** Drop the open ask of a session that is going away, without reporting an outcome. */
|
|
83
|
+
forgetSession(sessionId) {
|
|
84
|
+
this.openBySessionId.delete(requireSessionId(sessionId));
|
|
85
|
+
}
|
|
86
|
+
requireClose(sessionId, reason, answers) {
|
|
87
|
+
const outcome = this.close(sessionId, reason, this.timestamp(), answers);
|
|
88
|
+
if (outcome === undefined)
|
|
89
|
+
throw new Error(`Pending ask of session ${sessionId} disappeared while closing`);
|
|
90
|
+
return outcome;
|
|
91
|
+
}
|
|
92
|
+
close(sessionId, reason, closedAt, answers) {
|
|
93
|
+
const ask = this.openBySessionId.get(sessionId);
|
|
94
|
+
if (ask === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
this.openBySessionId.delete(sessionId);
|
|
97
|
+
return askUserOutcome(ask, answers, reason, closedAt);
|
|
98
|
+
}
|
|
99
|
+
timestamp() {
|
|
100
|
+
return this.now().toISOString();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Model-facing text of a closed ask. The model reads this, so it must name the
|
|
105
|
+
* unanswered questions as plainly as the answered ones.
|
|
106
|
+
*/
|
|
107
|
+
export function renderAskUserAnswersText(outcome) {
|
|
108
|
+
const lead = outcome.reason === "submitted"
|
|
109
|
+
? "The user submitted answers to your questions."
|
|
110
|
+
: `The question set was closed (${outcome.reason}) before it was fully answered.`;
|
|
111
|
+
return [lead, "", ...outcome.questions.map(questionLines).flat(), "", outcome.summary].join("\n");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Notice for the model when a new ask replaced one the user never answered, so a
|
|
115
|
+
* supersede is never a silent loss of the earlier questions.
|
|
116
|
+
*/
|
|
117
|
+
export function renderSupersededAskText(outcome) {
|
|
118
|
+
return [
|
|
119
|
+
`This replaced an earlier question set (${outcome.askId}) that the user never submitted.`,
|
|
120
|
+
`Left unanswered: ${outcome.unansweredIds.join(", ")}.`,
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
function questionLines(record) {
|
|
124
|
+
const header = `- ${record.question.id}: ${record.question.question}`;
|
|
125
|
+
if (!record.answered)
|
|
126
|
+
return [header, " Unanswered."];
|
|
127
|
+
const parts = [...record.values.map((value) => `selected ${value}`)];
|
|
128
|
+
if (record.otherText !== undefined)
|
|
129
|
+
parts.push(`custom: ${JSON.stringify(record.otherText)}`);
|
|
130
|
+
return [header, ` Answered: ${parts.join("; ")}`];
|
|
131
|
+
}
|
|
132
|
+
function askUserOutcome(ask, answers, reason, closedAt) {
|
|
133
|
+
const questions = ask.questions.map((question) => questionRecord(question, answers.get(question.id)));
|
|
134
|
+
const unansweredIds = questions.filter((record) => !record.answered).map((record) => record.question.id);
|
|
135
|
+
const answeredCount = questions.length - unansweredIds.length;
|
|
136
|
+
return {
|
|
137
|
+
askId: ask.askId,
|
|
138
|
+
reason,
|
|
139
|
+
askedAt: ask.askedAt,
|
|
140
|
+
closedAt,
|
|
141
|
+
questions,
|
|
142
|
+
answeredCount,
|
|
143
|
+
unansweredIds,
|
|
144
|
+
summary: summaryLine(questions.length, answeredCount, unansweredIds),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function questionRecord(question, answer) {
|
|
148
|
+
const values = answer?.values ?? [];
|
|
149
|
+
const otherText = answer?.otherText;
|
|
150
|
+
return {
|
|
151
|
+
question: cloneQuestion(question),
|
|
152
|
+
answered: values.length > 0 || otherText !== undefined,
|
|
153
|
+
values: [...values],
|
|
154
|
+
...(otherText === undefined ? {} : { otherText }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function summaryLine(total, answeredCount, unansweredIds) {
|
|
158
|
+
const answered = `Answered ${answeredCount.toString()} of ${total.toString()}`;
|
|
159
|
+
return unansweredIds.length === 0 ? `${answered}; none left unanswered` : `${answered}; unanswered: ${unansweredIds.join(", ")}`;
|
|
160
|
+
}
|
|
161
|
+
function validateQuestions(questions) {
|
|
162
|
+
if (questions.length === 0)
|
|
163
|
+
throw new PendingAskValidationError("An ask must contain at least one question");
|
|
164
|
+
if (questions.length > ASK_USER_QUESTION_LIMIT) {
|
|
165
|
+
throw new PendingAskValidationError(`An ask must not contain more than ${ASK_USER_QUESTION_LIMIT.toString()} questions`);
|
|
166
|
+
}
|
|
167
|
+
const seenIds = new Set();
|
|
168
|
+
return questions.map((question) => {
|
|
169
|
+
const id = requireId(question.id, "question id");
|
|
170
|
+
if (seenIds.has(id))
|
|
171
|
+
throw new PendingAskValidationError(`Duplicate question id ${id}`);
|
|
172
|
+
seenIds.add(id);
|
|
173
|
+
return validateQuestion(question, id);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function validateQuestion(question, id) {
|
|
177
|
+
if (question.options.length > ASK_USER_OPTION_LIMIT) {
|
|
178
|
+
throw new PendingAskValidationError(`Question ${id} must not offer more than ${ASK_USER_OPTION_LIMIT.toString()} options`);
|
|
179
|
+
}
|
|
180
|
+
const seenValues = new Set();
|
|
181
|
+
const options = question.options.map((option) => {
|
|
182
|
+
const value = requireId(option.value, `option value of question ${id}`);
|
|
183
|
+
if (seenValues.has(value))
|
|
184
|
+
throw new PendingAskValidationError(`Duplicate option value ${value} in question ${id}`);
|
|
185
|
+
seenValues.add(value);
|
|
186
|
+
return validateOption(option, value, id);
|
|
187
|
+
});
|
|
188
|
+
const detail = question.detail;
|
|
189
|
+
return {
|
|
190
|
+
id,
|
|
191
|
+
question: requireText(question.question, `text of question ${id}`),
|
|
192
|
+
...(detail === undefined ? {} : { detail: requireText(detail, `detail of question ${id}`) }),
|
|
193
|
+
options,
|
|
194
|
+
// Every question accepts a custom answer. Retain the marker so older web
|
|
195
|
+
// clients also expose the field when connected to this daemon.
|
|
196
|
+
allowOther: true,
|
|
197
|
+
...(question.multiple === true ? { multiple: true } : {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function validateOption(option, value, questionId) {
|
|
201
|
+
const detail = option.detail;
|
|
202
|
+
return {
|
|
203
|
+
value,
|
|
204
|
+
label: requireText(option.label, `label of option ${value} in question ${questionId}`),
|
|
205
|
+
...(detail === undefined ? {} : { detail: requireText(detail, `detail of option ${value} in question ${questionId}`) }),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function validateSubmission(ask, submission) {
|
|
209
|
+
const questionsById = new Map(ask.questions.map((question) => [question.id, question]));
|
|
210
|
+
const answers = new Map();
|
|
211
|
+
for (const answer of submission.answers) {
|
|
212
|
+
const question = questionsById.get(answer.id);
|
|
213
|
+
if (question === undefined)
|
|
214
|
+
throw new PendingAskValidationError(`Unknown question id ${answer.id}`);
|
|
215
|
+
if (answers.has(answer.id))
|
|
216
|
+
throw new PendingAskValidationError(`Duplicate answer for question ${answer.id}`);
|
|
217
|
+
const validated = validateAnswer(question, answer);
|
|
218
|
+
// Untouched questions and explicitly empty answers are the same thing, so an
|
|
219
|
+
// empty answer is dropped rather than recorded as answered.
|
|
220
|
+
if (validated !== undefined)
|
|
221
|
+
answers.set(answer.id, validated);
|
|
222
|
+
}
|
|
223
|
+
return answers;
|
|
224
|
+
}
|
|
225
|
+
function validateAnswer(question, answer) {
|
|
226
|
+
const optionValues = new Set(question.options.map((option) => option.value));
|
|
227
|
+
const values = [];
|
|
228
|
+
for (const value of answer.values) {
|
|
229
|
+
if (!optionValues.has(value))
|
|
230
|
+
throw new PendingAskValidationError(`Question ${question.id} has no option ${value}`);
|
|
231
|
+
if (values.includes(value))
|
|
232
|
+
throw new PendingAskValidationError(`Duplicate value ${value} for question ${question.id}`);
|
|
233
|
+
values.push(value);
|
|
234
|
+
}
|
|
235
|
+
const otherText = normalizeOtherText(question, answer.otherText);
|
|
236
|
+
const selectionCount = values.length + (otherText === undefined ? 0 : 1);
|
|
237
|
+
if (question.multiple !== true && selectionCount > 1) {
|
|
238
|
+
throw new PendingAskValidationError(`Question ${question.id} accepts a single answer`);
|
|
239
|
+
}
|
|
240
|
+
if (selectionCount === 0)
|
|
241
|
+
return undefined;
|
|
242
|
+
return { id: question.id, values, ...(otherText === undefined ? {} : { otherText }) };
|
|
243
|
+
}
|
|
244
|
+
function normalizeOtherText(question, otherText) {
|
|
245
|
+
if (otherText === undefined)
|
|
246
|
+
return undefined;
|
|
247
|
+
if (otherText.length > ASK_USER_OTHER_TEXT_MAX_LENGTH) {
|
|
248
|
+
throw new PendingAskValidationError(`Other text of question ${question.id} exceeds its length limit`);
|
|
249
|
+
}
|
|
250
|
+
const trimmed = otherText.trim();
|
|
251
|
+
return trimmed === "" ? undefined : trimmed;
|
|
252
|
+
}
|
|
253
|
+
function cloneAsk(ask) {
|
|
254
|
+
return { askId: ask.askId, askedAt: ask.askedAt, questions: ask.questions.map(cloneQuestion) };
|
|
255
|
+
}
|
|
256
|
+
function cloneQuestion(question) {
|
|
257
|
+
return { ...question, options: question.options.map((option) => ({ ...option })) };
|
|
258
|
+
}
|
|
259
|
+
function requireSessionId(sessionId) {
|
|
260
|
+
if (sessionId === "")
|
|
261
|
+
throw new Error("sessionId must not be empty");
|
|
262
|
+
return sessionId;
|
|
263
|
+
}
|
|
264
|
+
function requireId(value, field) {
|
|
265
|
+
if (value.trim() === "")
|
|
266
|
+
throw new PendingAskValidationError(`${field} must not be empty`);
|
|
267
|
+
if (value.length > ASK_USER_ID_MAX_LENGTH)
|
|
268
|
+
throw new PendingAskValidationError(`${field} exceeds its length limit`);
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
function requireText(value, field) {
|
|
272
|
+
if (value.trim() === "")
|
|
273
|
+
throw new PendingAskValidationError(`${field} must not be empty`);
|
|
274
|
+
if (value.length > ASK_USER_TEXT_MAX_LENGTH)
|
|
275
|
+
throw new PendingAskValidationError(`${field} exceeds its length limit`);
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=pendingAskStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pendingAskStore.js","sourceRoot":"","sources":["../../../src/server/sessions/pendingAskStore.ts"],"names":[],"mappings":"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"}
|