wicked-crew 0.7.10 → 0.7.11

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.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The run→ad-hoc-group index (wicked-studio#27; api-types 0.19.0).
3
+ *
4
+ * Ad-hoc sibling runs launched via `POST /runs` can be filed onto a grouping surface at launch:
5
+ * `campaignId` attaches the run to an EXISTING engine campaign (validated loudly — an unknown id
6
+ * fails the launch), `groupLabel` puts it in a label group created on first use. Either way the
7
+ * attach is daemon-side PROVENANCE only: the engine's run record carries no such field (the run
8
+ * executes byte-identically to an ungrouped launch, and the campaign scheduler never learns of
9
+ * it), so — exactly like retry lineage (CREW-UX-3) and the actor trail (task #88) — the durable
10
+ * record is the `run.launched` audit entry (`detail.campaignId` / `detail.groupLabel`), and this
11
+ * map is the read-side latency layer that lets `GET /runs` echo `campaign_id`/`group_label` and
12
+ * `GET /campaigns` serve `attached_runs` + the `groups` rows without a trail scan per request.
13
+ *
14
+ * Mirrors `RetryIndex`'s posture: hydrated once at server start (best-effort — a missing or
15
+ * unreadable trail leaves prior runs ungrouped, the pre-0.19 behavior, not an error) and updated
16
+ * at the same post-commit point that writes the audit entry, so the map can only lag by a failed
17
+ * hydrate, never diverge silently.
18
+ *
19
+ * SCAN BUDGET: this index consumes the SAME `run.launched` entries `RetryIndex` already scans,
20
+ * so it exposes {@link GroupIndex.hydrateFromLaunchEntries} and `createServer` feeds both
21
+ * indexes from ONE `readAll({ action: 'run.launched' })` — boot stays at three full-file trail
22
+ * scans, not four (the delivery-index consolidation note, crew#321).
23
+ */
24
+ import type { AuditLog } from './audit.js';
25
+ import type { AuditEntry } from '../core/types.js';
26
+ /** One launch's group attach: exactly one of the two forms (the launch route enforces it). */
27
+ export type GroupAttach = {
28
+ campaignId: string;
29
+ } | {
30
+ label: string;
31
+ };
32
+ export declare class GroupIndex {
33
+ private readonly runToAttach;
34
+ /** campaignId → attached run ids, launch order. */
35
+ private readonly campaignRuns;
36
+ /** label → member run ids, launch order. */
37
+ private readonly labelRuns;
38
+ /**
39
+ * Consume pre-read `run.launched` entries (NEWEST FIRST, `AuditLog.readAll`'s order —
40
+ * exhaustive, per BRIEF-UX-002 C5). The first entry seen per run wins, like the sibling
41
+ * indexes; member lists come out in LAUNCH order because insertion happens on a reversed
42
+ * walk. Idempotent per boot (called once).
43
+ */
44
+ hydrateFromLaunchEntries(entries: AuditEntry[]): void;
45
+ /** Standalone hydrate (tests / direct use) — production shares the scan via `createServer`. */
46
+ hydrate(audit: AuditLog, log?: (msg: string) => void): Promise<void>;
47
+ /** Record one launch's attach (post-commit, beside the audit write). */
48
+ set(runId: string, attach: GroupAttach): void;
49
+ /** This run's attach, or `undefined` (the DTO spells that as ABSENT fields). */
50
+ attachOf(runId: string): GroupAttach | undefined;
51
+ /** Run ids attached to this campaign, launch order (`[]` when none). */
52
+ attachedTo(campaignId: string): string[];
53
+ /** Every label group: `{ label, runIds }` in first-use order, members in launch order. */
54
+ labelGroups(): Array<{
55
+ label: string;
56
+ runIds: string[];
57
+ }>;
58
+ }
59
+ //# sourceMappingURL=group-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-index.d.ts","sourceRoot":"","sources":["../../src/api/group-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,8FAA8F;AAC9F,MAAM,MAAM,WAAW,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAC9D,mDAAmD;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,4CAA4C;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD;;;;;OAKG;IACH,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI;IAoBrD,+FAA+F;IACzF,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1E,wEAAwE;IACxE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAc7C,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIhD,wEAAwE;IACxE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC,0FAA0F;IAC1F,WAAW,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAM1D"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The run→ad-hoc-group index (wicked-studio#27; api-types 0.19.0).
3
+ *
4
+ * Ad-hoc sibling runs launched via `POST /runs` can be filed onto a grouping surface at launch:
5
+ * `campaignId` attaches the run to an EXISTING engine campaign (validated loudly — an unknown id
6
+ * fails the launch), `groupLabel` puts it in a label group created on first use. Either way the
7
+ * attach is daemon-side PROVENANCE only: the engine's run record carries no such field (the run
8
+ * executes byte-identically to an ungrouped launch, and the campaign scheduler never learns of
9
+ * it), so — exactly like retry lineage (CREW-UX-3) and the actor trail (task #88) — the durable
10
+ * record is the `run.launched` audit entry (`detail.campaignId` / `detail.groupLabel`), and this
11
+ * map is the read-side latency layer that lets `GET /runs` echo `campaign_id`/`group_label` and
12
+ * `GET /campaigns` serve `attached_runs` + the `groups` rows without a trail scan per request.
13
+ *
14
+ * Mirrors `RetryIndex`'s posture: hydrated once at server start (best-effort — a missing or
15
+ * unreadable trail leaves prior runs ungrouped, the pre-0.19 behavior, not an error) and updated
16
+ * at the same post-commit point that writes the audit entry, so the map can only lag by a failed
17
+ * hydrate, never diverge silently.
18
+ *
19
+ * SCAN BUDGET: this index consumes the SAME `run.launched` entries `RetryIndex` already scans,
20
+ * so it exposes {@link GroupIndex.hydrateFromLaunchEntries} and `createServer` feeds both
21
+ * indexes from ONE `readAll({ action: 'run.launched' })` — boot stays at three full-file trail
22
+ * scans, not four (the delivery-index consolidation note, crew#321).
23
+ */
24
+ export class GroupIndex {
25
+ runToAttach = new Map();
26
+ /** campaignId → attached run ids, launch order. */
27
+ campaignRuns = new Map();
28
+ /** label → member run ids, launch order. */
29
+ labelRuns = new Map();
30
+ /**
31
+ * Consume pre-read `run.launched` entries (NEWEST FIRST, `AuditLog.readAll`'s order —
32
+ * exhaustive, per BRIEF-UX-002 C5). The first entry seen per run wins, like the sibling
33
+ * indexes; member lists come out in LAUNCH order because insertion happens on a reversed
34
+ * walk. Idempotent per boot (called once).
35
+ */
36
+ hydrateFromLaunchEntries(entries) {
37
+ const seen = new Set();
38
+ const oldestFirst = [];
39
+ for (const entry of entries) {
40
+ if (typeof entry.runId !== 'string')
41
+ continue;
42
+ if (seen.has(entry.runId))
43
+ continue;
44
+ seen.add(entry.runId);
45
+ const campaignId = entry.detail?.['campaignId'];
46
+ const label = entry.detail?.['groupLabel'];
47
+ if (typeof campaignId === 'string' && campaignId !== '') {
48
+ oldestFirst.push({ runId: entry.runId, attach: { campaignId } });
49
+ }
50
+ else if (typeof label === 'string' && label !== '') {
51
+ oldestFirst.push({ runId: entry.runId, attach: { label } });
52
+ }
53
+ }
54
+ // The trail reads newest-first; one reverse restores launch order (unshift-in-loop is O(n²)).
55
+ oldestFirst.reverse();
56
+ for (const { runId, attach } of oldestFirst)
57
+ this.set(runId, attach);
58
+ }
59
+ /** Standalone hydrate (tests / direct use) — production shares the scan via `createServer`. */
60
+ async hydrate(audit, log) {
61
+ try {
62
+ this.hydrateFromLaunchEntries(await audit.readAll({ action: 'run.launched' }));
63
+ }
64
+ catch (err) {
65
+ log?.(`[runs] group-index hydrate failed (prior runs read as ungrouped until restart): ${err instanceof Error ? err.message : String(err)}`);
66
+ }
67
+ }
68
+ /** Record one launch's attach (post-commit, beside the audit write). */
69
+ set(runId, attach) {
70
+ if (this.runToAttach.has(runId))
71
+ return; // launch-time fact — first write wins
72
+ this.runToAttach.set(runId, attach);
73
+ if ('campaignId' in attach) {
74
+ const list = this.campaignRuns.get(attach.campaignId) ?? [];
75
+ list.push(runId);
76
+ this.campaignRuns.set(attach.campaignId, list);
77
+ }
78
+ else {
79
+ const list = this.labelRuns.get(attach.label) ?? [];
80
+ list.push(runId);
81
+ this.labelRuns.set(attach.label, list);
82
+ }
83
+ }
84
+ /** This run's attach, or `undefined` (the DTO spells that as ABSENT fields). */
85
+ attachOf(runId) {
86
+ return this.runToAttach.get(runId);
87
+ }
88
+ /** Run ids attached to this campaign, launch order (`[]` when none). */
89
+ attachedTo(campaignId) {
90
+ return [...(this.campaignRuns.get(campaignId) ?? [])];
91
+ }
92
+ /** Every label group: `{ label, runIds }` in first-use order, members in launch order. */
93
+ labelGroups() {
94
+ return [...this.labelRuns.entries()].map(([label, runIds]) => ({
95
+ label,
96
+ runIds: [...runIds],
97
+ }));
98
+ }
99
+ }
100
+ //# sourceMappingURL=group-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-index.js","sourceRoot":"","sources":["../../src/api/group-index.ts"],"names":[],"mappings":"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"}
@@ -13,8 +13,14 @@
13
13
  * the audit entry, so the map can only lag by a failed hydrate, never diverge silently.
14
14
  */
15
15
  import type { AuditLog } from './audit.js';
16
+ import type { AuditEntry } from '../core/types.js';
16
17
  export declare class RetryIndex {
17
18
  private readonly runToRetryOf;
19
+ /**
20
+ * Consume pre-read `run.launched` entries — the seam that lets `createServer` feed this index
21
+ * and `GroupIndex` from ONE trail scan (the delivery-index consolidation note, crew#321).
22
+ */
23
+ hydrateFromLaunchEntries(entries: AuditEntry[]): void;
18
24
  /**
19
25
  * Load lineage from EVERY `run.launched` entry in the trail — exhaustively, not capped
20
26
  * (BRIEF-UX-002 C5: a lineage fact 2,678 launches deep must survive a restart; the newest-1000
@@ -1 +1 @@
1
- {"version":3,"file":"retry-index.d.ts","sourceRoot":"","sources":["../../src/api/retry-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB1E,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIzC,4FAA4F;IAC5F,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAG9C"}
1
+ {"version":3,"file":"retry-index.d.ts","sourceRoot":"","sources":["../../src/api/retry-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D;;;OAGG;IACH,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI;IASrD;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1E,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIzC,4FAA4F;IAC5F,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAG9C"}
@@ -14,6 +14,18 @@
14
14
  */
15
15
  export class RetryIndex {
16
16
  runToRetryOf = new Map();
17
+ /**
18
+ * Consume pre-read `run.launched` entries — the seam that lets `createServer` feed this index
19
+ * and `GroupIndex` from ONE trail scan (the delivery-index consolidation note, crew#321).
20
+ */
21
+ hydrateFromLaunchEntries(entries) {
22
+ for (const entry of entries) {
23
+ const retryOf = entry.detail?.['retryOf'];
24
+ if (typeof entry.runId === 'string' && typeof retryOf === 'string') {
25
+ this.runToRetryOf.set(entry.runId, retryOf);
26
+ }
27
+ }
28
+ }
17
29
  /**
18
30
  * Load lineage from EVERY `run.launched` entry in the trail — exhaustively, not capped
19
31
  * (BRIEF-UX-002 C5: a lineage fact 2,678 launches deep must survive a restart; the newest-1000
@@ -23,12 +35,7 @@ export class RetryIndex {
23
35
  */
24
36
  async hydrate(audit, log) {
25
37
  try {
26
- for (const entry of await audit.readAll({ action: 'run.launched' })) {
27
- const retryOf = entry.detail?.['retryOf'];
28
- if (typeof entry.runId === 'string' && typeof retryOf === 'string') {
29
- this.runToRetryOf.set(entry.runId, retryOf);
30
- }
31
- }
38
+ this.hydrateFromLaunchEntries(await audit.readAll({ action: 'run.launched' }));
32
39
  }
33
40
  catch (err) {
34
41
  log?.(`[runs] retry-index hydrate failed (prior runs read as not-a-retry until restart): ${err instanceof Error ? err.message : String(err)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"retry-index.js","sourceRoot":"","sources":["../../src/api/retry-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,OAAO,UAAU;IACJ,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1D;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,KAAe,EAAE,GAA2B;QACxD,IAAI,CAAC;YACH,KAAK,MAAM,KAAK,IAAI,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;gBACpE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC;gBAC1C,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;oBACnE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,EAAE,CACH,qFACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,GAAG,CAAC,KAAa,EAAE,OAAe;QAChC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,4FAA4F;IAC5F,UAAU,CAAC,KAAa;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;CACF"}
1
+ {"version":3,"file":"retry-index.js","sourceRoot":"","sources":["../../src/api/retry-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,MAAM,OAAO,UAAU;IACJ,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1D;;;OAGG;IACH,wBAAwB,CAAC,OAAqB;QAC5C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1C,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACnE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,KAAe,EAAE,GAA2B;QACxD,IAAI,CAAC;YACH,IAAI,CAAC,wBAAwB,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;QACjF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,EAAE,CACH,qFACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,GAAG,CAAC,KAAa,EAAE,OAAe;QAChC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,4FAA4F;IAC5F,UAAU,CAAC,KAAa;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;CACF"}
@@ -11,6 +11,7 @@ import type { DocLedgerSweep } from '../interactive/doc-ledger-sweep.js';
11
11
  import { AuditLog } from './audit.js';
12
12
  import { type ErrorRing } from './diagnostics.js';
13
13
  import { RetryIndex } from './retry-index.js';
14
+ import { GroupIndex } from './group-index.js';
14
15
  import { GuidanceIndex } from './guidance-index.js';
15
16
  import { DeliveryIndex } from './delivery-index.js';
16
17
  import { type DeliverExec } from './post-hoc-deliver.js';
@@ -29,7 +30,7 @@ export { API_PREFIX } from './api-prefix.js';
29
30
  * schema does not accept — so `.strict()` is what turns that trip into a 400 instead of an
30
31
  * unworkflowed run reported as `201`.
31
32
  */
32
- export declare const LaunchSchema: z.ZodEffects<z.ZodObject<{
33
+ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
33
34
  problem: z.ZodString;
34
35
  sessionId: z.ZodOptional<z.ZodString>;
35
36
  clisJson: z.ZodOptional<z.ZodString>;
@@ -52,10 +53,22 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodObject<{
52
53
  * (the route checks the store and 400s with a named error otherwise); persisted via the
53
54
  * `run.launched` audit entry + retry index and echoed as `AgentSession.retry_of`. */
54
55
  retryOf: z.ZodOptional<z.ZodString>;
56
+ /** wicked-studio#27 (api-types 0.19.0) — attach this AD-HOC run to an EXISTING campaign's
57
+ * surface. Validated against the campaign store before anything launches (unknown ⇒ 404,
58
+ * engine without campaign bindings ⇒ 501 — never a silently dropped attach); persisted via
59
+ * the `run.launched` audit entry + group index, echoed as `AgentSession.campaign_id`, and
60
+ * served under `Campaign.attached_runs`. Provenance only: the run is NOT a DAG node. */
61
+ campaignId: z.ZodOptional<z.ZodString>;
62
+ /** wicked-studio#27 (api-types 0.19.0) — ad-hoc label group, created on first use: runs
63
+ * sharing a label form one `RunGroup` on `GET /campaigns`. Persisted/echoed like
64
+ * `campaignId` (as `AgentSession.group_label`). */
65
+ groupLabel: z.ZodOptional<z.ZodString>;
55
66
  }, "strict", z.ZodTypeAny, {
56
67
  problem: string;
57
68
  deliver?: "pr" | "none" | undefined;
58
69
  projectId?: string | undefined;
70
+ campaignId?: string | undefined;
71
+ groupLabel?: string | undefined;
59
72
  workflow?: string | undefined;
60
73
  repoRef?: string | undefined;
61
74
  clisJson?: string | undefined;
@@ -67,6 +80,8 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodObject<{
67
80
  problem: string;
68
81
  deliver?: "pr" | "none" | undefined;
69
82
  projectId?: string | undefined;
83
+ campaignId?: string | undefined;
84
+ groupLabel?: string | undefined;
70
85
  workflow?: string | undefined;
71
86
  repoRef?: string | undefined;
72
87
  clisJson?: string | undefined;
@@ -78,6 +93,8 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodObject<{
78
93
  problem: string;
79
94
  deliver?: "pr" | "none" | undefined;
80
95
  projectId?: string | undefined;
96
+ campaignId?: string | undefined;
97
+ groupLabel?: string | undefined;
81
98
  workflow?: string | undefined;
82
99
  repoRef?: string | undefined;
83
100
  clisJson?: string | undefined;
@@ -89,6 +106,34 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodObject<{
89
106
  problem: string;
90
107
  deliver?: "pr" | "none" | undefined;
91
108
  projectId?: string | undefined;
109
+ campaignId?: string | undefined;
110
+ groupLabel?: string | undefined;
111
+ workflow?: string | undefined;
112
+ repoRef?: string | undefined;
113
+ clisJson?: string | undefined;
114
+ sessionId?: string | undefined;
115
+ retryOf?: string | undefined;
116
+ entityMode?: "shared" | "isolated" | undefined;
117
+ humanConfirm?: string | undefined;
118
+ }>, {
119
+ problem: string;
120
+ deliver?: "pr" | "none" | undefined;
121
+ projectId?: string | undefined;
122
+ campaignId?: string | undefined;
123
+ groupLabel?: string | undefined;
124
+ workflow?: string | undefined;
125
+ repoRef?: string | undefined;
126
+ clisJson?: string | undefined;
127
+ sessionId?: string | undefined;
128
+ retryOf?: string | undefined;
129
+ entityMode?: "shared" | "isolated" | undefined;
130
+ humanConfirm?: string | undefined;
131
+ }, {
132
+ problem: string;
133
+ deliver?: "pr" | "none" | undefined;
134
+ projectId?: string | undefined;
135
+ campaignId?: string | undefined;
136
+ groupLabel?: string | undefined;
92
137
  workflow?: string | undefined;
93
138
  repoRef?: string | undefined;
94
139
  clisJson?: string | undefined;
@@ -162,6 +207,10 @@ export interface RuntimeDeps {
162
207
  /** Run→retry-lineage index (CREW-UX-3) — `createServer` hydrates one from the audit trail so
163
208
  * a restarted daemon still echoes `retry_of`; a directly-driven route set gets a fresh one. */
164
209
  retryIndex?: RetryIndex;
210
+ /** Run→ad-hoc-group index (wicked-studio#27) — `createServer` hydrates one from the audit
211
+ * trail (the SAME `run.launched` scan as `retryIndex`) so attaches survive a restart; a
212
+ * directly-driven route set gets a fresh one. */
213
+ groupIndex?: GroupIndex;
165
214
  /** Run→operator-guidance index (CREW-UX-7) — `createServer` hydrates one from the audit trail
166
215
  * so a restarted daemon still echoes `guidance`; a directly-driven route set gets a fresh one. */
167
216
  guidanceIndex?: GuidanceIndex;
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ import { readFileSync, existsSync } from 'node:fs';
5
5
  import { promises as fsp } from 'node:fs';
6
6
  import { isAbsolute, join, relative, resolve } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
- import { ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, SteeringUnsupportedError, humanGatePhaseIds } from '../core/adapter.js';
8
+ import { CampaignsUnsupportedError, ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, SteeringUnsupportedError, humanGatePhaseIds } from '../core/adapter.js';
9
9
  import { codeGraphDb, requirementsGraph } from '../core/repoPaths.js';
10
10
  import { QeGateCache } from '../qe/gate-events.js';
11
11
  import { buildAcceptanceView, resolveRunWorkflow } from '../qe/acceptance.js';
@@ -32,6 +32,7 @@ import { MEMBERSHIP_ATTACHED, membershipAttachedKey } from '../projects/events.j
32
32
  import { AuditLog } from './audit.js';
33
33
  import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion, listStoreFiles, readStudioBundleVersion, } from './diagnostics.js';
34
34
  import { RetryIndex } from './retry-index.js';
35
+ import { GroupIndex } from './group-index.js';
35
36
  import { GuidanceIndex } from './guidance-index.js';
36
37
  import { DeliveryIndex, deliveryStateWithVacuity, gitRunBranchIsEmpty, gitWorktreeIsClean, prUrlFrom, } from './delivery-index.js';
37
38
  import { runDeliverScript } from './post-hoc-deliver.js';
@@ -151,9 +152,22 @@ export const LaunchSchema = z.object({
151
152
  * (the route checks the store and 400s with a named error otherwise); persisted via the
152
153
  * `run.launched` audit entry + retry index and echoed as `AgentSession.retry_of`. */
153
154
  retryOf: z.string().min(1).optional(),
155
+ /** wicked-studio#27 (api-types 0.19.0) — attach this AD-HOC run to an EXISTING campaign's
156
+ * surface. Validated against the campaign store before anything launches (unknown ⇒ 404,
157
+ * engine without campaign bindings ⇒ 501 — never a silently dropped attach); persisted via
158
+ * the `run.launched` audit entry + group index, echoed as `AgentSession.campaign_id`, and
159
+ * served under `Campaign.attached_runs`. Provenance only: the run is NOT a DAG node. */
160
+ campaignId: z.string().min(1).optional(),
161
+ /** wicked-studio#27 (api-types 0.19.0) — ad-hoc label group, created on first use: runs
162
+ * sharing a label form one `RunGroup` on `GET /campaigns`. Persisted/echoed like
163
+ * `campaignId` (as `AgentSession.group_label`). */
164
+ groupLabel: z.string().min(1).max(200).optional(),
154
165
  }).strict().refine((b) => b.deliver !== 'pr' || b.workflow !== undefined, {
155
166
  message: 'deliver: "pr" requires a workflow — a free-text run has no def to append the deliver phase to',
156
167
  path: ['deliver'],
168
+ }).refine((b) => b.campaignId === undefined || b.groupLabel === undefined, {
169
+ message: 'campaignId and groupLabel are mutually exclusive — a run files onto ONE grouping surface (an existing campaign, or a label group)',
170
+ path: ['groupLabel'],
157
171
  });
158
172
  export const GateSchema = z.object({
159
173
  approve: z.boolean(),
@@ -235,6 +249,7 @@ runtime = {}) {
235
249
  const openWithOs = runtime.openWithOs ?? openWithSystemDefault;
236
250
  const signedIn = runtime.signedIn ?? signedInHeuristic;
237
251
  const retryIndex = runtime.retryIndex ?? new RetryIndex();
252
+ const groupIndex = runtime.groupIndex ?? new GroupIndex();
238
253
  const guidanceIndex = runtime.guidanceIndex ?? new GuidanceIndex();
239
254
  const deliveryIndex = runtime.deliveryIndex ?? new DeliveryIndex();
240
255
  const worktreeExists = runtime.worktreeExists ?? ((p) => existsSync(p));
@@ -265,6 +280,15 @@ runtime = {}) {
265
280
  const retryOf = retryIndex.retryOfFor(view.session.id);
266
281
  if (retryOf !== undefined)
267
282
  view.session.retry_of = retryOf;
283
+ // wicked-studio#27 (api-types 0.19.0): the launch-time group attach, from the same durable
284
+ // trail record as lineage. ABSENT when ungrouped — never null.
285
+ const attach = groupIndex.attachOf(view.session.id);
286
+ if (attach !== undefined) {
287
+ if ('campaignId' in attach)
288
+ view.session.campaign_id = attach.campaignId;
289
+ else
290
+ view.session.group_label = attach.label;
291
+ }
268
292
  const guidance = guidanceIndex.guidanceFor(view.session.id);
269
293
  if (guidance !== undefined)
270
294
  view.session.guidance = guidance;
@@ -628,9 +652,11 @@ runtime = {}) {
628
652
  manifest: {
629
653
  requestType: 'LaunchRunBody',
630
654
  responseType: '{ runId: string }',
631
- // 404/409: unknown project / archived-or-synthesized project + busy engine (see the
632
- // catch below); 400: zod reject or a retryOf naming no existing run.
633
- statusCodes: [201, 400, 404, 409],
655
+ // 404/409: unknown project / unknown campaignId (wicked-studio#27) /
656
+ // archived-or-synthesized project + busy engine (see the catch below); 400: zod
657
+ // reject or a retryOf naming no existing run; 501: campaignId attach on an engine
658
+ // addon without the campaign bindings ("upgrade the engine").
659
+ statusCodes: [201, 400, 404, 409, 501],
634
660
  },
635
661
  },
636
662
  }, async (req, reply) => {
@@ -716,6 +742,26 @@ runtime = {}) {
716
742
  });
717
743
  }
718
744
  }
745
+ // Ad-hoc campaign attach (wicked-studio#27): `campaignId` must name an EXISTING campaign,
746
+ // checked BEFORE anything launches — an unknown id is a 404 with nothing committed, never a
747
+ // run silently filed onto a surface that does not exist. An engine addon without the
748
+ // campaign bindings is a 501 ("upgrade the engine"), the campaigns-surface doctrine. A
749
+ // `groupLabel` needs no check: a label group is created by its first use.
750
+ if (b.campaignId !== undefined) {
751
+ try {
752
+ if ((await adapter.campaignDetail(b.campaignId)) === null) {
753
+ return reply.code(404).send({
754
+ error: `campaignId names an unknown campaign: ${b.campaignId} — an ad-hoc run can only attach to an existing campaign (launch one via POST /campaigns, or use groupLabel for a create-on-first-use group)`,
755
+ });
756
+ }
757
+ }
758
+ catch (err) {
759
+ if (err instanceof CampaignsUnsupportedError) {
760
+ return reply.code(501).send({ error: message(err) });
761
+ }
762
+ throw err;
763
+ }
764
+ }
719
765
  try {
720
766
  const runId = await adapter.launchRun(input);
721
767
  // Who launched it — the engine's LaunchOptions carries no actor field
@@ -734,10 +780,18 @@ runtime = {}) {
734
780
  // CREW-UX-3: the trail is the durable record of lineage — the retry index (and a
735
781
  // restarted daemon's hydrate) reads it back from exactly this entry.
736
782
  ...(b.retryOf !== undefined ? { retryOf: b.retryOf } : {}),
783
+ // wicked-studio#27: the trail is likewise the durable record of the group attach —
784
+ // the group index (and a restarted daemon's hydrate) reads it back from here.
785
+ ...(b.campaignId !== undefined ? { campaignId: b.campaignId } : {}),
786
+ ...(b.groupLabel !== undefined ? { groupLabel: b.groupLabel } : {}),
737
787
  },
738
788
  });
739
789
  if (b.retryOf !== undefined)
740
790
  retryIndex.set(runId, b.retryOf);
791
+ if (b.campaignId !== undefined)
792
+ groupIndex.set(runId, { campaignId: b.campaignId });
793
+ else if (b.groupLabel !== undefined)
794
+ groupIndex.set(runId, { label: b.groupLabel });
741
795
  if (b.projectId !== undefined) {
742
796
  // The engine attached the crew.run membership ATOMICALLY with the launch record
743
797
  // (DES-PROJECT-001 §2.2) — this is the post-commit half: tag future /ws frames and
@@ -2380,11 +2434,16 @@ runtime = {}) {
2380
2434
  registerProjectRoutes(app, adapter, { ...projects, settings: projectSettings }, security);
2381
2435
  // ── Campaigns (crew#342 + TH-9) — the engine's durable Run-DAG scheduler over REST ──────────
2382
2436
  // Progress streams as campaign* CoreEvents on the existing allowlist-free /ws relay; these
2383
- // routes are launch/resume/cancel + the reads a scoreboard builds from.
2437
+ // routes are launch/resume/cancel + the reads a scoreboard builds from. The GET routes join
2438
+ // per-member delivery (wicked-studio#27) with the SAME machinery the run DTOs use — one
2439
+ // sessionsDetail() per request, the shared TTL-memoized vacuity probes, never a per-node fetch.
2384
2440
  registerCampaignRoutes(app, adapter, {
2385
2441
  audit,
2386
2442
  actorOf,
2387
2443
  roster: () => CoreAdapter.roster(),
2444
+ groupIndex,
2445
+ deliveryUrlFor: (runId) => deliveryIndex.urlFor(runId),
2446
+ vacuity: vacuityProbes,
2388
2447
  });
2389
2448
  // ── Governance wiki management (wiki-mgmt) — scoreboard + honest empty-state meta ──────────
2390
2449
  // Browse (`GET /governance/rules` + facets) and retire (`DELETE /governance/rules/:id`) stay