wicked-crew 0.5.0 → 0.6.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.
- package/dist/api/audit.d.ts +57 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +115 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/auth.d.ts +192 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +515 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/gate-cache.d.ts +8 -0
- package/dist/api/gate-cache.d.ts.map +1 -1
- package/dist/api/gate-cache.js +10 -0
- package/dist/api/gate-cache.js.map +1 -1
- package/dist/api/open-path.d.ts +18 -0
- package/dist/api/open-path.d.ts.map +1 -0
- package/dist/api/open-path.js +79 -0
- package/dist/api/open-path.js.map +1 -0
- package/dist/api/routes.d.ts +112 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +435 -16
- package/dist/api/routes.js.map +1 -1
- package/dist/api/seat-health.d.ts +55 -0
- package/dist/api/seat-health.d.ts.map +1 -0
- package/dist/api/seat-health.js +273 -0
- package/dist/api/seat-health.js.map +1 -0
- package/dist/api/seat-signin.d.ts +27 -0
- package/dist/api/seat-signin.d.ts.map +1 -0
- package/dist/api/seat-signin.js +143 -0
- package/dist/api/seat-signin.js.map +1 -0
- package/dist/api/server.d.ts +111 -3
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +236 -11
- package/dist/api/server.js.map +1 -1
- package/dist/cli/index.js +97 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +119 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/core/adapter.d.ts +64 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +162 -4
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/types.d.ts +23 -361
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +11 -4
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +71 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -0
- package/dist/interactive/bridge-pool.js +187 -0
- package/dist/interactive/bridge-pool.js.map +1 -0
- package/dist/interactive/bridge-root.d.ts +36 -0
- package/dist/interactive/bridge-root.d.ts.map +1 -0
- package/dist/interactive/bridge-root.js +48 -0
- package/dist/interactive/bridge-root.js.map +1 -0
- package/dist/interactive/draft-events.d.ts +144 -0
- package/dist/interactive/draft-events.d.ts.map +1 -0
- package/dist/interactive/draft-events.js +490 -0
- package/dist/interactive/draft-events.js.map +1 -0
- package/dist/interactive/edit-events.d.ts +172 -0
- package/dist/interactive/edit-events.d.ts.map +1 -0
- package/dist/interactive/edit-events.js +530 -0
- package/dist/interactive/edit-events.js.map +1 -0
- package/dist/interactive/ledger.d.ts +39 -0
- package/dist/interactive/ledger.d.ts.map +1 -0
- package/dist/interactive/ledger.js +93 -0
- package/dist/interactive/ledger.js.map +1 -0
- package/dist/interactive/proxy-routes.d.ts +39 -0
- package/dist/interactive/proxy-routes.d.ts.map +1 -0
- package/dist/interactive/proxy-routes.js +189 -0
- package/dist/interactive/proxy-routes.js.map +1 -0
- package/dist/interactive/ws-relay.d.ts +85 -0
- package/dist/interactive/ws-relay.d.ts.map +1 -0
- package/dist/interactive/ws-relay.js +191 -0
- package/dist/interactive/ws-relay.js.map +1 -0
- package/dist/projects/activity.d.ts +29 -0
- package/dist/projects/activity.d.ts.map +1 -0
- package/dist/projects/activity.js +172 -0
- package/dist/projects/activity.js.map +1 -0
- package/dist/projects/charter.d.ts +28 -0
- package/dist/projects/charter.d.ts.map +1 -0
- package/dist/projects/charter.js +53 -0
- package/dist/projects/charter.js.map +1 -0
- package/dist/projects/events.d.ts +55 -0
- package/dist/projects/events.d.ts.map +1 -0
- package/dist/projects/events.js +141 -0
- package/dist/projects/events.js.map +1 -0
- package/dist/projects/membership-index.d.ts +25 -0
- package/dist/projects/membership-index.d.ts.map +1 -0
- package/dist/projects/membership-index.js +46 -0
- package/dist/projects/membership-index.js.map +1 -0
- package/dist/projects/routes.d.ts +90 -0
- package/dist/projects/routes.d.ts.map +1 -0
- package/dist/projects/routes.js +388 -0
- package/dist/projects/routes.js.map +1 -0
- package/dist/projects/settings.d.ts +32 -0
- package/dist/projects/settings.d.ts.map +1 -0
- package/dist/projects/settings.js +64 -0
- package/dist/projects/settings.js.map +1 -0
- package/dist/qe/acceptance.d.ts +137 -0
- package/dist/qe/acceptance.d.ts.map +1 -0
- package/dist/qe/acceptance.js +249 -0
- package/dist/qe/acceptance.js.map +1 -0
- package/dist/qe/gate-events.d.ts +111 -0
- package/dist/qe/gate-events.d.ts.map +1 -0
- package/dist/qe/gate-events.js +168 -0
- package/dist/qe/gate-events.js.map +1 -0
- package/dist/qe/ledger.d.ts +100 -0
- package/dist/qe/ledger.d.ts.map +1 -0
- package/dist/qe/ledger.js +154 -0
- package/dist/qe/ledger.js.map +1 -0
- package/dist/studio/assets/index-CCwXa1cn.js +428 -0
- package/dist/studio/assets/index-HWxo0h41.css +32 -0
- package/dist/studio/index.html +2 -2
- package/package.json +10 -4
- package/dist/studio/assets/index-DaaUU8Ep.css +0 -32
- package/dist/studio/assets/index-Fu5DRC00.js +0 -423
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
import { type ChildProcess } from 'node:child_process';
|
|
24
|
+
export declare const LOCK_NAME = ".wi-serve.json";
|
|
25
|
+
/** ADR-0025: 1.5 s × 3 while the pid lives. */
|
|
26
|
+
export declare const HEALTH_TIMEOUT_MS = 1500;
|
|
27
|
+
export declare const HEALTH_ATTEMPTS = 3;
|
|
28
|
+
/** A cold `npx wicked-interactive serve` may have to resolve and fetch the package first. */
|
|
29
|
+
export declare const START_TIMEOUT_MS = 60000;
|
|
30
|
+
/** A bridge that answered `/api/health` for its root. */
|
|
31
|
+
export interface LiveBridge {
|
|
32
|
+
host: string;
|
|
33
|
+
port: number;
|
|
34
|
+
pid: number;
|
|
35
|
+
}
|
|
36
|
+
/** The 503 the proxy renders as `{"code":"bridge_unavailable","hint":...}` (§5.6). */
|
|
37
|
+
export declare class BridgeUnavailableError extends Error {
|
|
38
|
+
/** An ACTIONABLE command an operator can actually run — never a bare "try again". */
|
|
39
|
+
readonly hint: string;
|
|
40
|
+
constructor(message: string, hint: string);
|
|
41
|
+
}
|
|
42
|
+
/** Injectable IO — the integration suite substitutes a fake bridge for the real `npx` spawn. */
|
|
43
|
+
export interface BridgePoolIo {
|
|
44
|
+
spawn?: (root: string) => ChildProcess;
|
|
45
|
+
startTimeoutMs?: number;
|
|
46
|
+
healthTimeoutMs?: number;
|
|
47
|
+
log?: (msg: string) => void;
|
|
48
|
+
}
|
|
49
|
+
/** `<root>/.wi-serve.json`, or null when absent/unparseable/incomplete. */
|
|
50
|
+
export declare function readLock(root: string): LiveBridge | null;
|
|
51
|
+
/** Signal 0 probes existence without delivering: EPERM means alive-but-not-ours. */
|
|
52
|
+
export declare function pidAlive(pid: number): boolean;
|
|
53
|
+
export declare class InteractiveBridgePool {
|
|
54
|
+
private readonly io;
|
|
55
|
+
/** Last bridge known good per root — the fast path that keeps the proxy off `fetch` per request. */
|
|
56
|
+
private readonly live;
|
|
57
|
+
/** In-flight resolutions per root, so a burst of first requests starts ONE bridge, not N. */
|
|
58
|
+
private readonly inflight;
|
|
59
|
+
constructor(io?: BridgePoolIo);
|
|
60
|
+
/** Reuse-or-start, idempotent per root. Throws `BridgeUnavailableError` when start is impossible. */
|
|
61
|
+
ensure(root: string): Promise<LiveBridge>;
|
|
62
|
+
/** Drop the cached bridge for a root — called when a proxied connection is refused. */
|
|
63
|
+
invalidate(root: string): void;
|
|
64
|
+
/** Live bridges, for tests and future operator introspection. */
|
|
65
|
+
keys(): string[];
|
|
66
|
+
private resolveOrStart;
|
|
67
|
+
/** The lockfile points at a bridge that is alive, answering, and serving THIS root. */
|
|
68
|
+
private healthy;
|
|
69
|
+
private start;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=bridge-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-pool.d.ts","sourceRoot":"","sources":["../../src/interactive/bridge-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI3E,eAAO,MAAM,SAAS,mBAAmB,CAAC;AAC1C,+CAA+C;AAC/C,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,6FAA6F;AAC7F,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,sFAAsF;AACtF,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAK1C;AAOD,gGAAgG;AAChG,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAQxD;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ7C;AAkBD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IACtD,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;gBAEvD,EAAE,GAAE,YAAiB;IAIjC,qGAAqG;IAC/F,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAc/C,uFAAuF;IACvF,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B,iEAAiE;IACjE,IAAI,IAAI,MAAM,EAAE;YAIF,cAAc;IAW5B,uFAAuF;YACzE,OAAO;YAeP,KAAK;CAsCpB"}
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
24
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
25
|
+
import { join, resolve } from 'node:path';
|
|
26
|
+
export const LOCK_NAME = '.wi-serve.json';
|
|
27
|
+
/** ADR-0025: 1.5 s × 3 while the pid lives. */
|
|
28
|
+
export const HEALTH_TIMEOUT_MS = 1500;
|
|
29
|
+
export const HEALTH_ATTEMPTS = 3;
|
|
30
|
+
/** A cold `npx wicked-interactive serve` may have to resolve and fetch the package first. */
|
|
31
|
+
export const START_TIMEOUT_MS = 60_000;
|
|
32
|
+
/** The 503 the proxy renders as `{"code":"bridge_unavailable","hint":...}` (§5.6). */
|
|
33
|
+
export class BridgeUnavailableError extends Error {
|
|
34
|
+
/** An ACTIONABLE command an operator can actually run — never a bare "try again". */
|
|
35
|
+
hint;
|
|
36
|
+
constructor(message, hint) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = 'BridgeUnavailableError';
|
|
39
|
+
this.hint = hint;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** The one command that reproduces a failed start in a terminal, where its output is visible. */
|
|
43
|
+
function serveCommand(root) {
|
|
44
|
+
return `npx wicked-interactive serve --root ${root}`;
|
|
45
|
+
}
|
|
46
|
+
/** `<root>/.wi-serve.json`, or null when absent/unparseable/incomplete. */
|
|
47
|
+
export function readLock(root) {
|
|
48
|
+
try {
|
|
49
|
+
const raw = JSON.parse(readFileSync(join(root, LOCK_NAME), 'utf8'));
|
|
50
|
+
if (typeof raw.port !== 'number' || typeof raw.pid !== 'number')
|
|
51
|
+
return null;
|
|
52
|
+
return { host: typeof raw.host === 'string' && raw.host !== '' ? raw.host : '127.0.0.1', port: raw.port, pid: raw.pid };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Signal 0 probes existence without delivering: EPERM means alive-but-not-ours. */
|
|
59
|
+
export function pidAlive(pid) {
|
|
60
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
61
|
+
return false;
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 0);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return err.code === 'EPERM';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** `GET /api/health` → the root that bridge is serving, or null (timeout, refusal, non-200). */
|
|
71
|
+
async function bridgeIdentity(bridge, timeoutMs) {
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetch(`http://${bridge.host}:${bridge.port}/api/health`, {
|
|
74
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok)
|
|
77
|
+
return null;
|
|
78
|
+
const body = (await res.json());
|
|
79
|
+
return typeof body.root === 'string' ? body.root : null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
86
|
+
export class InteractiveBridgePool {
|
|
87
|
+
io;
|
|
88
|
+
/** Last bridge known good per root — the fast path that keeps the proxy off `fetch` per request. */
|
|
89
|
+
live = new Map();
|
|
90
|
+
/** In-flight resolutions per root, so a burst of first requests starts ONE bridge, not N. */
|
|
91
|
+
inflight = new Map();
|
|
92
|
+
constructor(io = {}) {
|
|
93
|
+
this.io = io;
|
|
94
|
+
}
|
|
95
|
+
/** Reuse-or-start, idempotent per root. Throws `BridgeUnavailableError` when start is impossible. */
|
|
96
|
+
async ensure(root) {
|
|
97
|
+
// Fast path: we started/adopted it and its pid is still alive. A full health round-trip on
|
|
98
|
+
// every proxied request would put a 1.5 s timeout budget in front of every asset fetch.
|
|
99
|
+
const cached = this.live.get(root);
|
|
100
|
+
if (cached && pidAlive(cached.pid))
|
|
101
|
+
return cached;
|
|
102
|
+
this.live.delete(root);
|
|
103
|
+
const pending = this.inflight.get(root);
|
|
104
|
+
if (pending)
|
|
105
|
+
return pending;
|
|
106
|
+
const started = this.resolveOrStart(root).finally(() => this.inflight.delete(root));
|
|
107
|
+
this.inflight.set(root, started);
|
|
108
|
+
return started;
|
|
109
|
+
}
|
|
110
|
+
/** Drop the cached bridge for a root — called when a proxied connection is refused. */
|
|
111
|
+
invalidate(root) {
|
|
112
|
+
this.live.delete(root);
|
|
113
|
+
}
|
|
114
|
+
/** Live bridges, for tests and future operator introspection. */
|
|
115
|
+
keys() {
|
|
116
|
+
return [...this.live.keys()];
|
|
117
|
+
}
|
|
118
|
+
async resolveOrStart(root) {
|
|
119
|
+
const adopted = await this.healthy(root);
|
|
120
|
+
if (adopted) {
|
|
121
|
+
this.live.set(root, adopted);
|
|
122
|
+
return adopted;
|
|
123
|
+
}
|
|
124
|
+
const started = await this.start(root);
|
|
125
|
+
this.live.set(root, started);
|
|
126
|
+
return started;
|
|
127
|
+
}
|
|
128
|
+
/** The lockfile points at a bridge that is alive, answering, and serving THIS root. */
|
|
129
|
+
async healthy(root) {
|
|
130
|
+
const lock = readLock(root);
|
|
131
|
+
if (lock === null || !pidAlive(lock.pid))
|
|
132
|
+
return null;
|
|
133
|
+
const timeout = this.io.healthTimeoutMs ?? HEALTH_TIMEOUT_MS;
|
|
134
|
+
for (let attempt = 0; attempt < HEALTH_ATTEMPTS; attempt++) {
|
|
135
|
+
const identity = await bridgeIdentity(lock, timeout);
|
|
136
|
+
if (identity !== null && resolve(identity) === root)
|
|
137
|
+
return lock;
|
|
138
|
+
// Only keep retrying while the pid still lives — a bridge that exited mid-probe is dead,
|
|
139
|
+
// not slow, and burning the remaining attempts on it just delays the restart.
|
|
140
|
+
if (!pidAlive(lock.pid))
|
|
141
|
+
return null;
|
|
142
|
+
if (attempt < HEALTH_ATTEMPTS - 1)
|
|
143
|
+
await sleep(300);
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
async start(root) {
|
|
148
|
+
try {
|
|
149
|
+
// `npx` runs with cwd=root; a missing directory fails the spawn with an opaque error.
|
|
150
|
+
mkdirSync(root, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
throw new BridgeUnavailableError(`interactive root ${root} is not usable: ${err.message}`, `create the docs root and retry: mkdir -p ${root} && ${serveCommand(root)}`);
|
|
154
|
+
}
|
|
155
|
+
let spawnFailure = null;
|
|
156
|
+
const child = (this.io.spawn ?? defaultSpawn)(root);
|
|
157
|
+
// Detached + unref: the bridge is a SHARED instance keyed by root, so it must outlive the
|
|
158
|
+
// daemon that happened to start it (and be adoptable by the next one via the lockfile).
|
|
159
|
+
child.on('error', (err) => {
|
|
160
|
+
spawnFailure = err.message;
|
|
161
|
+
});
|
|
162
|
+
child.unref?.();
|
|
163
|
+
const deadline = Date.now() + (this.io.startTimeoutMs ?? START_TIMEOUT_MS);
|
|
164
|
+
while (Date.now() < deadline) {
|
|
165
|
+
if (spawnFailure !== null) {
|
|
166
|
+
throw new BridgeUnavailableError(`could not spawn the interactive bridge in ${root}: ${spawnFailure}`, `install Node 22+ so \`npx\` is on PATH, then run: ${serveCommand(root)}`);
|
|
167
|
+
}
|
|
168
|
+
const healthy = await this.healthy(root);
|
|
169
|
+
if (healthy)
|
|
170
|
+
return healthy;
|
|
171
|
+
await sleep(150);
|
|
172
|
+
}
|
|
173
|
+
this.io.log?.(`interactive bridge for ${root} did not come up within the start budget`);
|
|
174
|
+
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')})`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** `npx wicked-interactive serve` in `<root>`, detached, output to the bridge's own log. */
|
|
178
|
+
function defaultSpawn(root) {
|
|
179
|
+
// `--yes` is load-bearing: without it npx PROMPTS when the package is not installed, and a
|
|
180
|
+
// daemon has no tty to answer with — the request would hang instead of failing to a 503.
|
|
181
|
+
return nodeSpawn('npx', ['--yes', 'wicked-interactive', 'serve', '--root', root], {
|
|
182
|
+
cwd: root,
|
|
183
|
+
detached: true,
|
|
184
|
+
stdio: 'ignore',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
//# 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,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in governed answering of wicked-interactive's first-draft generation (task #86 spike,
|
|
3
|
+
* Phase 7c first leg).
|
|
4
|
+
*
|
|
5
|
+
* wicked-interactive's service is model-free: when a doc is created with `kind: "source"` it
|
|
6
|
+
* seeds a placeholder v0 and emits `wicked.interactive.doc.created`, expecting *something with
|
|
7
|
+
* intelligence* to answer with `wicked.interactive.draft.completed` carrying the first draft
|
|
8
|
+
* (the service then instruments `data-wid` anchors, themes it, and lands `_v1.html`). Today
|
|
9
|
+
* that answerer is an ad-hoc `assist` agent session. This module makes a crew-governed run the
|
|
10
|
+
* answerer instead — same bus vocabulary, zero interactive-service changes beyond the additive
|
|
11
|
+
* producer row (`wi-crew`) in interactive's events.js ownership table.
|
|
12
|
+
*
|
|
13
|
+
* Shape mirrors `qe/gate-events.ts` (crew's existing bus seam, Phase 6a): OPT-IN, dynamic
|
|
14
|
+
* wicked-bus import, graceful degradation when the bus is absent, durable cursor with
|
|
15
|
+
* `cursor_init: 'latest'` under a dedicated plugin name.
|
|
16
|
+
*
|
|
17
|
+
* Behavioral invariants honored (recon-verified against interactive):
|
|
18
|
+
* - Heartbeat: the canvas shows a working veil and the browser fires ~20s
|
|
19
|
+
* `status.requested` heartbeats; a silent answerer reads as a frozen UI. We narrate
|
|
20
|
+
* `wicked.interactive.status.posted` on every phase transition AND on a ≤15s timer.
|
|
21
|
+
* - Idempotency: the durable cursor redelivers under at-least-once semantics, and a replayed
|
|
22
|
+
* `doc.created` must not produce a duplicate `_v2.html`. A durable per-doc ledger
|
|
23
|
+
* (JSON file, atomic rename) gates the launch, and the final `draft.completed` emit carries
|
|
24
|
+
* a deterministic idempotency key (`crew:interactive.draft:<doc>:v1`) so even a double
|
|
25
|
+
* emit dedupes at the bus (WB-002).
|
|
26
|
+
* - INV-2 (`data-wid`): first drafts are whole documents with no pre-existing anchors — the
|
|
27
|
+
* service instruments fresh ones — so the worker contract explicitly forbids inventing
|
|
28
|
+
* `data-wid` attributes rather than requiring preservation. The feedback→edit leg (fragment
|
|
29
|
+
* preservation at scale) is the structural seam next door: edit-events.ts.
|
|
30
|
+
*/
|
|
31
|
+
import { InteractiveHandoffLedger } from './ledger.js';
|
|
32
|
+
import type { CoreAdapter } from '../core/adapter.js';
|
|
33
|
+
import type { WorkflowDef } from '../core/types.js';
|
|
34
|
+
export declare const INTERACTIVE_DOMAIN = "wicked-interactive";
|
|
35
|
+
export declare const DOC_CREATED = "wicked.interactive.doc.created";
|
|
36
|
+
export declare const DRAFT_COMPLETED = "wicked.interactive.draft.completed";
|
|
37
|
+
export declare const STATUS_POSTED = "wicked.interactive.status.posted";
|
|
38
|
+
/** Exact-type filter with a domain guard — no wildcard, one event type is the whole trigger. */
|
|
39
|
+
export declare const INTERACTIVE_BUS_FILTER = "wicked.interactive.doc.created@wicked-interactive";
|
|
40
|
+
/** Dedicated durable-cursor identity — NOT the qe subscriber's `wicked-crew`, so the two
|
|
41
|
+
* seams advance independent cursors and stopping one never strands the other. */
|
|
42
|
+
export declare const INTERACTIVE_BUS_PLUGIN = "wicked-crew-interactive-draft";
|
|
43
|
+
/** The producer identity stamped on every event crew's interactive seams emit (this module and
|
|
44
|
+
* edit-events.ts). Must appear in interactive's events.js ownership table for DRAFT_COMPLETED,
|
|
45
|
+
* EDIT_COMPLETED, and STATUS_POSTED — the additive vocabulary rows that are the only
|
|
46
|
+
* interactive changes Phase 7c is allowed. */
|
|
47
|
+
export declare const INTERACTIVE_PRODUCER = "wi-crew";
|
|
48
|
+
/** Interactive's doc-name grammar (server.js DOC_NAME) — re-checked before any launch so a
|
|
49
|
+
* malformed document_id can't name a ledger key or a draft file path. Shared with the
|
|
50
|
+
* structural-edit seam (edit-events.ts), which guards the same identity. */
|
|
51
|
+
export declare const DOC_NAME: RegExp;
|
|
52
|
+
export declare const INTERACTIVE_DRAFT_WORKFLOW = "interactive-draft";
|
|
53
|
+
/**
|
|
54
|
+
* The governed workflow that produces a first draft. Two agent phases — outline (recon) then
|
|
55
|
+
* draft (build, creator role) — so the run narrates a real phase transition and the drafting
|
|
56
|
+
* worker builds on a planned structure instead of one-shotting the whole document.
|
|
57
|
+
*
|
|
58
|
+
* The phase `instructions` adapt the draft-production contract from interactive's assist skill
|
|
59
|
+
* (Step 5): honor the brief/sources/style, ground content in what the brief supports, produce a
|
|
60
|
+
* complete self-contained HTML document, and NEVER mint `data-wid` attributes (the service
|
|
61
|
+
* instruments fresh anchors itself). They are SINGLE-LINE by contract: the engine folds
|
|
62
|
+
* instructions onto the unit description with a single-line separator, and the PTY seat runner
|
|
63
|
+
* refuses any prompt carrying an embedded newline (wicked-core FINDING-011).
|
|
64
|
+
*
|
|
65
|
+
* All gates are `auto` with `validator_pin: null` — no human gate, no deterministic floor —
|
|
66
|
+
* because the acceptance gate for a draft is the INTERACTIVE side (the service's INV-2
|
|
67
|
+
* instrument+theme pipeline and the user's own eyes on the canvas). Registered via
|
|
68
|
+
* `adapter.registerWorkflow()` at arm time (validate-before-persist, hot-registered into the
|
|
69
|
+
* engine), not added to BUILTIN_WORKFLOWS: this def is crew-only data owned by this seam, not a
|
|
70
|
+
* mirror of a wicked-core drop-in.
|
|
71
|
+
*/
|
|
72
|
+
export declare const INTERACTIVE_DRAFT_WORKFLOW_DEF: WorkflowDef;
|
|
73
|
+
/** The doc-creation fields this seam acts on. */
|
|
74
|
+
export interface SourceDocCreated {
|
|
75
|
+
documentId: string;
|
|
76
|
+
brief: string;
|
|
77
|
+
sourcePaths: string[];
|
|
78
|
+
style: string;
|
|
79
|
+
/** The crew project this doc is bound to (required — parseSourceDocCreated returns null when
|
|
80
|
+
* absent; unbound docs are handled solo by the assist skill, not by this seam). */
|
|
81
|
+
projectId: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse a bus frame into a {@link SourceDocCreated}, or `null` when it is not an actionable
|
|
85
|
+
* `doc.created` (wrong type, non-`source` kind, missing/malformed document_id). `kind: "demo"`
|
|
86
|
+
* and plain html docs are the assist loop's business, not this seam's.
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseSourceDocCreated(eventType: string, payload: unknown): SourceDocCreated | null;
|
|
89
|
+
/** Collapse whitespace/newlines to single spaces and cap length — the intent must stay a
|
|
90
|
+
* single line (the PTY seat runner refuses embedded newlines) and a pasted-novel brief must
|
|
91
|
+
* not balloon the worker prompt. Shared with the structural-edit seam. */
|
|
92
|
+
export declare function oneLine(text: string, cap: number): string;
|
|
93
|
+
/**
|
|
94
|
+
* The run's problem statement (the engine scopes it per phase and folds each phase's
|
|
95
|
+
* instructions on top). Carries everything doc-specific: identity, brief, sources, style, and
|
|
96
|
+
* the absolute path the finished HTML must land at.
|
|
97
|
+
*/
|
|
98
|
+
export declare function draftProblem(doc: SourceDocCreated, outPath: string): string;
|
|
99
|
+
/** Deterministic bus idempotency key for the one draft this seam may land per document. */
|
|
100
|
+
export declare function draftIdempotencyKey(documentId: string): string;
|
|
101
|
+
export { InteractiveHandoffLedger, type HandoffLedgerEntry } from './ledger.js';
|
|
102
|
+
/** Options for {@link startInteractiveDraftSubscriber}. */
|
|
103
|
+
export interface InteractiveDraftOptions {
|
|
104
|
+
/** Bus SQLite db path. Omit to let wicked-bus resolve its own default
|
|
105
|
+
* (honors `WICKED_BUS_DATA_DIR`) — which is where interactive's service emits unless
|
|
106
|
+
* redirected, so the default is usually right. */
|
|
107
|
+
dbPath?: string;
|
|
108
|
+
/** Poll cadence, ms (default 2000; tests shorten it). */
|
|
109
|
+
pollIntervalMs?: number;
|
|
110
|
+
/** Heartbeat narration cadence while a run is in flight, ms (default 15000 — inside the
|
|
111
|
+
* UI's ~20s `status.requested` window so the canvas never reads frozen). */
|
|
112
|
+
heartbeatMs?: number;
|
|
113
|
+
/** Ledger file (default `~/.wicked-crew/interactive-draft-ledger.json`). */
|
|
114
|
+
ledgerPath?: string;
|
|
115
|
+
/** Where governed workers write finished drafts (default `~/.wicked-crew/interactive-drafts`). */
|
|
116
|
+
draftDir?: string;
|
|
117
|
+
/** Seat roster JSON for the governed run (default: the production council roster).
|
|
118
|
+
* The functional-test harness passes a deterministic stub seat here. */
|
|
119
|
+
clisJson?: string;
|
|
120
|
+
/** Called after a launch that FILED the run into a project (doc.created carried
|
|
121
|
+
* `project_id`). The server wires this to the same post-commit half the launch route
|
|
122
|
+
* performs: tag the run in the live membership index + emit `wicked.crew.membership.attached`
|
|
123
|
+
* (the engine already attached the crew.run membership atomically with the launch). */
|
|
124
|
+
onRunFiled?: (runId: string, projectId: string) => void;
|
|
125
|
+
/** Diagnostics sink (default: console.error). */
|
|
126
|
+
log?: (message: string) => void;
|
|
127
|
+
}
|
|
128
|
+
/** Handle for a running subscription. */
|
|
129
|
+
export interface InteractiveDraftSubscription {
|
|
130
|
+
stop(): Promise<void> | void;
|
|
131
|
+
/** The durable ledger (diagnostics / tests). */
|
|
132
|
+
ledger: InteractiveHandoffLedger;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Arm the seam: register the `interactive-draft` workflow, open a durable
|
|
136
|
+
* `wicked.interactive.doc.created` subscription, and answer each `kind: "source"` creation
|
|
137
|
+
* with a governed run that ends in `wicked.interactive.draft.completed`.
|
|
138
|
+
*
|
|
139
|
+
* Graceful degradation mirrors `startQeGateSubscriber`: a missing wicked-bus package or an
|
|
140
|
+
* unopenable db LOGS and returns `null` — the daemon must still boot on a machine whose bus is
|
|
141
|
+
* broken; interactive's assist loop remains the (always-available) fallback answerer.
|
|
142
|
+
*/
|
|
143
|
+
export declare function startInteractiveDraftSubscriber(adapter: CoreAdapter, opts?: InteractiveDraftOptions): Promise<InteractiveDraftSubscription | null>;
|
|
144
|
+
//# sourceMappingURL=draft-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft-events.d.ts","sourceRoot":"","sources":["../../src/interactive/draft-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAOH,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAI/D,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,WAAW,mCAAmC,CAAC;AAC5D,eAAO,MAAM,eAAe,uCAAuC,CAAC;AACpE,eAAO,MAAM,aAAa,qCAAqC,CAAC;AAEhE,gGAAgG;AAChG,eAAO,MAAM,sBAAsB,sDAAyC,CAAC;AAE7E;kFACkF;AAClF,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AAEtE;;;+CAG+C;AAC/C,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C;;6EAE6E;AAC7E,eAAO,MAAM,QAAQ,QAA8B,CAAC;AAIpD,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,8BAA8B,EAAE,WAqC5C,CAAC;AAIF,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd;wFACoF;IACpF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAiBlG;AAED;;2EAE2E;AAC3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAW3E;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAQD,OAAO,EAAE,wBAAwB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIhF,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC;;uDAEmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iFAC6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;6EACyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;4FAGwF;IACxF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,iDAAiD;IACjD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,yCAAyC;AACzC,MAAM,WAAW,4BAA4B;IAC3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,gDAAgD;IAChD,MAAM,EAAE,wBAAwB,CAAC;CAClC;AAoBD;;;;;;;;GAQG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,uBAA4B,GACjC,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAmW9C"}
|