@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.
Files changed (96) hide show
  1. package/README.md +8 -8
  2. package/dist/cli.js +17 -5
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/{CodeViewer-uASb28Y1.js → CodeViewer-CDVbMiN9.js} +1 -1
  5. package/dist/client/assets/{TerminalPanel-DfTmykFw.js → TerminalPanel-CTS1CgqF.js} +2 -2
  6. package/dist/client/assets/{UnifiedDiffViewer-laQN1x7P.js → UnifiedDiffViewer-Cqke12ZX.js} +1 -1
  7. package/dist/client/assets/index-LME0LfPb.js +4068 -0
  8. package/dist/client/index.html +1 -1
  9. package/dist/config.js +52 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/pi-web-plugins/info/infoInternals.js +208 -0
  12. package/dist/pi-web-plugins/info/pi-web-plugin.js +12 -15
  13. package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
  14. package/dist/pi-web-plugins/relays/package.json +9 -0
  15. package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
  16. package/dist/pi-web-plugins/relays/relayDiscovery.js +96 -0
  17. package/dist/pi-web-plugins/relays/relaysPanelElement.js +393 -0
  18. package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
  19. package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
  20. package/dist/plugin-api.d.ts +7 -1
  21. package/dist/server/configRoutes.js +11 -0
  22. package/dist/server/configRoutes.js.map +1 -1
  23. package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
  24. package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
  25. package/dist/server/git/gitService.js +176 -5
  26. package/dist/server/git/gitService.js.map +1 -1
  27. package/dist/server/piWebStatus.js +6 -3
  28. package/dist/server/piWebStatus.js.map +1 -1
  29. package/dist/server/realtime/sessionEventHub.js +4 -0
  30. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  31. package/dist/server/sessiond/sessionServiceDependencies.js +32 -0
  32. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
  33. package/dist/server/sessiond.js +127 -79
  34. package/dist/server/sessiond.js.map +1 -1
  35. package/dist/server/sessions/askUserTool.js +105 -0
  36. package/dist/server/sessions/askUserTool.js.map +1 -0
  37. package/dist/server/sessions/authService.js +58 -7
  38. package/dist/server/sessions/authService.js.map +1 -1
  39. package/dist/server/sessions/extensionDialogWaiters.js +76 -0
  40. package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
  41. package/dist/server/sessions/globalProviderPolicy.js +167 -0
  42. package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
  43. package/dist/server/sessions/modelCatalogRefresher.js +192 -0
  44. package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
  45. package/dist/server/sessions/parentSessionLocator.js +75 -0
  46. package/dist/server/sessions/parentSessionLocator.js.map +1 -0
  47. package/dist/server/sessions/pendingAskStore.js +278 -0
  48. package/dist/server/sessions/pendingAskStore.js.map +1 -0
  49. package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
  50. package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
  51. package/dist/server/sessions/piSessionService.js +1226 -130
  52. package/dist/server/sessions/piSessionService.js.map +1 -1
  53. package/dist/server/sessions/plainTextTheme.js +112 -0
  54. package/dist/server/sessions/plainTextTheme.js.map +1 -0
  55. package/dist/server/sessions/sessionCommandService.js +68 -9
  56. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  57. package/dist/server/sessions/sessionFileHeader.js +45 -0
  58. package/dist/server/sessions/sessionFileHeader.js.map +1 -0
  59. package/dist/server/sessions/sessionNotificationStore.js +484 -0
  60. package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
  61. package/dist/server/sessions/sessionRoutes.js +273 -1
  62. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  63. package/dist/server/sessions/sessionTreeProjection.js +250 -0
  64. package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
  65. package/dist/server/sessions/sessionUnreadStore.js +520 -0
  66. package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
  67. package/dist/server/sessions/spawnSessionTool.js +2 -2
  68. package/dist/server/sessions/spawnSessionTool.js.map +1 -1
  69. package/dist/server/sessions/spawnSubsessionTool.js +10 -12
  70. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  71. package/dist/server/sessions/spawnTargetResolver.js +3 -16
  72. package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
  73. package/dist/server/workspaces/gitWorktreeDiscovery.js +9 -0
  74. package/dist/server/workspaces/gitWorktreeDiscovery.js.map +1 -1
  75. package/dist/server/workspaces/projectWorkspaceCwds.js +22 -0
  76. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
  77. package/dist/server/workspaces/workspaceService.js +15 -2
  78. package/dist/server/workspaces/workspaceService.js.map +1 -1
  79. package/dist/shared/activity.js +9 -1
  80. package/dist/shared/activity.js.map +1 -1
  81. package/dist/shared/apiTypes.d.ts +452 -2
  82. package/dist/shared/apiTypes.js +34 -0
  83. package/dist/shared/apiTypes.js.map +1 -1
  84. package/dist/shared/capabilities.js +9 -0
  85. package/dist/shared/capabilities.js.map +1 -1
  86. package/dist/shared/federatedRoutes.js +12 -0
  87. package/dist/shared/federatedRoutes.js.map +1 -1
  88. package/docs/config.md +84 -7
  89. package/docs/plugins.md +87 -17
  90. package/install.sh +1 -1
  91. package/package.json +9 -7
  92. package/dist/client/assets/index-Dhapi0oR.js +0 -3093
  93. package/dist/server/sessiond/sessionDaemonStartup.js +0 -49
  94. package/dist/server/sessiond/sessionDaemonStartup.js.map +0 -1
  95. package/dist/server/sessions/sessionArchiveMigration.js +0 -590
  96. 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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,8BAA8B,EAC9B,uBAAuB,EACvB,wBAAwB,GASzB,MAAM,0BAA0B,CAAC;AAkClC,2FAA2F;AAC3F,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAID;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,eAAe;IAK1B,YAAY,UAAkC,EAAE;QAF/B,oBAAe,GAAG,IAAI,GAAG,EAA0B,CAAC;QAGnE,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,UAAU,CAAC;IACvD,CAAC;IAED,oEAAoE;IACpE,UAAU,CAAC,SAAiB;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;QAClE,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,KAA0B;QAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAmB;YAC1B,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC;YAC7C,OAAO;YACP,SAAS;SACV,CAAC;QACF,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO;YACL,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC;YAClB,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;SACpD,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,SAAiB,EAAE,KAAa,EAAE,UAA6B;QACpE,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;QAClE,IAAI,GAAG,EAAE,KAAK,KAAK,KAAK;YAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QACrD,0EAA0E;QAC1E,kDAAkD;QAClD,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QACpD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;IAC3F,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,SAAiB,EAAE,KAAa;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;QAClE,IAAI,GAAG,EAAE,KAAK,KAAK,KAAK;YAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC;IAC7F,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,uFAAuF;IACvF,aAAa,CAAC,SAAiB;QAC7B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3D,CAAC;IAEO,YAAY,CAAC,SAAiB,EAAE,MAA0B,EAAE,OAAwB;QAC1F,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;QACzE,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,SAAS,4BAA4B,CAAC,CAAC;QAC5G,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CACX,SAAiB,EACjB,MAA0B,EAC1B,QAAgB,EAChB,OAAwB;QAExB,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACxC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACvC,OAAO,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAEO,SAAS;QACf,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAClC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAuB;IAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,WAAW;QACzC,CAAC,CAAC,+CAA+C;QACjD,CAAC,CAAC,gCAAgC,OAAO,CAAC,MAAM,iCAAiC,CAAC;IACpF,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAuB;IAC7D,OAAO;QACL,0CAA0C,OAAO,CAAC,KAAK,kCAAkC;QACzF,oBAAoB,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KACxD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,MAA6B;IAClD,MAAM,MAAM,GAAG,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACtE,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC9F,OAAO,CAAC,MAAM,EAAE,eAAe,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,cAAc,CACrB,GAAmB,EACnB,OAAwB,EACxB,MAA0B,EAC1B,QAAgB;IAEhB,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACtG,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACzG,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;IAC9D,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,QAAQ;QACR,SAAS;QACT,aAAa;QACb,aAAa;QACb,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,QAAyB,EAAE,MAAiC;IAClF,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,EAAE,CAAC;IACpC,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,CAAC;IACpC,OAAO;QACL,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC;QACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,KAAK,SAAS;QACtD,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC;QACnB,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,aAAqB,EAAE,aAAuB;IAChF,MAAM,QAAQ,GAAG,YAAY,aAAa,CAAC,QAAQ,EAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC/E,OAAO,aAAa,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,wBAAwB,CAAC,CAAC,CAAC,GAAG,QAAQ,iBAAiB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACnI,CAAC;AAED,SAAS,iBAAiB,CAAC,SAA4B;IACrD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,2CAA2C,CAAC,CAAC;IAC7G,IAAI,SAAS,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;QAC/C,MAAM,IAAI,yBAAyB,CAAC,qCAAqC,uBAAuB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC3H,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChC,MAAM,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QACjD,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAC;QACxF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,gBAAgB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAyB,EAAE,EAAU;IAC7D,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACpD,MAAM,IAAI,yBAAyB,CAAC,YAAY,EAAE,6BAA6B,qBAAqB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC7H,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,4BAA4B,EAAE,EAAE,CAAC,CAAC;QACxE,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,0BAA0B,KAAK,gBAAgB,EAAE,EAAE,CAAC,CAAC;QACpH,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,OAAO,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC/B,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,CAAC;QAClE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,CAAC,EAAE,CAAC;QAC5F,OAAO;QACP,yEAAyE;QACzE,+DAA+D;QAC/D,UAAU,EAAE,IAAI;QAChB,GAAG,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,MAA6B,EAAE,KAAa,EAAE,UAAkB;IACtF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,OAAO;QACL,KAAK;QACL,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,mBAAmB,KAAK,gBAAgB,UAAU,EAAE,CAAC;QACtF,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,oBAAoB,KAAK,gBAAgB,UAAU,EAAE,CAAC,EAAE,CAAC;KACxH,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAmB,EAAE,UAA6B;IAC5E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,IAAI,yBAAyB,CAAC,uBAAuB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;QACpG,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9G,MAAM,SAAS,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnD,6EAA6E;QAC7E,4DAA4D;QAC5D,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,cAAc,CAAC,QAAyB,EAAE,MAAqB;IACtE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,YAAY,QAAQ,CAAC,EAAE,kBAAkB,KAAK,EAAE,CAAC,CAAC;QACpH,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,mBAAmB,KAAK,iBAAiB,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QACxH,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,MAAM,SAAS,GAAG,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACjE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,IAAI,QAAQ,CAAC,QAAQ,KAAK,IAAI,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,yBAAyB,CAAC,YAAY,QAAQ,CAAC,EAAE,0BAA0B,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,cAAc,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAyB,EAAE,SAA6B;IAClF,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC9C,IAAI,SAAS,CAAC,MAAM,GAAG,8BAA8B,EAAE,CAAC;QACtD,MAAM,IAAI,yBAAyB,CAAC,0BAA0B,QAAQ,CAAC,EAAE,2BAA2B,CAAC,CAAC;IACxG,CAAC;IACD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IACjC,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9C,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB;IACnC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;AACjG,CAAC;AAED,SAAS,aAAa,CAAC,QAAyB;IAC9C,OAAO,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;AACrF,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAiB;IACzC,IAAI,SAAS,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACrE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,SAAS,CAAC,KAAa,EAAE,KAAa;IAC7C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,MAAM,IAAI,yBAAyB,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IAC3F,IAAI,KAAK,CAAC,MAAM,GAAG,sBAAsB;QAAE,MAAM,IAAI,yBAAyB,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;IACpH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,KAAa;IAC/C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,MAAM,IAAI,yBAAyB,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IAC3F,IAAI,KAAK,CAAC,MAAM,GAAG,wBAAwB;QAAE,MAAM,IAAI,yBAAyB,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;IACtH,OAAO,KAAK,CAAC;AACf,CAAC"}