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.
- package/dist/api/audit.d.ts +70 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +131 -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/guidance-index.d.ts +39 -0
- package/dist/api/guidance-index.d.ts.map +1 -0
- package/dist/api/guidance-index.js +67 -0
- package/dist/api/guidance-index.js.map +1 -0
- package/dist/api/open-path.d.ts +34 -0
- package/dist/api/open-path.d.ts.map +1 -0
- package/dist/api/open-path.js +101 -0
- package/dist/api/open-path.js.map +1 -0
- package/dist/api/retry-index.d.ts +30 -0
- package/dist/api/retry-index.d.ts.map +1 -0
- package/dist/api/retry-index.js +45 -0
- package/dist/api/retry-index.js.map +1 -0
- package/dist/api/routes.d.ts +164 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +769 -21
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts +63 -0
- package/dist/api/run-files.d.ts.map +1 -0
- package/dist/api/run-files.js +271 -0
- package/dist/api/run-files.js.map +1 -0
- 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 +190 -3
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +365 -11
- package/dist/api/server.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +62 -0
- package/dist/api/stall-watchdog.d.ts.map +1 -0
- package/dist/api/stall-watchdog.js +138 -0
- package/dist/api/stall-watchdog.js.map +1 -0
- package/dist/cli/index.js +171 -12
- 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 +87 -10
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +347 -28
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/bridge-reaper.d.ts +134 -0
- package/dist/core/bridge-reaper.d.ts.map +1 -0
- package/dist/core/bridge-reaper.js +286 -0
- package/dist/core/bridge-reaper.js.map +1 -0
- package/dist/core/deliver.d.ts +118 -0
- package/dist/core/deliver.d.ts.map +1 -0
- package/dist/core/deliver.js +241 -0
- package/dist/core/deliver.js.map +1 -0
- package/dist/core/deliverable-floor.d.ts +103 -0
- package/dist/core/deliverable-floor.d.ts.map +1 -0
- package/dist/core/deliverable-floor.js +173 -0
- package/dist/core/deliverable-floor.js.map +1 -0
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js.map +1 -1
- package/dist/core/types.d.ts +92 -352
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +14 -4
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +99 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -0
- package/dist/interactive/bridge-pool.js +244 -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/chat-events.d.ts +207 -0
- package/dist/interactive/chat-events.d.ts.map +1 -0
- package/dist/interactive/chat-events.js +769 -0
- package/dist/interactive/chat-events.js.map +1 -0
- package/dist/interactive/demo-events.d.ts +283 -0
- package/dist/interactive/demo-events.d.ts.map +1 -0
- package/dist/interactive/demo-events.js +889 -0
- package/dist/interactive/demo-events.js.map +1 -0
- package/dist/interactive/draft-events.d.ts +224 -0
- package/dist/interactive/draft-events.d.ts.map +1 -0
- package/dist/interactive/draft-events.js +793 -0
- package/dist/interactive/draft-events.js.map +1 -0
- package/dist/interactive/edit-events.d.ts +194 -0
- package/dist/interactive/edit-events.d.ts.map +1 -0
- package/dist/interactive/edit-events.js +601 -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/repo-snapshot.d.ts +100 -0
- package/dist/interactive/repo-snapshot.d.ts.map +1 -0
- package/dist/interactive/repo-snapshot.js +289 -0
- package/dist/interactive/repo-snapshot.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/graph-paths.d.ts +92 -0
- package/dist/projects/graph-paths.d.ts.map +1 -0
- package/dist/projects/graph-paths.js +130 -0
- package/dist/projects/graph-paths.js.map +1 -0
- package/dist/projects/graph.d.ts +179 -0
- package/dist/projects/graph.d.ts.map +1 -0
- package/dist/projects/graph.js +775 -0
- package/dist/projects/graph.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 +97 -0
- package/dist/projects/routes.d.ts.map +1 -0
- package/dist/projects/routes.js +510 -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-8p8uwCxG.js +530 -0
- package/dist/studio/assets/index-D6S9zUtO.css +32 -0
- package/dist/studio/index.html +5 -3
- package/package.json +10 -4
- package/dist/studio/assets/index-DaaUU8Ep.css +0 -32
- package/dist/studio/assets/index-Fu5DRC00.js +0 -423
package/dist/core/adapter.js
CHANGED
|
@@ -7,6 +7,8 @@ import { homedir } from 'node:os';
|
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
8
|
import { DEFAULT_SETTINGS } from './types.js';
|
|
9
9
|
import { execCapped } from './exec.js';
|
|
10
|
+
import { composeDeliverWorkflow, DELIVER_PHASE_ID } from './deliver.js';
|
|
11
|
+
import { composeDeliverableFloor } from './deliverable-floor.js';
|
|
10
12
|
/** Resolved path under the user's home directory. */
|
|
11
13
|
function wickedDir(...parts) {
|
|
12
14
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
|
|
@@ -43,7 +45,9 @@ function workflowOverlayDir() {
|
|
|
43
45
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
|
|
44
46
|
return join(home, '.config', 'wicked-core', 'workflows');
|
|
45
47
|
}
|
|
46
|
-
|
|
48
|
+
/** Where the daemon persists system settings. Exported so tests write fixtures to the ONE
|
|
49
|
+
* spelling of this path (the tests/ tree is audited against spelling core paths itself). */
|
|
50
|
+
export function settingsFilePath() {
|
|
47
51
|
return join(homedir(), '.config', 'wicked-core', 'settings.json');
|
|
48
52
|
}
|
|
49
53
|
/** Read user-registered workflow overlays from `dir` (the same dir `registerWorkflow` writes to).
|
|
@@ -109,6 +113,58 @@ function locateWickedCoreExe() {
|
|
|
109
113
|
// `subscribe` seam has a blast radius of exactly one file.
|
|
110
114
|
const require = createRequire(import.meta.url);
|
|
111
115
|
const { Core } = require('wicked-core-ts');
|
|
116
|
+
/**
|
|
117
|
+
* Does the installed wicked-core-ts addon understand `LaunchOptions.extraWriteRoots` (≥ 0.6.1)?
|
|
118
|
+
*
|
|
119
|
+
* A napi object silently IGNORES fields the addon doesn't declare, so passing the option to an
|
|
120
|
+
* older addon would launch a run whose boundary never widened — the worker then gets a boundary
|
|
121
|
+
* deny on the very deliverable path the caller declared (the crew#263 failure, resurrected
|
|
122
|
+
* silently). Fail CLOSED on version instead: same doctrine as the `projectId` guard below.
|
|
123
|
+
*/
|
|
124
|
+
function addonSupportsExtraWriteRoots() {
|
|
125
|
+
return addonAtLeast(0, 6, 1);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Does the installed addon understand `LaunchOptions.projectGraph` (≥ 0.7.1)?
|
|
129
|
+
*
|
|
130
|
+
* Same doctrine, different cost of being wrong. A silently-dropped binding does not deny anything —
|
|
131
|
+
* it launches a run whose workers quietly see one repo while the launcher recorded that they see
|
|
132
|
+
* the project. The daemon would then log "bound to the project graph as 'wicked-core'" for a run
|
|
133
|
+
* that never was, which is worse than not binding at all: the operator's evidence about what the
|
|
134
|
+
* run could see becomes false. Fail closed and keep the log honest.
|
|
135
|
+
*/
|
|
136
|
+
function addonSupportsProjectGraph() {
|
|
137
|
+
return addonAtLeast(0, 7, 1);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Is the installed `wicked-core-ts` at least `maj.min.pat`?
|
|
141
|
+
*
|
|
142
|
+
* ONE version reader for every capability gate, because the parsing rule here is load-bearing and
|
|
143
|
+
* was already got wrong once: `split('.').map(Number)` returns NaN on a pre-release or build suffix
|
|
144
|
+
* (`0.6.1-beta.1`), which fails a perfectly capable addon closed (Copilot on #263). Matching only
|
|
145
|
+
* the numeric MAJOR.MINOR.PATCH prefix fixes that, and a second copy of the fix is a second chance
|
|
146
|
+
* to lose it. No match ⇒ unparseable ⇒ fail closed.
|
|
147
|
+
*/
|
|
148
|
+
function addonAtLeast(maj, min, pat) {
|
|
149
|
+
try {
|
|
150
|
+
const pkg = require('wicked-core-ts/package.json');
|
|
151
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(pkg.version ?? '');
|
|
152
|
+
if (!m)
|
|
153
|
+
return false;
|
|
154
|
+
const got = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
155
|
+
const want = [maj, min, pat];
|
|
156
|
+
for (let i = 0; i < 3; i += 1) {
|
|
157
|
+
if (got[i] > want[i])
|
|
158
|
+
return true;
|
|
159
|
+
if (got[i] < want[i])
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
112
168
|
// ── Built-in workflow definitions (crew#44) ──────────────────────────────────
|
|
113
169
|
// Static mirrors of wicked-core workflow defs: feature, bug, migration, survey-repo,
|
|
114
170
|
// domain-graph-slice, memories, collab, onboarding, chat, and domain-extraction.
|
|
@@ -149,7 +205,7 @@ const CORE_SEEDED_WORKFLOWS = new Set(['feature', 'bug', 'migration', 'onboardin
|
|
|
149
205
|
* moves. This is display only: as of FINDING-049 these defs are never written to core's overlay dir
|
|
150
206
|
* (see CORE_SEEDED_WORKFLOWS), so a stale value here cannot reach the engine.
|
|
151
207
|
*/
|
|
152
|
-
const EVIDENCE_FLOOR_PIN = '
|
|
208
|
+
const EVIDENCE_FLOOR_PIN = 'e2e7af1db9e48454';
|
|
153
209
|
export const BUILTIN_WORKFLOWS = [
|
|
154
210
|
{
|
|
155
211
|
id: 'chat',
|
|
@@ -184,7 +240,7 @@ export const BUILTIN_WORKFLOWS = [
|
|
|
184
240
|
{ id: 'design', kind: 'recon', gate_type: 'strategy', gate: 'auto', executes_code: false, verified_evidence: false, required_deliverables: [], depends_on: ['clarify'], role: 'neutral', skill_ref: null, allowed_skills: [], validator_pin: null },
|
|
185
241
|
{ id: 'build', kind: 'build', gate_type: 'execution', gate: 'auto', executes_code: true, verified_evidence: false, required_deliverables: [], depends_on: ['design'], role: 'creator', skill_ref: null, allowed_skills: [], validator_pin: null },
|
|
186
242
|
{ id: 'adversarial-review', kind: 'review', gate_type: 'execution', gate: { human_confirm: { unconditional: false } }, executes_code: false, verified_evidence: false, required_deliverables: [], depends_on: ['build'], role: 'evaluator', skill_ref: null, allowed_skills: [], validator_pin: EVIDENCE_FLOOR_PIN },
|
|
187
|
-
{ id: 'test', kind: 'test', gate_type: 'execution', gate: { human_confirm_if: 'verdict_not_pass' }, executes_code: false, verified_evidence: true, required_deliverables: [], depends_on: ['build'], role: 'neutral', skill_ref: null, allowed_skills: [], validator_pin:
|
|
243
|
+
{ id: 'test', kind: 'test', gate_type: 'execution', gate: { human_confirm_if: 'verdict_not_pass' }, executes_code: false, verified_evidence: true, required_deliverables: [], depends_on: ['build'], role: 'neutral', skill_ref: null, allowed_skills: [], validator_pin: EVIDENCE_FLOOR_PIN },
|
|
188
244
|
{ id: 'review', kind: 'review', gate_type: 'execution', gate: 'auto', executes_code: false, verified_evidence: false, required_deliverables: [], depends_on: ['test'], role: 'neutral', skill_ref: null, allowed_skills: [], validator_pin: null },
|
|
189
245
|
],
|
|
190
246
|
},
|
|
@@ -278,7 +334,7 @@ export const BUILTIN_WORKFLOWS = [
|
|
|
278
334
|
{ id: 'survey', kind: 'recon', gate_type: null, gate: 'auto', executes_code: false, verified_evidence: false, required_deliverables: [], depends_on: [], role: 'neutral', skill_ref: 'wicked-garden-domain', allowed_skills: [], validator_pin: null },
|
|
279
335
|
{ id: 'analyze', kind: 'recon', gate_type: null, gate: 'auto', executes_code: false, verified_evidence: false, required_deliverables: [], depends_on: ['survey'], role: 'neutral', skill_ref: 'wicked-garden-domain', allowed_skills: [], validator_pin: null },
|
|
280
336
|
{ id: 'extract', kind: 'recon', gate_type: 'value', gate: 'auto', executes_code: false, verified_evidence: false, required_deliverables: [], depends_on: ['analyze'], role: 'creator', skill_ref: 'wicked-garden-domain-extractor', allowed_skills: [], validator_pin: null },
|
|
281
|
-
{ id: 'coverage', kind: 'test', gate_type: 'execution', gate: { human_confirm_if: 'verdict_not_pass' }, executes_code: false, verified_evidence: true, required_deliverables: ['coverage-report.json'], depends_on: ['extract'], role: 'evaluator', skill_ref: 'wicked-garden-domain-coverage', allowed_skills: [], validator_pin: '
|
|
337
|
+
{ id: 'coverage', kind: 'test', gate_type: 'execution', gate: { human_confirm_if: 'verdict_not_pass' }, executes_code: false, verified_evidence: true, required_deliverables: ['coverage-report.json'], depends_on: ['extract'], role: 'evaluator', skill_ref: 'wicked-garden-domain-coverage', allowed_skills: [], validator_pin: 'bfe4020a365c598b' },
|
|
282
338
|
// domain-graph is a DETERMINISTIC Tool that runs `wicked-core domain-graph`, which PERSISTS the
|
|
283
339
|
// domain/requirement/rule graph into the repo store (core#237) — not an LLM skill that could hit
|
|
284
340
|
// a non-persisting hermetic fallback. Mirrors wicked-core/workflows/domain-extraction.json.
|
|
@@ -304,6 +360,17 @@ export class ChatUnsupportedError extends Error {
|
|
|
304
360
|
this.name = 'ChatUnsupportedError';
|
|
305
361
|
}
|
|
306
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* The project surface (DES-PROJECT-001) is not available in this deployment — the installed
|
|
365
|
+
* wicked-core-ts predates 0.6.0. Typed for the same reason as `ChatUnsupportedError`: the routes
|
|
366
|
+
* answer 501 ("upgrade the engine") on this, never 400 ("fix your request").
|
|
367
|
+
*/
|
|
368
|
+
export class ProjectsUnsupportedError extends Error {
|
|
369
|
+
constructor(what) {
|
|
370
|
+
super(`${what} is not supported by this wicked-core build (needs wicked-core-ts >= 0.6.0)`);
|
|
371
|
+
this.name = 'ProjectsUnsupportedError';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
307
374
|
/**
|
|
308
375
|
* `resolveElicitation` is not available in this deployment — the NAPI binding has not
|
|
309
376
|
* landed in the installed `wicked-core-ts` yet (DES-002 §4 P-1 stub).
|
|
@@ -376,13 +443,6 @@ function quarantineStaleOnboardingOverlay() {
|
|
|
376
443
|
`whatever repo the run names (FINDING-075).`);
|
|
377
444
|
}
|
|
378
445
|
}
|
|
379
|
-
/**
|
|
380
|
-
* The single isolation boundary over wicked-core-ts. It holds the ONE `Core`
|
|
381
|
-
* handle, makes the ONE `subscribe()` call for the whole process, parses each
|
|
382
|
-
* CoreEvent, and re-emits it to registered in-daemon listeners. Every REST
|
|
383
|
-
* endpoint and the WS fan-out funnel through this stable API — so when the
|
|
384
|
-
* in-flight core-ts subscribe/teardown signature lands, only this file changes.
|
|
385
|
-
*/
|
|
386
446
|
/** The ids of a workflow's phases that carry a HUMAN gate (`human_confirm` unconditional, or the
|
|
387
447
|
* conditional `human_confirm_if`) — i.e. the phases that will PAUSE for a person.
|
|
388
448
|
*
|
|
@@ -398,6 +458,13 @@ export function humanGatePhaseIds(wf) {
|
|
|
398
458
|
('human_confirm' in p.gate || 'human_confirm_if' in p.gate))
|
|
399
459
|
.map((p) => p.id);
|
|
400
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* The single isolation boundary over wicked-core-ts. It holds the ONE `Core`
|
|
463
|
+
* handle, makes the ONE `subscribe()` call for the whole process, parses each
|
|
464
|
+
* CoreEvent, and re-emits it to registered in-daemon listeners. Every REST
|
|
465
|
+
* endpoint and the WS fan-out funnel through this stable API — so when the
|
|
466
|
+
* in-flight core-ts subscribe/teardown signature lands, only this file changes.
|
|
467
|
+
*/
|
|
401
468
|
export class CoreAdapter {
|
|
402
469
|
core;
|
|
403
470
|
subscription;
|
|
@@ -490,25 +557,138 @@ export class CoreAdapter {
|
|
|
490
557
|
opts.humanConfirm = input.humanConfirm;
|
|
491
558
|
if (input.repoRef !== undefined)
|
|
492
559
|
opts.repoRef = input.repoRef;
|
|
493
|
-
if (input.
|
|
494
|
-
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
560
|
+
if (input.projectId !== undefined) {
|
|
561
|
+
// Fail CLOSED on an old addon: silently dropping projectId would launch an unfiled run the
|
|
562
|
+
// caller believed was filed — the exact failure §2.2 exists to prevent.
|
|
563
|
+
if (typeof this.core.projectCreate !== 'function') {
|
|
564
|
+
throw new ProjectsUnsupportedError('Filing a run into a project');
|
|
565
|
+
}
|
|
566
|
+
opts.projectId = input.projectId;
|
|
567
|
+
}
|
|
568
|
+
if (input.extraWriteRoots !== undefined && input.extraWriteRoots.length > 0) {
|
|
569
|
+
// Fail CLOSED on an old addon (napi ignores undeclared fields): a silently-dropped
|
|
570
|
+
// widening resurrects the crew#263 boundary deny on the declared deliverable path.
|
|
571
|
+
if (!addonSupportsExtraWriteRoots()) {
|
|
572
|
+
throw new Error('extraWriteRoots needs wicked-core-ts >= 0.6.1; the installed addon would silently ' +
|
|
573
|
+
'ignore it and the run would be denied writing its own deliverable');
|
|
574
|
+
}
|
|
575
|
+
opts.extraWriteRoots = input.extraWriteRoots;
|
|
576
|
+
}
|
|
577
|
+
if (input.projectGraph !== undefined) {
|
|
578
|
+
// Fail CLOSED on an old addon (napi ignores undeclared fields): the run would silently get
|
|
579
|
+
// its repo's graph while everything on this side recorded that it got the project's.
|
|
580
|
+
if (!addonSupportsProjectGraph()) {
|
|
581
|
+
throw new Error('projectGraph needs wicked-core-ts >= 0.7.1; the installed addon would silently ignore ' +
|
|
582
|
+
'it and the run would see one repo while the daemon logged that it saw the project');
|
|
583
|
+
}
|
|
584
|
+
// The two invariants the TYPE cannot express, enforced where the launch is actually
|
|
585
|
+
// assembled (Copilot on #327). Both are cross-field, so `projectGraph`'s own shape can never
|
|
586
|
+
// carry them, and `core/types.ts` says as much rather than pretending the compiler helps.
|
|
499
587
|
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
588
|
+
// Loud, not lenient. Every one of these is a CALLER bug, and the failure it would otherwise
|
|
589
|
+
// produce is the silent kind this whole slice exists to end: a run that reports one thing
|
|
590
|
+
// about what it could see and observes another. `resolveProjectGraphBinding` — the only
|
|
591
|
+
// producer today — satisfies both on every arm, so a throw here means a new caller got it
|
|
592
|
+
// wrong, which is exactly when you want to hear about it.
|
|
593
|
+
if (input.projectId === undefined) {
|
|
594
|
+
throw new Error('projectGraph is the PROJECT\'s graph, so the run must be filed into that project: ' +
|
|
595
|
+
'pass projectId alongside it, or omit projectGraph and let the run use its repo graph');
|
|
596
|
+
}
|
|
597
|
+
if (input.repoRef !== undefined && input.projectGraph.repoLabel === undefined) {
|
|
598
|
+
throw new Error(`a repo-bound run needs projectGraph.repoLabel: without it the engine cannot confirm the ` +
|
|
599
|
+
`project graph holds '${input.repoRef}', and a graph that does not would answer ` +
|
|
600
|
+
`"not found" about the run's own worktree`);
|
|
601
|
+
}
|
|
602
|
+
opts.projectGraph = input.projectGraph;
|
|
603
|
+
}
|
|
604
|
+
if (input.workflow !== undefined) {
|
|
605
|
+
let workflowId = input.workflow;
|
|
606
|
+
// Both per-run compositions (deliver + deliverable floor) fold into ONE def and ONE
|
|
607
|
+
// registration: composing twice would arm two ids and launch the second, leaving the
|
|
608
|
+
// first as engine litter. `composed` stays null until something actually composes, which
|
|
609
|
+
// is what keeps the undelivered/unfloored launch byte-for-byte the old path.
|
|
610
|
+
const requireDeliverables = input.requireDeliverables ?? [];
|
|
611
|
+
let composed = null;
|
|
612
|
+
if (input.deliver === 'pr' || requireDeliverables.length > 0) {
|
|
613
|
+
const base = this.getWorkflow(input.workflow);
|
|
614
|
+
if (base === null) {
|
|
615
|
+
throw new Error(input.deliver === 'pr'
|
|
616
|
+
? `deliver: "pr" needs a registered workflow to append the deliver phase to — ` +
|
|
617
|
+
`'${input.workflow}' is not registered`
|
|
618
|
+
: `requireDeliverables needs a registered workflow to append the deliverable ` +
|
|
619
|
+
`floor to — '${input.workflow}' is not registered`);
|
|
620
|
+
}
|
|
621
|
+
composed = base;
|
|
622
|
+
}
|
|
623
|
+
if (composed !== null && requireDeliverables.length > 0) {
|
|
624
|
+
// THE DELIVERABLE FLOOR (crew#311): "done" is re-derived from the artifact. The
|
|
625
|
+
// launcher declared what this run must produce, so a deterministic Tool phase appended
|
|
626
|
+
// here re-verifies that those files EXIST and carry bytes, and fails the run naming
|
|
627
|
+
// what was expected and what was found when they do not. Composed BEFORE the deliver
|
|
628
|
+
// phase below so the floor sits ahead of it: a run that produced nothing must fail
|
|
629
|
+
// rather than open a pull request over an empty branch.
|
|
630
|
+
composed = composeDeliverableFloor(composed, input.sessionId, requireDeliverables);
|
|
511
631
|
}
|
|
632
|
+
if (composed !== null && input.deliver === 'pr') {
|
|
633
|
+
// First-class delivery (crew#293): compose a PER-RUN def — the selected workflow's
|
|
634
|
+
// phases plus the hardened deliver Tool phase appended last — and arm it with the
|
|
635
|
+
// engine under a run-scoped id. The shared def is NEVER mutated, and the composed def
|
|
636
|
+
// is hot-registered only (no overlay file, no userWorkflows entry): it exists for this
|
|
637
|
+
// launch, not for the catalog.
|
|
638
|
+
if (composed.phases.some((p) => p.id === DELIVER_PHASE_ID)) {
|
|
639
|
+
// The def already delivers (an operator's own feature-pr-style overlay). Appending a
|
|
640
|
+
// second `deliver` phase would collide on id — launch the def as-is; the intent
|
|
641
|
+
// ("this run opens its PR") is already satisfied.
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
// The run's intent rides along so the commit the deliver phase makes NAMES what it
|
|
645
|
+
// delivered (`wicked-crew run <id>: <intent>`, #318) instead of being an anonymous
|
|
646
|
+
// blob. Composition stays DEFERRED (#319): deliver and the deliverable floor fold
|
|
647
|
+
// into one def and one registration below, never two armed ids.
|
|
648
|
+
composed = composeDeliverWorkflow(composed, input.sessionId, input.problem);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (composed !== null && composed.id !== input.workflow) {
|
|
652
|
+
await this._armPerRunWorkflow(composed);
|
|
653
|
+
workflowId = composed.id;
|
|
654
|
+
}
|
|
655
|
+
opts.workflow = workflowId;
|
|
656
|
+
if (workflowId === input.workflow) {
|
|
657
|
+
// Ensure DROP-IN workflow definitions are present in the Rust overlay dir on first use.
|
|
658
|
+
// Uses a dedicated helper (not registerWorkflow) to avoid adding built-ins to userWorkflows,
|
|
659
|
+
// which would duplicate them in listWorkflows(). The write is skipped after the first call
|
|
660
|
+
// per process lifetime.
|
|
661
|
+
//
|
|
662
|
+
// Ids core seeds itself are excluded: writing them shadows the real def with this stale
|
|
663
|
+
// mirror — see CORE_SEEDED_WORKFLOWS. Core resolves those from its own registry, so there is
|
|
664
|
+
// nothing to write and never was.
|
|
665
|
+
//
|
|
666
|
+
// Skipped entirely on the per-run deliver path above (workflowId !== input.workflow):
|
|
667
|
+
// the composed def carries the base's full phase list, so the run resolves its own
|
|
668
|
+
// registration and the base drop-in is not consulted.
|
|
669
|
+
const builtinDef = CORE_SEEDED_WORKFLOWS.has(input.workflow)
|
|
670
|
+
? undefined
|
|
671
|
+
: BUILTIN_WORKFLOWS.find((w) => w.id === input.workflow);
|
|
672
|
+
if (builtinDef && !this._builtinOverlayWritten.has(input.workflow)) {
|
|
673
|
+
// Mark before await so concurrent launchRun() calls for the same builtin
|
|
674
|
+
// don't both pass the has() check and race to write the same file.
|
|
675
|
+
this._builtinOverlayWritten.add(input.workflow);
|
|
676
|
+
await this._writeBuiltinOverlay(builtinDef);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
else if (input.deliver === 'pr') {
|
|
681
|
+
// Fail loud, not silent: dropping the option would run to completion with the caller
|
|
682
|
+
// believing a PR opens at the end — the exact operator gap crew#293 closes.
|
|
683
|
+
throw new Error('deliver: "pr" requires a workflow — a free-text run has no def to append the deliver phase to');
|
|
684
|
+
}
|
|
685
|
+
else if ((input.requireDeliverables ?? []).length > 0) {
|
|
686
|
+
// Same rule for the floor (Copilot, #319): a caller that declares what a run MUST produce
|
|
687
|
+
// and gets no enforcement is worse off than one that never declared it — it would watch a
|
|
688
|
+
// free-text run complete and believe the artifacts were re-derived. There is no def to
|
|
689
|
+
// append the floor phase to, so refuse the launch instead of silently ignoring the option.
|
|
690
|
+
throw new Error('requireDeliverables requires a workflow — a free-text run has no def to append the ' +
|
|
691
|
+
'deliverable floor to');
|
|
512
692
|
}
|
|
513
693
|
return this.core.launchRun(opts);
|
|
514
694
|
}
|
|
@@ -524,6 +704,20 @@ export class CoreAdapter {
|
|
|
524
704
|
cancelRun(runId) {
|
|
525
705
|
return this.core.cancelRun(runId);
|
|
526
706
|
}
|
|
707
|
+
/**
|
|
708
|
+
* Archive (or unarchive) a TERMINAL run (crew#265) — write-off, not delete. Resolves `true`
|
|
709
|
+
* when the session existed, `false` for an unknown id (→ 404); REJECTS when the run is
|
|
710
|
+
* non-terminal (the route answers 409). Fail-closed on an old addon: method-presence guard,
|
|
711
|
+
* same doctrine as `injectWorkerMessage`.
|
|
712
|
+
*/
|
|
713
|
+
async archiveRun(runId, archived, note) {
|
|
714
|
+
const archive = this.core.archiveRun;
|
|
715
|
+
if (typeof archive !== 'function') {
|
|
716
|
+
throw new Error('Run archival needs wicked-core-ts >= 0.6.2; this build does not support it');
|
|
717
|
+
}
|
|
718
|
+
// JSON-encoded bool by contract (parse, never truthiness-test — 'false' is truthy).
|
|
719
|
+
return JSON.parse(await archive.call(this.core, runId, archived, note ?? null));
|
|
720
|
+
}
|
|
527
721
|
/** Inject an operator message into a run's active PTY worker(s). target="all" or a CLI key. */
|
|
528
722
|
injectWorkerMessage(runId, message, target) {
|
|
529
723
|
if (typeof this.core.injectWorkerMessage !== 'function') {
|
|
@@ -588,6 +782,87 @@ export class CoreAdapter {
|
|
|
588
782
|
async workOutput(unitId) {
|
|
589
783
|
return JSON.parse(await this.core.workOutput(unitId));
|
|
590
784
|
}
|
|
785
|
+
// ── Projects (DES-PROJECT-001) ──────────────────────────────────────────────
|
|
786
|
+
// 1:1 maps of the 0.6.0 engine surface. Writes ride the single-writer actor;
|
|
787
|
+
// reads are read-only store opens inside the addon. Every method throws
|
|
788
|
+
// ProjectsUnsupportedError on a pre-0.6.0 addon (routes answer 501).
|
|
789
|
+
/** True when the installed addon carries the project surface (0.6.0+). */
|
|
790
|
+
projectsSupported() {
|
|
791
|
+
return typeof this.core.projectCreate === 'function';
|
|
792
|
+
}
|
|
793
|
+
requireProjects(fn, what) {
|
|
794
|
+
if (typeof fn !== 'function')
|
|
795
|
+
throw new ProjectsUnsupportedError(what);
|
|
796
|
+
return fn;
|
|
797
|
+
}
|
|
798
|
+
async projectCreate(name, description) {
|
|
799
|
+
const fn = this.requireProjects(this.core.projectCreate, 'Creating a project');
|
|
800
|
+
return JSON.parse(await fn.call(this.core, name, description ?? null));
|
|
801
|
+
}
|
|
802
|
+
async projectUpdate(id, patch) {
|
|
803
|
+
const fn = this.requireProjects(this.core.projectUpdate, 'Updating a project');
|
|
804
|
+
return JSON.parse(await fn.call(this.core, id, patch.name ?? null, patch.description ?? null, patch.status ?? null));
|
|
805
|
+
}
|
|
806
|
+
/** Every stored project (all statuses, newest first). The `default` project is NOT here — the
|
|
807
|
+
* route layer synthesizes it (ADR §7: computed, never stored). */
|
|
808
|
+
async projectList() {
|
|
809
|
+
const fn = this.requireProjects(this.core.projectList, 'Listing projects');
|
|
810
|
+
return JSON.parse(await fn.call(this.core));
|
|
811
|
+
}
|
|
812
|
+
async projectGet(id) {
|
|
813
|
+
const fn = this.requireProjects(this.core.projectGet, 'Reading a project');
|
|
814
|
+
return JSON.parse(await fn.call(this.core, id));
|
|
815
|
+
}
|
|
816
|
+
async projectMembers(projectId) {
|
|
817
|
+
const fn = this.requireProjects(this.core.projectMembers, 'Listing project members');
|
|
818
|
+
return JSON.parse(await fn.call(this.core, projectId));
|
|
819
|
+
}
|
|
820
|
+
/** Attach a member. `created:false` = the idempotent duplicate hit (emit no event for it). */
|
|
821
|
+
async projectMemberAttach(projectId, kind, ref, meta, attachedBy) {
|
|
822
|
+
const fn = this.requireProjects(this.core.projectMemberAttach, 'Attaching a project member');
|
|
823
|
+
return JSON.parse(await fn.call(this.core, projectId, kind, ref, meta !== undefined ? JSON.stringify(meta) : null, attachedBy ?? null));
|
|
824
|
+
}
|
|
825
|
+
/** Detach (tombstone). `false` = no such live member on that project (the route's 404). */
|
|
826
|
+
async projectMemberDetach(projectId, memberId) {
|
|
827
|
+
const fn = this.requireProjects(this.core.projectMemberDetach, 'Detaching a project member');
|
|
828
|
+
return JSON.parse(await fn.call(this.core, projectId, memberId));
|
|
829
|
+
}
|
|
830
|
+
/** The projects holding a live `(kind, ref)` membership — the run→project reverse read. */
|
|
831
|
+
async memberProjects(kind, ref) {
|
|
832
|
+
const fn = this.requireProjects(this.core.memberProjects, 'Resolving a member’s projects');
|
|
833
|
+
return JSON.parse(await fn.call(this.core, kind, ref));
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Durable interaction requests (ADR §5.3), newest first — or `null` when this addon predates
|
|
837
|
+
* the binding (the `runEvents` convention: a missing capability must stay distinguishable from
|
|
838
|
+
* "no open prompts", or the caches' fallback chain misreports upgrades as empty inboxes).
|
|
839
|
+
*/
|
|
840
|
+
async interactionRequests(sessionId, status) {
|
|
841
|
+
if (typeof this.core.interactionRequests !== 'function')
|
|
842
|
+
return null;
|
|
843
|
+
return JSON.parse(await this.core.interactionRequests(sessionId ?? null, status ?? null));
|
|
844
|
+
}
|
|
845
|
+
// ── The foundation record (ADR §3.2): charter writes + record probes ────────
|
|
846
|
+
/** Capture a memory at a STRICT `kind:id[/…]` scope (the engine refuses malformed segments). */
|
|
847
|
+
async captureMemory(content, scope) {
|
|
848
|
+
const fn = this.requireProjects(this.core.captureMemory, 'Capturing a memory');
|
|
849
|
+
await fn.call(this.core, content, scope);
|
|
850
|
+
}
|
|
851
|
+
/** Memories within `scope`'s subtree, newest first — the ADR's memory.coverage probe. */
|
|
852
|
+
async listMemories(scope, limit) {
|
|
853
|
+
const fn = this.requireProjects(this.core.listMemories, 'Listing memories');
|
|
854
|
+
return JSON.parse(await fn.call(this.core, scope, limit));
|
|
855
|
+
}
|
|
856
|
+
/** Ingest a document (title + chunks) into the knowledge store. Returns the chunk count. */
|
|
857
|
+
async ingestKnowledge(title, chunks) {
|
|
858
|
+
const fn = this.requireProjects(this.core.ingestKnowledge, 'Ingesting knowledge');
|
|
859
|
+
return JSON.parse(await fn.call(this.core, title, JSON.stringify(chunks)));
|
|
860
|
+
}
|
|
861
|
+
/** Recall up to `k` knowledge chunks relevant to `query`. */
|
|
862
|
+
async recallKnowledge(query, k) {
|
|
863
|
+
const fn = this.requireProjects(this.core.recallKnowledge, 'Recalling knowledge');
|
|
864
|
+
return JSON.parse(await fn.call(this.core, query, k));
|
|
865
|
+
}
|
|
591
866
|
// ── Chat sessions (core#134 / crew#165) ────────────────────────────────────
|
|
592
867
|
async chatOpen(chatId, clis, cwd) {
|
|
593
868
|
const raw = await this.core.chatOpen(chatId, JSON.stringify(clis), cwd ?? null);
|
|
@@ -968,6 +1243,32 @@ export class CoreAdapter {
|
|
|
968
1243
|
// regression FINDING-084's first attempted fix caused.
|
|
969
1244
|
await writeFile(join(dir, `${def.id}.json`), JSON.stringify(overlayDef, null, 2), 'utf8');
|
|
970
1245
|
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Arm a PER-RUN composed workflow def (crew#293 deliver, crew#311 deliverable floor) with the
|
|
1248
|
+
* engine — hot registration
|
|
1249
|
+
* ONLY. Deliberately neither of the other two paths:
|
|
1250
|
+
* - not `registerWorkflow()`: the composed def must not enter `userWorkflows` or the overlay
|
|
1251
|
+
* dir — it is launch input for one run, and persisting it would grow the catalog and the
|
|
1252
|
+
* overlay dir by one entry per delivered run;
|
|
1253
|
+
* - not `_writeBuiltinOverlay()`: same reason, no file.
|
|
1254
|
+
* The engine's `registerWorkflow` binding validates the def server-side and makes it visible
|
|
1255
|
+
* to the next `launchRun` with no restart — exactly the lifetime a per-run def needs. (The
|
|
1256
|
+
* def is consumed at PLANNING time; a later daemon restart resumes the run from its persisted
|
|
1257
|
+
* units, so the in-memory registration expiring with the process is fine.)
|
|
1258
|
+
*/
|
|
1259
|
+
async _armPerRunWorkflow(def) {
|
|
1260
|
+
const core = this.core;
|
|
1261
|
+
const register = core['registerWorkflow'];
|
|
1262
|
+
if (typeof register !== 'function') {
|
|
1263
|
+
// Same doctrine as registerWorkflow(): no validator ⇒ refuse loudly. Silently launching
|
|
1264
|
+
// the BASE workflow instead would drop the delivery the caller explicitly asked for —
|
|
1265
|
+
// and, for crew#311, would drop the deliverable floor, launching a run that can once
|
|
1266
|
+
// again report done over an artifact that was never written.
|
|
1267
|
+
throw new Error('deliver: "pr" / requireDeliverables need a wicked-core build with the registerWorkflow ' +
|
|
1268
|
+
'binding — the per-run workflow cannot be armed');
|
|
1269
|
+
}
|
|
1270
|
+
await register.call(this.core, JSON.stringify(def));
|
|
1271
|
+
}
|
|
971
1272
|
/**
|
|
972
1273
|
* Register a user-authored workflow: persist to the Rust workflow overlay dir
|
|
973
1274
|
* (`~/.config/wicked-core/workflows/<id>.json` or `$WICKED_WORKFLOWS_DIR`),
|
|
@@ -1070,6 +1371,24 @@ export class CoreAdapter {
|
|
|
1070
1371
|
if (typeof v !== 'number' || !Number.isInteger(v) || v < 1 || v > 10000)
|
|
1071
1372
|
delete parsed.graphNodeLimit;
|
|
1072
1373
|
}
|
|
1374
|
+
// workerStallMinutes (crew#287): positive minutes; a hand-edited zero/negative/NaN would
|
|
1375
|
+
// make the stall watchdog fire on every sweep, so drop it and fall back to the default.
|
|
1376
|
+
if ('workerStallMinutes' in parsed) {
|
|
1377
|
+
// Same bounds the PUT /settings route enforces (integer, 1..1440) — a hand-edited
|
|
1378
|
+
// settings.json must not enable values the API rejects (Copilot on #301).
|
|
1379
|
+
const v = parsed.workerStallMinutes;
|
|
1380
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < 1 || v > 1440) {
|
|
1381
|
+
delete parsed.workerStallMinutes;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
// worker_config_root (seat sign-in): absolute path or "" (= engine default). A hand-edited
|
|
1385
|
+
// relative path is dropped rather than exported as WICKED_WORKER_HOME, where the engine
|
|
1386
|
+
// would resolve it against an arbitrary spawn cwd.
|
|
1387
|
+
if ('worker_config_root' in parsed) {
|
|
1388
|
+
const r = parsed.worker_config_root;
|
|
1389
|
+
if (typeof r !== 'string' || (r !== '' && !isAbsolute(r)))
|
|
1390
|
+
delete parsed.worker_config_root;
|
|
1391
|
+
}
|
|
1073
1392
|
return { ...DEFAULT_SETTINGS, ...parsed };
|
|
1074
1393
|
}
|
|
1075
1394
|
catch {
|