wicked-crew 0.5.0 → 0.7.0

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 (167) hide show
  1. package/dist/api/audit.d.ts +70 -0
  2. package/dist/api/audit.d.ts.map +1 -0
  3. package/dist/api/audit.js +131 -0
  4. package/dist/api/audit.js.map +1 -0
  5. package/dist/api/auth.d.ts +192 -0
  6. package/dist/api/auth.d.ts.map +1 -0
  7. package/dist/api/auth.js +515 -0
  8. package/dist/api/auth.js.map +1 -0
  9. package/dist/api/gate-cache.d.ts +8 -0
  10. package/dist/api/gate-cache.d.ts.map +1 -1
  11. package/dist/api/gate-cache.js +10 -0
  12. package/dist/api/gate-cache.js.map +1 -1
  13. package/dist/api/guidance-index.d.ts +39 -0
  14. package/dist/api/guidance-index.d.ts.map +1 -0
  15. package/dist/api/guidance-index.js +67 -0
  16. package/dist/api/guidance-index.js.map +1 -0
  17. package/dist/api/open-path.d.ts +34 -0
  18. package/dist/api/open-path.d.ts.map +1 -0
  19. package/dist/api/open-path.js +101 -0
  20. package/dist/api/open-path.js.map +1 -0
  21. package/dist/api/retry-index.d.ts +30 -0
  22. package/dist/api/retry-index.d.ts.map +1 -0
  23. package/dist/api/retry-index.js +45 -0
  24. package/dist/api/retry-index.js.map +1 -0
  25. package/dist/api/routes.d.ts +164 -1
  26. package/dist/api/routes.d.ts.map +1 -1
  27. package/dist/api/routes.js +769 -21
  28. package/dist/api/routes.js.map +1 -1
  29. package/dist/api/run-files.d.ts +63 -0
  30. package/dist/api/run-files.d.ts.map +1 -0
  31. package/dist/api/run-files.js +271 -0
  32. package/dist/api/run-files.js.map +1 -0
  33. package/dist/api/seat-health.d.ts +55 -0
  34. package/dist/api/seat-health.d.ts.map +1 -0
  35. package/dist/api/seat-health.js +273 -0
  36. package/dist/api/seat-health.js.map +1 -0
  37. package/dist/api/seat-signin.d.ts +27 -0
  38. package/dist/api/seat-signin.d.ts.map +1 -0
  39. package/dist/api/seat-signin.js +143 -0
  40. package/dist/api/seat-signin.js.map +1 -0
  41. package/dist/api/server.d.ts +190 -3
  42. package/dist/api/server.d.ts.map +1 -1
  43. package/dist/api/server.js +365 -11
  44. package/dist/api/server.js.map +1 -1
  45. package/dist/api/stall-watchdog.d.ts +62 -0
  46. package/dist/api/stall-watchdog.d.ts.map +1 -0
  47. package/dist/api/stall-watchdog.js +138 -0
  48. package/dist/api/stall-watchdog.js.map +1 -0
  49. package/dist/cli/index.js +171 -12
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/cli/mcp.d.ts +14 -0
  52. package/dist/cli/mcp.d.ts.map +1 -0
  53. package/dist/cli/mcp.js +119 -0
  54. package/dist/cli/mcp.js.map +1 -0
  55. package/dist/core/adapter.d.ts +87 -10
  56. package/dist/core/adapter.d.ts.map +1 -1
  57. package/dist/core/adapter.js +347 -28
  58. package/dist/core/adapter.js.map +1 -1
  59. package/dist/core/bridge-reaper.d.ts +134 -0
  60. package/dist/core/bridge-reaper.d.ts.map +1 -0
  61. package/dist/core/bridge-reaper.js +286 -0
  62. package/dist/core/bridge-reaper.js.map +1 -0
  63. package/dist/core/deliver.d.ts +118 -0
  64. package/dist/core/deliver.d.ts.map +1 -0
  65. package/dist/core/deliver.js +241 -0
  66. package/dist/core/deliver.js.map +1 -0
  67. package/dist/core/deliverable-floor.d.ts +103 -0
  68. package/dist/core/deliverable-floor.d.ts.map +1 -0
  69. package/dist/core/deliverable-floor.js +173 -0
  70. package/dist/core/deliverable-floor.js.map +1 -0
  71. package/dist/core/exec.d.ts +2 -0
  72. package/dist/core/exec.d.ts.map +1 -1
  73. package/dist/core/exec.js.map +1 -1
  74. package/dist/core/types.d.ts +92 -352
  75. package/dist/core/types.d.ts.map +1 -1
  76. package/dist/core/types.js +14 -4
  77. package/dist/core/types.js.map +1 -1
  78. package/dist/interactive/bridge-pool.d.ts +99 -0
  79. package/dist/interactive/bridge-pool.d.ts.map +1 -0
  80. package/dist/interactive/bridge-pool.js +244 -0
  81. package/dist/interactive/bridge-pool.js.map +1 -0
  82. package/dist/interactive/bridge-root.d.ts +36 -0
  83. package/dist/interactive/bridge-root.d.ts.map +1 -0
  84. package/dist/interactive/bridge-root.js +48 -0
  85. package/dist/interactive/bridge-root.js.map +1 -0
  86. package/dist/interactive/chat-events.d.ts +207 -0
  87. package/dist/interactive/chat-events.d.ts.map +1 -0
  88. package/dist/interactive/chat-events.js +769 -0
  89. package/dist/interactive/chat-events.js.map +1 -0
  90. package/dist/interactive/demo-events.d.ts +283 -0
  91. package/dist/interactive/demo-events.d.ts.map +1 -0
  92. package/dist/interactive/demo-events.js +889 -0
  93. package/dist/interactive/demo-events.js.map +1 -0
  94. package/dist/interactive/draft-events.d.ts +224 -0
  95. package/dist/interactive/draft-events.d.ts.map +1 -0
  96. package/dist/interactive/draft-events.js +793 -0
  97. package/dist/interactive/draft-events.js.map +1 -0
  98. package/dist/interactive/edit-events.d.ts +194 -0
  99. package/dist/interactive/edit-events.d.ts.map +1 -0
  100. package/dist/interactive/edit-events.js +601 -0
  101. package/dist/interactive/edit-events.js.map +1 -0
  102. package/dist/interactive/ledger.d.ts +39 -0
  103. package/dist/interactive/ledger.d.ts.map +1 -0
  104. package/dist/interactive/ledger.js +93 -0
  105. package/dist/interactive/ledger.js.map +1 -0
  106. package/dist/interactive/proxy-routes.d.ts +39 -0
  107. package/dist/interactive/proxy-routes.d.ts.map +1 -0
  108. package/dist/interactive/proxy-routes.js +189 -0
  109. package/dist/interactive/proxy-routes.js.map +1 -0
  110. package/dist/interactive/repo-snapshot.d.ts +100 -0
  111. package/dist/interactive/repo-snapshot.d.ts.map +1 -0
  112. package/dist/interactive/repo-snapshot.js +289 -0
  113. package/dist/interactive/repo-snapshot.js.map +1 -0
  114. package/dist/interactive/ws-relay.d.ts +85 -0
  115. package/dist/interactive/ws-relay.d.ts.map +1 -0
  116. package/dist/interactive/ws-relay.js +191 -0
  117. package/dist/interactive/ws-relay.js.map +1 -0
  118. package/dist/projects/activity.d.ts +29 -0
  119. package/dist/projects/activity.d.ts.map +1 -0
  120. package/dist/projects/activity.js +172 -0
  121. package/dist/projects/activity.js.map +1 -0
  122. package/dist/projects/charter.d.ts +28 -0
  123. package/dist/projects/charter.d.ts.map +1 -0
  124. package/dist/projects/charter.js +53 -0
  125. package/dist/projects/charter.js.map +1 -0
  126. package/dist/projects/events.d.ts +55 -0
  127. package/dist/projects/events.d.ts.map +1 -0
  128. package/dist/projects/events.js +141 -0
  129. package/dist/projects/events.js.map +1 -0
  130. package/dist/projects/graph-paths.d.ts +92 -0
  131. package/dist/projects/graph-paths.d.ts.map +1 -0
  132. package/dist/projects/graph-paths.js +130 -0
  133. package/dist/projects/graph-paths.js.map +1 -0
  134. package/dist/projects/graph.d.ts +179 -0
  135. package/dist/projects/graph.d.ts.map +1 -0
  136. package/dist/projects/graph.js +775 -0
  137. package/dist/projects/graph.js.map +1 -0
  138. package/dist/projects/membership-index.d.ts +25 -0
  139. package/dist/projects/membership-index.d.ts.map +1 -0
  140. package/dist/projects/membership-index.js +46 -0
  141. package/dist/projects/membership-index.js.map +1 -0
  142. package/dist/projects/routes.d.ts +97 -0
  143. package/dist/projects/routes.d.ts.map +1 -0
  144. package/dist/projects/routes.js +510 -0
  145. package/dist/projects/routes.js.map +1 -0
  146. package/dist/projects/settings.d.ts +32 -0
  147. package/dist/projects/settings.d.ts.map +1 -0
  148. package/dist/projects/settings.js +64 -0
  149. package/dist/projects/settings.js.map +1 -0
  150. package/dist/qe/acceptance.d.ts +137 -0
  151. package/dist/qe/acceptance.d.ts.map +1 -0
  152. package/dist/qe/acceptance.js +249 -0
  153. package/dist/qe/acceptance.js.map +1 -0
  154. package/dist/qe/gate-events.d.ts +111 -0
  155. package/dist/qe/gate-events.d.ts.map +1 -0
  156. package/dist/qe/gate-events.js +168 -0
  157. package/dist/qe/gate-events.js.map +1 -0
  158. package/dist/qe/ledger.d.ts +100 -0
  159. package/dist/qe/ledger.d.ts.map +1 -0
  160. package/dist/qe/ledger.js +154 -0
  161. package/dist/qe/ledger.js.map +1 -0
  162. package/dist/studio/assets/index-8p8uwCxG.js +530 -0
  163. package/dist/studio/assets/index-D6S9zUtO.css +32 -0
  164. package/dist/studio/index.html +5 -3
  165. package/package.json +10 -4
  166. package/dist/studio/assets/index-DaaUU8Ep.css +0 -32
  167. package/dist/studio/assets/index-Fu5DRC00.js +0 -423
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The wicked-interactive bridge pool — discovery, health, reuse-or-start (DES-MERGE-001 §5.3/§5.6).
3
+ *
4
+ * A bridge is a local `wicked-interactive serve` process that records itself in
5
+ * `<root>/.wi-serve.json` = `{ port, host, pid, startedAt, version }` (ADR-0022). Its port is
6
+ * DYNAMIC — the first free port above its base — which is precisely why crew, a server process
7
+ * that can read that lockfile, proxies it instead of the browser dialling a port literal.
8
+ *
9
+ * LOCAL-ONLY BY DESIGN. Every mechanism here (a pid, a file in a directory, spawning a child)
10
+ * is single-host. That is the slice-1 posture, not an oversight: when the execution seam goes
11
+ * remote, this module is the seam that gets a remote implementation, and the proxy above it
12
+ * does not change.
13
+ *
14
+ * Pooling is keyed by the RESOLVED root (see `bridge-root.ts`), so two projects that share the
15
+ * default root share one bridge and one port; projects bound to different roots get their own.
16
+ *
17
+ * Health follows ADR-0025's hardened reuse check, and the ORDER matters: a recorded pid that is
18
+ * alive but slow (cold first hit, busy materializing) must be REUSED, not duplicated — so a
19
+ * live pid earns three 1.5 s attempts before the bridge is declared dead. Identity is checked
20
+ * too: `/api/health` must report THIS root, or a recycled port belonging to some other service
21
+ * would be proxied as if it were ours.
22
+ *
23
+ * On start/adopt the pool also records the daemon's own origin with the bridge (crew#298,
24
+ * `POST /api/studio-origin`, interactive ≥ 0.8.0) so the bridge's `GET /` redirects a direct
25
+ * visitor into studio instead of its API-only fallback page. Fire-and-forget, once per pooled
26
+ * bridge: recording can never fail — or slow down — a proxied request.
27
+ */
28
+ import { spawn as nodeSpawn } from 'node:child_process';
29
+ import { mkdirSync, readFileSync } from 'node:fs';
30
+ import { join, resolve } from 'node:path';
31
+ export const LOCK_NAME = '.wi-serve.json';
32
+ /** ADR-0025: 1.5 s × 3 while the pid lives. */
33
+ export const HEALTH_TIMEOUT_MS = 1500;
34
+ export const HEALTH_ATTEMPTS = 3;
35
+ /** A cold `npx wicked-interactive serve` may have to resolve and fetch the package first. */
36
+ export const START_TIMEOUT_MS = 60_000;
37
+ /** The 503 the proxy renders as `{"code":"bridge_unavailable","hint":...}` (§5.6). */
38
+ export class BridgeUnavailableError extends Error {
39
+ /** An ACTIONABLE command an operator can actually run — never a bare "try again". */
40
+ hint;
41
+ constructor(message, hint) {
42
+ super(message);
43
+ this.name = 'BridgeUnavailableError';
44
+ this.hint = hint;
45
+ }
46
+ }
47
+ /** The one command that reproduces a failed start in a terminal, where its output is visible. */
48
+ function serveCommand(root) {
49
+ return `npx wicked-interactive serve --root ${root}`;
50
+ }
51
+ /**
52
+ * The daemon's own http origin from its bound server address, or null before `listen`. Wildcard
53
+ * binds (`0.0.0.0` / `::`) are not dialable from a browser, so they normalize to loopback — the
54
+ * bridge is local-only anyway, so whoever hits its port can reach the daemon at 127.0.0.1 too.
55
+ */
56
+ export function boundOrigin(addr) {
57
+ if (addr === null || typeof addr === 'string')
58
+ return null;
59
+ const host = addr.address === '' || addr.address === '0.0.0.0' || addr.address === '::'
60
+ ? '127.0.0.1'
61
+ : addr.address.includes(':')
62
+ ? `[${addr.address}]`
63
+ : addr.address;
64
+ return `http://${host}:${addr.port}`;
65
+ }
66
+ /** `<root>/.wi-serve.json`, or null when absent/unparseable/incomplete. */
67
+ export function readLock(root) {
68
+ try {
69
+ const raw = JSON.parse(readFileSync(join(root, LOCK_NAME), 'utf8'));
70
+ if (typeof raw.port !== 'number' || typeof raw.pid !== 'number')
71
+ return null;
72
+ return { host: typeof raw.host === 'string' && raw.host !== '' ? raw.host : '127.0.0.1', port: raw.port, pid: raw.pid };
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ /** Signal 0 probes existence without delivering: EPERM means alive-but-not-ours. */
79
+ export function pidAlive(pid) {
80
+ if (!Number.isInteger(pid) || pid <= 0)
81
+ return false;
82
+ try {
83
+ process.kill(pid, 0);
84
+ return true;
85
+ }
86
+ catch (err) {
87
+ return err.code === 'EPERM';
88
+ }
89
+ }
90
+ /** `GET /api/health` → the root that bridge is serving, or null (timeout, refusal, non-200). */
91
+ async function bridgeIdentity(bridge, timeoutMs) {
92
+ try {
93
+ const res = await fetch(`http://${bridge.host}:${bridge.port}/api/health`, {
94
+ signal: AbortSignal.timeout(timeoutMs),
95
+ });
96
+ if (!res.ok)
97
+ return null;
98
+ const body = (await res.json());
99
+ return typeof body.root === 'string' ? body.root : null;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
106
+ export class InteractiveBridgePool {
107
+ io;
108
+ /** Last bridge known good per root — the fast path that keeps the proxy off `fetch` per request. */
109
+ live = new Map();
110
+ /** In-flight resolutions per root, so a burst of first requests starts ONE bridge, not N. */
111
+ inflight = new Map();
112
+ constructor(io = {}) {
113
+ this.io = io;
114
+ }
115
+ /** Reuse-or-start, idempotent per root. Throws `BridgeUnavailableError` when start is impossible. */
116
+ async ensure(root) {
117
+ // Fast path: we started/adopted it and its pid is still alive. A full health round-trip on
118
+ // every proxied request would put a 1.5 s timeout budget in front of every asset fetch.
119
+ const cached = this.live.get(root);
120
+ if (cached && pidAlive(cached.bridge.pid))
121
+ return cached.bridge;
122
+ this.live.delete(root);
123
+ const pending = this.inflight.get(root);
124
+ if (pending)
125
+ return pending;
126
+ const started = this.resolveOrStart(root).finally(() => this.inflight.delete(root));
127
+ this.inflight.set(root, started);
128
+ return started;
129
+ }
130
+ /** Drop the cached bridge for a root — called when a proxied connection is refused. */
131
+ invalidate(root) {
132
+ this.live.delete(root);
133
+ }
134
+ /** Live bridges, for tests and future operator introspection. */
135
+ keys() {
136
+ return [...this.live.keys()];
137
+ }
138
+ async resolveOrStart(root) {
139
+ // Adopt-or-start; either way the bridge just answered `/api/health` for this root, which is
140
+ // exactly the moment #298 wants the studio origin recorded — fire-and-forget, so recording
141
+ // can never delay (let alone fail) the proxied request that triggered the resolution.
142
+ const bridge = (await this.healthy(root)) ?? (await this.start(root));
143
+ const entry = { bridge, originRecorded: false };
144
+ this.live.set(root, entry);
145
+ this.recordStudioOrigin(entry);
146
+ return bridge;
147
+ }
148
+ /**
149
+ * POST the daemon's own origin to the bridge's `/api/studio-origin` (#298), so the bridge's
150
+ * `GET /` redirects a direct visitor into studio instead of the API-only fallback page.
151
+ * At most one attempt per pooled bridge per process; 404/405 means the bridge predates the
152
+ * endpoint (interactive < 0.8.0) and is an expected skip, any other failure is a warn — never
153
+ * an error, because origin recording must never fail a proxy request.
154
+ */
155
+ recordStudioOrigin(entry) {
156
+ if (entry.originRecorded)
157
+ return;
158
+ entry.originRecorded = true;
159
+ const origin = this.io.studioOrigin?.() ?? null;
160
+ if (origin === null)
161
+ return; // no origin to record (pool not bootstrapped from a listening daemon)
162
+ const { host, port } = entry.bridge;
163
+ const timeout = this.io.healthTimeoutMs ?? HEALTH_TIMEOUT_MS;
164
+ void (async () => {
165
+ try {
166
+ const res = await fetch(`http://${host}:${port}/api/studio-origin`, {
167
+ method: 'POST',
168
+ headers: { 'content-type': 'application/json' },
169
+ body: JSON.stringify({ origin }),
170
+ signal: AbortSignal.timeout(timeout),
171
+ });
172
+ await res.arrayBuffer().catch(() => undefined); // drain, so the connection is released
173
+ if (res.status === 404 || res.status === 405) {
174
+ this.io.debug?.(`bridge at ${host}:${port} has no /api/studio-origin (interactive < 0.8.0) — skipping origin record`);
175
+ }
176
+ else if (!res.ok) {
177
+ this.io.log?.(`recording studio origin ${origin} with the bridge at ${host}:${port} failed: HTTP ${res.status}`);
178
+ }
179
+ }
180
+ catch (err) {
181
+ this.io.log?.(`recording studio origin ${origin} with the bridge at ${host}:${port} failed: ${err.message}`);
182
+ }
183
+ })();
184
+ }
185
+ /** The lockfile points at a bridge that is alive, answering, and serving THIS root. */
186
+ async healthy(root) {
187
+ const lock = readLock(root);
188
+ if (lock === null || !pidAlive(lock.pid))
189
+ return null;
190
+ const timeout = this.io.healthTimeoutMs ?? HEALTH_TIMEOUT_MS;
191
+ for (let attempt = 0; attempt < HEALTH_ATTEMPTS; attempt++) {
192
+ const identity = await bridgeIdentity(lock, timeout);
193
+ if (identity !== null && resolve(identity) === root)
194
+ return lock;
195
+ // Only keep retrying while the pid still lives — a bridge that exited mid-probe is dead,
196
+ // not slow, and burning the remaining attempts on it just delays the restart.
197
+ if (!pidAlive(lock.pid))
198
+ return null;
199
+ if (attempt < HEALTH_ATTEMPTS - 1)
200
+ await sleep(300);
201
+ }
202
+ return null;
203
+ }
204
+ async start(root) {
205
+ try {
206
+ // `npx` runs with cwd=root; a missing directory fails the spawn with an opaque error.
207
+ mkdirSync(root, { recursive: true });
208
+ }
209
+ catch (err) {
210
+ throw new BridgeUnavailableError(`interactive root ${root} is not usable: ${err.message}`, `create the docs root and retry: mkdir -p ${root} && ${serveCommand(root)}`);
211
+ }
212
+ let spawnFailure = null;
213
+ const child = (this.io.spawn ?? defaultSpawn)(root);
214
+ // Detached + unref: the bridge is a SHARED instance keyed by root, so it must outlive the
215
+ // daemon that happened to start it (and be adoptable by the next one via the lockfile).
216
+ child.on('error', (err) => {
217
+ spawnFailure = err.message;
218
+ });
219
+ child.unref?.();
220
+ const deadline = Date.now() + (this.io.startTimeoutMs ?? START_TIMEOUT_MS);
221
+ while (Date.now() < deadline) {
222
+ if (spawnFailure !== null) {
223
+ throw new BridgeUnavailableError(`could not spawn the interactive bridge in ${root}: ${spawnFailure}`, `install Node 22+ so \`npx\` is on PATH, then run: ${serveCommand(root)}`);
224
+ }
225
+ const healthy = await this.healthy(root);
226
+ if (healthy)
227
+ return healthy;
228
+ await sleep(150);
229
+ }
230
+ this.io.log?.(`interactive bridge for ${root} did not come up within the start budget`);
231
+ throw new BridgeUnavailableError(`the interactive bridge for ${root} did not become healthy in time`, `run \`${serveCommand(root)}\` in a terminal to see the failure (or check ${join(root, '.wi-serve.log')})`);
232
+ }
233
+ }
234
+ /** `npx wicked-interactive serve` in `<root>`, detached, output to the bridge's own log. */
235
+ function defaultSpawn(root) {
236
+ // `--yes` is load-bearing: without it npx PROMPTS when the package is not installed, and a
237
+ // daemon has no tty to answer with — the request would hang instead of failing to a 503.
238
+ return nodeSpawn('npx', ['--yes', 'wicked-interactive', 'serve', '--root', root], {
239
+ cwd: root,
240
+ detached: true,
241
+ stdio: 'ignore',
242
+ });
243
+ }
244
+ //# sourceMappingURL=bridge-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-pool.js","sourceRoot":"","sources":["../../src/interactive/bridge-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Which wicked-interactive document root a crew project speaks to (DES-MERGE-001 §7.1/§7.2).
3
+ *
4
+ * §7.1 closed the identity question: a crew Project is THE entity, and an interactive
5
+ * "instance" (a docs directory) maps onto it through ONE nullable setting, `interactiveRoot`.
6
+ * Null means "the shared default root" — it is a default, never a constraint (§7.2): two
7
+ * projects that leave it null share one bridge, a project that sets it gets its own.
8
+ *
9
+ * The resolved string is also the BRIDGE POOL KEY, which is why every spelling of the same
10
+ * directory has to collapse to one value here rather than in the pool. `~/decks`, `decks`
11
+ * (relative), and `/Users/me/decks/` are the same instance; keying on the raw setting would
12
+ * start a second `wicked-interactive serve` on a second port for each spelling — exactly the
13
+ * "why is it on 5 ports" confusion ADR-0025 exists to prevent.
14
+ */
15
+ /** The setting carrier — a `Project` record or a crew-side settings row both satisfy this. */
16
+ export interface InteractiveRootSetting {
17
+ /** Absolute or `~`-relative docs root; null/absent ⇒ the shared default. */
18
+ interactiveRoot?: string | null | undefined;
19
+ }
20
+ /** Env override for the SHARED DEFAULT only (never for an explicit per-project setting).
21
+ * Exists so a test harness or an e2e run can point "the default root" at a scratch dir. */
22
+ export declare const ROOT_ENV = "WICKED_INTERACTIVE_ROOT";
23
+ /**
24
+ * What `wicked-interactive serve` uses with no `--root`: the canonical shared root
25
+ * `~/wicked-interactive/docs` (ADR-0025 amended, `bin/wicked-interactive.js:181`). Kept
26
+ * byte-identical to interactive's own default on purpose — that is what lets an operator's
27
+ * already-running default bridge be ADOPTED by the pool instead of duplicated.
28
+ */
29
+ export declare function defaultInteractiveRoot(home?: string): string;
30
+ /**
31
+ * The resolved, canonical docs root for a project — and therefore its bridge pool key.
32
+ * Precedence: the project's own `interactiveRoot` › `WICKED_INTERACTIVE_ROOT` › the shared
33
+ * default. A blank/whitespace setting is treated as null, not as "the cwd".
34
+ */
35
+ export declare function resolveInteractiveRoot(setting: InteractiveRootSetting | null | undefined, env?: Record<string, string | undefined>, home?: string): string;
36
+ //# sourceMappingURL=bridge-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-root.d.ts","sourceRoot":"","sources":["../../src/interactive/bridge-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,8FAA8F;AAC9F,MAAM,WAAW,sBAAsB;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC7C;AAED;4FAC4F;AAC5F,eAAO,MAAM,QAAQ,4BAA4B,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEvE;AASD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EAClD,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,IAAI,GAAE,MAAkB,GACvB,MAAM,CAMR"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Which wicked-interactive document root a crew project speaks to (DES-MERGE-001 §7.1/§7.2).
3
+ *
4
+ * §7.1 closed the identity question: a crew Project is THE entity, and an interactive
5
+ * "instance" (a docs directory) maps onto it through ONE nullable setting, `interactiveRoot`.
6
+ * Null means "the shared default root" — it is a default, never a constraint (§7.2): two
7
+ * projects that leave it null share one bridge, a project that sets it gets its own.
8
+ *
9
+ * The resolved string is also the BRIDGE POOL KEY, which is why every spelling of the same
10
+ * directory has to collapse to one value here rather than in the pool. `~/decks`, `decks`
11
+ * (relative), and `/Users/me/decks/` are the same instance; keying on the raw setting would
12
+ * start a second `wicked-interactive serve` on a second port for each spelling — exactly the
13
+ * "why is it on 5 ports" confusion ADR-0025 exists to prevent.
14
+ */
15
+ import { homedir } from 'node:os';
16
+ import { resolve } from 'node:path';
17
+ /** Env override for the SHARED DEFAULT only (never for an explicit per-project setting).
18
+ * Exists so a test harness or an e2e run can point "the default root" at a scratch dir. */
19
+ export const ROOT_ENV = 'WICKED_INTERACTIVE_ROOT';
20
+ /**
21
+ * What `wicked-interactive serve` uses with no `--root`: the canonical shared root
22
+ * `~/wicked-interactive/docs` (ADR-0025 amended, `bin/wicked-interactive.js:181`). Kept
23
+ * byte-identical to interactive's own default on purpose — that is what lets an operator's
24
+ * already-running default bridge be ADOPTED by the pool instead of duplicated.
25
+ */
26
+ export function defaultInteractiveRoot(home = homedir()) {
27
+ return resolve(home, 'wicked-interactive', 'docs');
28
+ }
29
+ /** Expand a leading `~` and absolutize, so every spelling of one directory keys the same. */
30
+ function canonicalize(value, home) {
31
+ const expanded = value === '~' ? home : value.startsWith('~/') || value.startsWith('~\\') ? resolve(home, value.slice(2)) : value;
32
+ return resolve(expanded);
33
+ }
34
+ /**
35
+ * The resolved, canonical docs root for a project — and therefore its bridge pool key.
36
+ * Precedence: the project's own `interactiveRoot` › `WICKED_INTERACTIVE_ROOT` › the shared
37
+ * default. A blank/whitespace setting is treated as null, not as "the cwd".
38
+ */
39
+ export function resolveInteractiveRoot(setting, env = process.env, home = homedir()) {
40
+ const own = setting?.interactiveRoot;
41
+ if (typeof own === 'string' && own.trim() !== '')
42
+ return canonicalize(own.trim(), home);
43
+ const shared = env[ROOT_ENV];
44
+ if (typeof shared === 'string' && shared.trim() !== '')
45
+ return canonicalize(shared.trim(), home);
46
+ return defaultInteractiveRoot(home);
47
+ }
48
+ //# sourceMappingURL=bridge-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-root.js","sourceRoot":"","sources":["../../src/interactive/bridge-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC;4FAC4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,OAAO,EAAE;IAC7D,OAAO,OAAO,CAAC,IAAI,EAAE,oBAAoB,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC;AAED,6FAA6F;AAC7F,SAAS,YAAY,CAAC,KAAa,EAAE,IAAY;IAC/C,MAAM,QAAQ,GACZ,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnH,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAkD,EAClD,MAA0C,OAAO,CAAC,GAAG,EACrD,OAAe,OAAO,EAAE;IAExB,MAAM,GAAG,GAAG,OAAO,EAAE,eAAe,CAAC;IACrC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACjG,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Opt-in governed answering of wicked-interactive's conversational ITERATION asks (CREW-UX-5 —
3
+ * the third interactive leg, beside draft-events.ts and edit-events.ts).
4
+ *
5
+ * A finished doc's thread send in wicked-studio does `postFork` (mints a version) and injects
6
+ * `wicked.interactive.chat.posted` — and NOTHING consumed that topic since the ad-hoc assist
7
+ * agent retired: crew answers `doc.created` (the first draft) and `feedback.processed` (the
8
+ * structural handoff), but the plain conversational ask ("make the intro punchier") had no
9
+ * answerer, so BRIEF-UX-001 J3's "iterate twice" was impossible. This module makes a
10
+ * crew-governed run the answerer: understand-the-ask → revise, announced back on the SAME
11
+ * `draft.completed` wire the first draft rides — the service lands the revised full HTML as a
12
+ * generated version (`materializeDraft` → `applyGeneratedHtml`), which is exactly what an
13
+ * iteration is.
14
+ *
15
+ * Shape mirrors draft-events.ts (dynamic wicked-bus import, graceful degradation, durable
16
+ * cursor `cursor_init: 'latest'` under a dedicated plugin name, durable replay-dedup ledger,
17
+ * `wi-crew` narration via status.posted). The chat-specific deltas:
18
+ *
19
+ * - THE TRIGGER IS A CONVERSATION LINE, so the actionable filter is layered (the contract):
20
+ * (a) `role: "user"` only — agent narration echoes also ride chat.posted;
21
+ * (b) the doc must EXIST under the resolved docs root and not belong to a foreign loop —
22
+ * and the disk truth here is subtle: interactive's `initManifest` only ever RECORDS a
23
+ * `kind` for demo docs (a real `kind: "source"` doc's versions.json carries NO kind
24
+ * field at all — the "source" spelling rides the doc.created EVENT only; verified
25
+ * against interactive 0.8.0 and main, src/service/server.js `initWorkspace(dir, html)`).
26
+ * So the gate accepts `source` (future manifests may record it) and the absent-default
27
+ * `doc`, and rejects explicit foreign kinds (`demo`) — a doc we cannot see is not ours
28
+ * to answer either;
29
+ * (c) per-doc serialization — an ask on a doc whose draft/edit/chat run is still in flight
30
+ * is QUEUED (FIFO per doc), never raced: two concurrent revisions of one doc would
31
+ * land as two forks of the same parent and the second would silently drop the first;
32
+ * (d) the text must be an ASK, not an echo of a machine-composed message — the feedback
33
+ * overlay injects its batch as a chat.posted TOO (same `source_message_id` as its
34
+ * `feedback.submitted`), and that batch is already the edit seam's business.
35
+ * - VERSION SNAPSHOT AT LAUNCH: the seam copies the doc's CURRENT HEAD html into the chat
36
+ * inbox and names the COPY in the task, so (1) the worker never needs read access to the
37
+ * doc workspace (the one declared write root covers input + deliverable — crew#263 /
38
+ * wicked-core#259: write roots are readable) and (2) a queued second ask snapshots the
39
+ * head AFTER the first revision landed — which is what "iterate twice" means.
40
+ * - THE LANDING GATE: `draft.completed` is announced by path and the service lands the new
41
+ * version ASYNCHRONOUSLY. A queued ask drained the instant our run completes would snapshot
42
+ * the stale head and silently drop the revision the user just watched land. So a successful
43
+ * completion arms a per-doc gate — drain only once the manifest head ADVANCES past the head
44
+ * we launched from, or a timeout passes (the service may be down; waiting forever would
45
+ * strand the queue).
46
+ * - IDEMPOTENCY KEYING: one doc legitimately produces many asks over its life, so the dedupe
47
+ * unit is the ASK — `source_message_id` when the frame carries one (the studio reuses the
48
+ * SAME message id on a user-driven resend, so a retry that reached the bus twice dedupes),
49
+ * else the bus `event_id` (pure redelivery). Never the doc lifetime.
50
+ */
51
+ import { InteractiveHandoffLedger } from './ledger.js';
52
+ import type { CoreAdapter } from '../core/adapter.js';
53
+ import type { WorkflowDef } from '../core/types.js';
54
+ export declare const CHAT_POSTED = "wicked.interactive.chat.posted";
55
+ /** Exact-type filter with a domain guard — no wildcard, one event type is the whole trigger. */
56
+ export declare const INTERACTIVE_CHAT_BUS_FILTER = "wicked.interactive.chat.posted@wicked-interactive";
57
+ /** Dedicated durable-cursor identity — NOT the draft/edit seams', so the three interactive
58
+ * seams advance independent cursors and stopping one never strands another. */
59
+ export declare const INTERACTIVE_CHAT_BUS_PLUGIN = "wicked-crew-interactive-chat";
60
+ export declare const INTERACTIVE_CHAT_WORKFLOW = "interactive-chat";
61
+ /**
62
+ * The governed workflow that fulfils an iteration ask. Two agent phases — understand (recon)
63
+ * then revise (build, creator role) — the draft leg's sibling: the reviser builds on a stated
64
+ * plan instead of one-shotting a rewrite, and the run narrates a real phase transition.
65
+ *
66
+ * The phase `instructions` carry the ITERATION contract: start from the CURRENT document (the
67
+ * snapshot path named in the task), change only what the ask touches, KEEP every existing
68
+ * `data-wid` on kept elements (interactive's instrument pass preserves pre-existing anchors —
69
+ * INV-1 — so feedback deep-links survive the iteration) and mint none on added ones. They are
70
+ * SINGLE-LINE by contract (PTY seat runner, wicked-core FINDING-011).
71
+ *
72
+ * All gates are `auto` with `validator_pin: null` — same rationale as the draft leg: the
73
+ * acceptance gate for a revision is interactive's instrument+theme pipeline and the user's own
74
+ * eyes on the canvas.
75
+ */
76
+ export declare const INTERACTIVE_CHAT_WORKFLOW_DEF: WorkflowDef;
77
+ /** The chat-posted fields this seam acts on. */
78
+ export interface ChatAsk {
79
+ documentId: string;
80
+ /** The user's ask, verbatim (flattened later — the problem statement is single-line). */
81
+ text: string;
82
+ /** The thread message behind the ask (studio inject wire, §7.7). The studio REUSES the same
83
+ * id on a user-driven resend, so this is the dedupe unit when present. */
84
+ sourceMessageId?: string;
85
+ /** Present when the doc is project-bound (interactive's DES-PROJECT-001 enrichment). */
86
+ projectId?: string;
87
+ }
88
+ /**
89
+ * Parse a bus frame into a {@link ChatAsk}, or `null` when it is not an actionable ask:
90
+ * wrong type, malformed payload, non-`user` role (contract (a) — agent narration and any
91
+ * transcript echo ride the same topic), slug-invalid document_id, or empty text. Kind and
92
+ * existence checks (contract (b)) happen against the filesystem, not the payload — the frame
93
+ * does not carry `kind`.
94
+ */
95
+ export declare function parseChatPosted(eventType: string, payload: unknown): ChatAsk | null;
96
+ /** Contract (d): `true` when the text is a conversational ask this seam should answer —
97
+ * not the feedback overlay's batch echo (already answered elsewhere). */
98
+ export declare function isIterationAsk(text: string): boolean;
99
+ /** The one dedupe unit of this seam: the ASK — the studio's message id when the frame carries
100
+ * one (a resend reuses it), else the bus event id (pure redelivery). Never the doc lifetime. */
101
+ export declare function chatKey(documentId: string, eventId: number, sourceMessageId?: string): string;
102
+ /** Deterministic bus idempotency key for the one revision this seam may land per ask. */
103
+ export declare function chatIdempotencyKey(documentId: string, eventId: number, sourceMessageId?: string): string;
104
+ /** What the versions.json read yields — enough to gate (kind) and snapshot (head html path). */
105
+ export interface DocHead {
106
+ kind: string;
107
+ head: number;
108
+ /** Absolute path of the head version's html artifact. */
109
+ headHtmlPath: string;
110
+ }
111
+ /**
112
+ * Read a doc workspace's manifest (interactive's `versions.json`, fsstore.js shape) and resolve
113
+ * its head html path. `null` when the doc does not exist under this root or the manifest is
114
+ * unreadable — not ours to answer, per contract (b). Follows interactive's own tolerant read:
115
+ * `kind` defaults to `"doc"` when absent (listDocs does the same) — and ABSENT is what a real
116
+ * source doc looks like on disk (interactive records kind only for demo docs), so the caller
117
+ * gates through {@link isAnswerableDocKind}, never a `=== 'source'` comparison.
118
+ */
119
+ export declare function readDocHead(docsRoot: string, documentId: string): DocHead | null;
120
+ /**
121
+ * Contract (b)'s kind half: `true` when a doc of this manifest kind is this seam's to answer.
122
+ * `source` is the spec's spelling (recorded by no released interactive yet, accepted for the
123
+ * day a manifest carries it); `doc` is the absent-default every REAL source (and plain html)
124
+ * doc reads as — interactive's initManifest keeps non-demo kinds implicit. Explicit foreign
125
+ * kinds (`demo`, anything future) belong to their own loops.
126
+ */
127
+ export declare function isAnswerableDocKind(kind: string): boolean;
128
+ /**
129
+ * The run's problem statement (the engine scopes it per phase and folds each phase's
130
+ * instructions on top). Carries everything ask-specific: identity, the flattened ask, the
131
+ * CURRENT-version snapshot to read, and the absolute path the revised HTML must land at.
132
+ *
133
+ * CREW-UX-8 SPLIT — the revision leg carries NO repo grounding, deliberately. The draft leg
134
+ * (draft-events.ts) grounds its v1 in a launch-scoped repo snapshot and that shape is proven
135
+ * live; the SAME shape on the revise turn wedged 2/2 on the real engine (crew#288 comment —
136
+ * the repo-grounded revise turn hits the known second-turn wedge), while UNGROUNDED revisions
137
+ * are proven to land (the CREW-UX-5 verification). So bound asks launch exactly like unfiled
138
+ * ones — head-copy in the external inbox, one write root, no repoRef (wicked-core#293), no
139
+ * live-repo path in the task (wicked-core#294). Revision grounding returns when either of
140
+ * those is fixed.
141
+ */
142
+ export declare function chatProblem(ask: ChatAsk, currentPath: string, outPath: string): string;
143
+ /** Options for {@link startInteractiveChatSubscriber}. */
144
+ export interface InteractiveChatOptions {
145
+ /** Bus SQLite db path. Omit to let wicked-bus resolve its own default
146
+ * (honors `WICKED_BUS_DATA_DIR`) — where interactive's service emits unless redirected. */
147
+ dbPath?: string;
148
+ /** Poll cadence, ms (default 2000; tests shorten it). */
149
+ pollIntervalMs?: number;
150
+ /** Heartbeat narration cadence while a run is in flight, ms (default 15000 — inside the
151
+ * UI's ~20s `status.requested` window so the canvas never reads frozen; the studio's own
152
+ * 90s silence budget makes the FIRST pickup narration the load-bearing one). */
153
+ heartbeatMs?: number;
154
+ /** Ledger file (default `~/.wicked-crew/interactive-chat-ledger.json`). */
155
+ ledgerPath?: string;
156
+ /** Root under which each ask gets its own per-run subdirectory (`<chatDir>/<safeKey>/`)
157
+ * holding the head snapshot and the revised deliverable; only that subdirectory is declared
158
+ * as the run's extra write root (per-run isolation — Copilot, crew#313: declaring the shared
159
+ * dir wholesale let one doc's worker read every other doc's head and revision).
160
+ * Default `~/.wicked-crew/interactive-chats`. */
161
+ chatDir?: string;
162
+ /** Seat roster JSON for the governed run (default: the production council roster).
163
+ * The functional-test harness passes a deterministic stub seat here. */
164
+ clisJson?: string;
165
+ /** The docs root an ask's doc is read from. Default: the shared-default resolution
166
+ * (`WICKED_INTERACTIVE_ROOT` › `~/wicked-interactive/docs`); the server wires the
167
+ * per-project `interactiveRoot` setting through here so a project on its own root
168
+ * resolves correctly. */
169
+ resolveDocsRoot?: (projectId: string | undefined) => string;
170
+ /** Contract (c)'s cross-seam half: `true` while a DRAFT or EDIT run is in flight for the
171
+ * doc. The server wires the sibling subscriptions' in-flight sets through here; the chat
172
+ * seam's own in-flight runs are tracked internally. */
173
+ isDocBusy?: (documentId: string) => boolean;
174
+ /** Queue-drain sweep cadence, ms (default 1000; tests shorten it). */
175
+ queueSweepMs?: number;
176
+ /** How long a drained ask waits for the PREVIOUS revision's version to land before
177
+ * proceeding on the stale head anyway, ms (default 60000; tests shorten it). */
178
+ landingGateMs?: number;
179
+ /** Called after a launch that FILED the run into a project (chat.posted carried
180
+ * `project_id`). Same wiring as the sibling seams: the server points this at the launch
181
+ * route's post-commit half (membership index tag + membership.attached emit). */
182
+ onRunFiled?: (runId: string, projectId: string) => void;
183
+ /** Diagnostics sink (default: console.error). */
184
+ log?: (message: string) => void;
185
+ }
186
+ /** Handle for a running subscription. */
187
+ export interface InteractiveChatSubscription {
188
+ stop(): Promise<void> | void;
189
+ /** The durable ledger (diagnostics / tests). */
190
+ ledger: InteractiveHandoffLedger;
191
+ /** Documents with a chat run currently in flight (diagnostics / cross-seam wiring). */
192
+ inFlightDocs(): string[];
193
+ /** Asks queued behind a busy doc, per doc (diagnostics / tests). */
194
+ queuedCount(documentId: string): number;
195
+ }
196
+ /**
197
+ * Arm the seam: register the `interactive-chat` workflow, open a durable
198
+ * `wicked.interactive.chat.posted` subscription, and answer each user ask on an existing
199
+ * answerable doc (contract (b)) with a governed run that ends in `wicked.interactive.draft.completed`
200
+ * (the service lands the revised full HTML as a generated version).
201
+ *
202
+ * Graceful degradation mirrors the sibling seams: a missing wicked-bus package or an
203
+ * unopenable db LOGS and returns `null` — the daemon must still boot on a machine whose bus
204
+ * is broken.
205
+ */
206
+ export declare function startInteractiveChatSubscriber(adapter: CoreAdapter, opts?: InteractiveChatOptions): Promise<InteractiveChatSubscription | null>;
207
+ //# sourceMappingURL=chat-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-events.d.ts","sourceRoot":"","sources":["../../src/interactive/chat-events.ts"],"names":[],"mappings":"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"}