wicked-crew 0.7.31 → 0.7.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +2 -1
  2. package/dist/api/chat-turns.d.ts +87 -0
  3. package/dist/api/chat-turns.d.ts.map +1 -0
  4. package/dist/api/chat-turns.js +147 -0
  5. package/dist/api/chat-turns.js.map +1 -0
  6. package/dist/api/roster-standing.d.ts +42 -0
  7. package/dist/api/roster-standing.d.ts.map +1 -0
  8. package/dist/api/roster-standing.js +54 -0
  9. package/dist/api/roster-standing.js.map +1 -0
  10. package/dist/api/routes.d.ts +54 -3
  11. package/dist/api/routes.d.ts.map +1 -1
  12. package/dist/api/routes.js +58 -38
  13. package/dist/api/routes.js.map +1 -1
  14. package/dist/api/server.d.ts.map +1 -1
  15. package/dist/api/server.js +45 -1
  16. package/dist/api/server.js.map +1 -1
  17. package/dist/api/testing.d.ts +4 -4
  18. package/dist/cli/index.js +6 -3
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/core/adapter.d.ts +24 -0
  21. package/dist/core/adapter.d.ts.map +1 -1
  22. package/dist/core/adapter.js +40 -2
  23. package/dist/core/adapter.js.map +1 -1
  24. package/dist/core/types.d.ts +9 -0
  25. package/dist/core/types.d.ts.map +1 -1
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/interactive/bridge-pool.d.ts +2 -2
  28. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  29. package/dist/interactive/bridge-pool.js +19 -4
  30. package/dist/interactive/bridge-pool.js.map +1 -1
  31. package/dist/interactive/bus-subscriber-errors.d.ts +61 -0
  32. package/dist/interactive/bus-subscriber-errors.d.ts.map +1 -0
  33. package/dist/interactive/bus-subscriber-errors.js +85 -0
  34. package/dist/interactive/bus-subscriber-errors.js.map +1 -0
  35. package/dist/interactive/chat-events.d.ts +16 -0
  36. package/dist/interactive/chat-events.d.ts.map +1 -1
  37. package/dist/interactive/chat-events.js +59 -10
  38. package/dist/interactive/chat-events.js.map +1 -1
  39. package/dist/interactive/demo-events.d.ts +66 -0
  40. package/dist/interactive/demo-events.d.ts.map +1 -1
  41. package/dist/interactive/demo-events.js +154 -10
  42. package/dist/interactive/demo-events.js.map +1 -1
  43. package/dist/interactive/doc-delete-routes.js +2 -2
  44. package/dist/interactive/doc-delete-routes.js.map +1 -1
  45. package/dist/interactive/draft-events.d.ts +13 -0
  46. package/dist/interactive/draft-events.d.ts.map +1 -1
  47. package/dist/interactive/draft-events.js +34 -9
  48. package/dist/interactive/draft-events.js.map +1 -1
  49. package/dist/interactive/draft-skill.d.ts +68 -0
  50. package/dist/interactive/draft-skill.d.ts.map +1 -0
  51. package/dist/interactive/draft-skill.js +127 -0
  52. package/dist/interactive/draft-skill.js.map +1 -0
  53. package/dist/interactive/edit-events.d.ts +13 -0
  54. package/dist/interactive/edit-events.d.ts.map +1 -1
  55. package/dist/interactive/edit-events.js +35 -9
  56. package/dist/interactive/edit-events.js.map +1 -1
  57. package/dist/interactive/proxy-routes.d.ts +36 -0
  58. package/dist/interactive/proxy-routes.d.ts.map +1 -1
  59. package/dist/interactive/proxy-routes.js +90 -3
  60. package/dist/interactive/proxy-routes.js.map +1 -1
  61. package/dist/interactive/ws-relay.d.ts +2 -0
  62. package/dist/interactive/ws-relay.d.ts.map +1 -1
  63. package/dist/interactive/ws-relay.js +7 -3
  64. package/dist/interactive/ws-relay.js.map +1 -1
  65. package/dist/projects/activity.d.ts +17 -3
  66. package/dist/projects/activity.d.ts.map +1 -1
  67. package/dist/projects/activity.js +34 -13
  68. package/dist/projects/activity.js.map +1 -1
  69. package/dist/projects/events.d.ts +2 -0
  70. package/dist/projects/events.d.ts.map +1 -1
  71. package/dist/projects/events.js +7 -3
  72. package/dist/projects/events.js.map +1 -1
  73. package/dist/qe/acceptance.d.ts +72 -3
  74. package/dist/qe/acceptance.d.ts.map +1 -1
  75. package/dist/qe/acceptance.js +137 -17
  76. package/dist/qe/acceptance.js.map +1 -1
  77. package/dist/qe/gate-events.d.ts +2 -0
  78. package/dist/qe/gate-events.d.ts.map +1 -1
  79. package/dist/qe/gate-events.js +7 -3
  80. package/dist/qe/gate-events.js.map +1 -1
  81. package/dist/qe/ledger.d.ts +128 -28
  82. package/dist/qe/ledger.d.ts.map +1 -1
  83. package/dist/qe/ledger.js +272 -42
  84. package/dist/qe/ledger.js.map +1 -1
  85. package/dist/skills/runtime.d.ts +7 -0
  86. package/dist/skills/runtime.d.ts.map +1 -1
  87. package/dist/skills/runtime.js +17 -0
  88. package/dist/skills/runtime.js.map +1 -1
  89. package/dist/studio/assets/{index-Clm0MXDF.js → index-DhojBZ1T.js} +91 -91
  90. package/dist/studio/index.html +1 -1
  91. package/dist/studio/testid-inventory.json +51 -3
  92. package/endpoint-manifest.json +1 -1
  93. package/package.json +4 -4
@@ -17,7 +17,9 @@ import { execCapped, ExecOutputTooLarge } from '../core/exec.js';
17
17
  import { callEstateTool, EstateMcpError } from '../core/estate-mcp-client.js';
18
18
  import { SeatHealthTracker } from './seat-health.js';
19
19
  import { applyWorkerConfigRoot, signedInHeuristic } from './seat-signin.js';
20
- import { chatSeatAdmission, seatStanding } from './seat-standing.js';
20
+ import { chatSeatAdmission } from './seat-standing.js';
21
+ import { rosterWithStandingFactory } from './roster-standing.js';
22
+ import { ChatTurnIndex } from './chat-turns.js';
21
23
  import { ChatScopeIndex, chatScopeDeps, prepareChatScratch, removeChatScratch, resolveChatScope, } from './chat-scope.js';
22
24
  import { allowedRootsFor, isInsideRoot, openWithSystemDefault } from './open-path.js';
23
25
  import { InvalidDiffBaseError, MERGE_BASE_LITERAL, NotARegularFileError, UnresolvableDiffBaseError, branchDiff, isPlainRef, readFileCapped, worktreeDiff, } from './run-files.js';
@@ -339,6 +341,12 @@ export const LaunchSchema = z.object({
339
341
  * defaults to `"pr"` (flippable via the `deliverDefault` setting), everything else to
340
342
  * `"none"` — see the resolution below. */
341
343
  deliver: z.enum(['pr', 'none']).optional(),
344
+ /** F-E2E-030 — who confirms the deliver phase. `'human'` (the default when omitted): the engine
345
+ * pauses before the composed `deliver` Tool unit pushes and opens the PR, whatever
346
+ * `humanConfirm` says. `'auto'`: the caller's EXPLICIT opt-out — the push and PR follow verify
347
+ * unattended, under the daemon's active gh account; a UI sending it must name the posture
348
+ * "auto-deliver" to its operator. Additive; an older daemon's schema rejects it with a 400. */
349
+ deliverGate: z.enum(['human', 'auto']).optional(),
342
350
  /** DES-UX-001 §8.3 (CREW-UX-3) — the run this launch retries. Must name an EXISTING run id
343
351
  * (the route checks the store and 400s with a named error otherwise); persisted via the
344
352
  * `run.launched` audit entry + retry index and echoed as `AgentSession.retry_of`. */
@@ -356,6 +364,12 @@ export const LaunchSchema = z.object({
356
364
  }).strict().refine((b) => b.deliver !== 'pr' || b.workflow !== undefined, {
357
365
  message: 'deliver: "pr" requires a workflow — a free-text run has no def to append the deliver phase to',
358
366
  path: ['deliver'],
367
+ }).refine((b) => b.deliverGate === undefined || b.deliver !== 'none', {
368
+ // F-E2E-030: a deliver-gate posture on a launch that declines delivery is a contradiction the
369
+ // caller should hear about, not a silent no-op. (A launch that OMITS `deliver` may still
370
+ // resolve to no deliver unit — repo-less, free-text, read-only def; the field is then inert.)
371
+ message: 'deliverGate has no meaning with deliver: "none" — there is no deliver phase to gate',
372
+ path: ['deliverGate'],
359
373
  }).refine((b) => b.campaignId === undefined || b.groupLabel === undefined, {
360
374
  message: 'campaignId and groupLabel are mutually exclusive — a run files onto ONE grouping surface (an existing campaign, or a label group)',
361
375
  path: ['groupLabel'],
@@ -471,6 +485,7 @@ runtime = {}) {
471
485
  const runTimingIndex = runtime.runTimingIndex ?? new RunTimingIndex();
472
486
  const guidanceIndex = runtime.guidanceIndex ?? new GuidanceIndex();
473
487
  const chatScopes = runtime.chatScopes ?? new ChatScopeIndex();
488
+ const chatTurns = runtime.chatTurns ?? new ChatTurnIndex();
474
489
  const deliveryIndex = runtime.deliveryIndex ?? new DeliveryIndex();
475
490
  // Wave 6: the doc↔run binding and the registered test sets — `createServer` injects the real
476
491
  // ones; a directly-driven route set gets an empty index (every run `document_id: null`, no sets).
@@ -611,7 +626,13 @@ runtime = {}) {
611
626
  // null / [] for the rest ("where declared", never invented). See src/api/endpoint-manifest.ts.
612
627
  app.get(`${V}/health`, { config: { manifest: { statusCodes: [200] } } }, async () => {
613
628
  const ping = await adapter.ping();
614
- return { status: 'ok', version: PKG_VERSION, ping };
629
+ // F-E2E-030: what this deployment can keep. A composer reads `capabilities.deliverGate`
630
+ // before it promises "pauses at the deliver gate"; a stub-driven route set without the
631
+ // probe honestly reports no gate.
632
+ const capabilities = typeof adapter.engineCapabilities === 'function'
633
+ ? adapter.engineCapabilities()
634
+ : { deliverGate: false };
635
+ return { status: 'ok', version: PKG_VERSION, ping, capabilities };
615
636
  });
616
637
  // The daemon's self-knowledge surface (diagnostics): what is deployed, what it stores, what
617
638
  // has gone wrong recently, and whether ACP is really working across the CLIs — the answer
@@ -702,41 +723,14 @@ runtime = {}) {
702
723
  return { port, host };
703
724
  });
704
725
  // The council seats for the launch form (static production roster), each carrying its RUNTIME
705
- // health (crew#274). The roster is declarative — every configured seat is listed — and `health`
706
- // is what the platform has observed: default active with no message; inactive + the error
707
- // excerpt after a seat-level failure, until an ok output or the recovery probe flips it back.
708
- // Existing fields ride through verbatim (the seat still round-trips into `clisJson` on launch)
709
- // — the spread is deliberately NOT a field whitelist, which is what lets the engine's
710
- // `login_invocation` (seat sign-in, wicked-core PR#278) pass through untouched. Each seat also
711
- // gains `signed_in`: the cheap file/env presence heuristic, computed against the LIVE
712
- // `WICKED_WORKER_HOME` env — the same value the engine reads at the next worker spawn, kept
713
- // current by `applyWorkerConfigRoot` at boot and on every settings change.
714
- //
715
- // Each seat ALSO carries its STANDING (F-2R2-009): `auth` re-reads `signed_in` for what it means
716
- // (a `signed_out` codex is benched on its first ballot; a `not_required` opencode answers on its
717
- // free tier), and `council_eligible` says what a council would do with the seat as far as the
718
- // daemon can tell. `POST /chats` seats its defaults through the SAME standing, so the roster and
719
- // the chat never disagree about a seat.
720
- const rosterWithStanding = () => {
721
- const workerRoot = process.env['WICKED_WORKER_HOME'];
722
- return CoreAdapter.roster().map((seat) => {
723
- const key = String(seat.key);
724
- const health = seatHealth.healthFor(key);
725
- const signed = signedIn(key, workerRoot === '' ? undefined : workerRoot);
726
- return {
727
- ...seat,
728
- health,
729
- signed_in: signed,
730
- // The bench is THIS daemon's council evidence (councilSeatFailed, bounded window) — the
731
- // prediction learns from what the engine actually did with the seat (#533 review, F-1).
732
- ...seatStanding(seat, signed, health, seatHealth.councilBenchFor(key),
733
- // F-A45-006: the seat's OWN "no credential" report (a ballot's "No API key found", a
734
- // worker's 401, an auth ACP fallback) overrides the file probe — `auth` flips, not only
735
- // `council_eligible`, and the evidence rides on the wire.
736
- seatHealth.authFailureFor(key)),
737
- };
738
- });
739
- };
726
+ // health (crew#274) and its STANDING (F-2R2-009) — `api/roster-standing.ts` is the ONE accessor
727
+ // every launch path shares (F-RECON-002/003: the interactive seams, the adapter's onboarding
728
+ // launch and `wicked-crew start` used to hand the engine the RAW registry roster, so signed-out
729
+ // seats were convened and even elected). `createServer` injects the daemon's single instance
730
+ // (built over the same `seatHealth`); a directly-driven route set builds an equivalent one here.
731
+ // `POST /chats` seats its defaults through the SAME standing, so the roster and the chat never
732
+ // disagree about a seat.
733
+ const rosterWithStanding = runtime.rosterWithStanding ?? rosterWithStandingFactory({ seatHealth, signedIn });
740
734
  app.get(`${V}/roster`, async () => ({ roster: rosterWithStanding() }));
741
735
  // Open a file/folder with the OS default application (crew#273) — the studio Files tab's
742
736
  // click-to-open. The open MUST happen daemon-side (the SPA cannot spawn a process), which is
@@ -1055,6 +1049,11 @@ runtime = {}) {
1055
1049
  input.entityMode = b.entityMode;
1056
1050
  if (b.humanConfirm !== undefined)
1057
1051
  input.humanConfirm = b.humanConfirm;
1052
+ // F-E2E-030: only the explicit `'auto'` opts out of the engine's deliver gate. `'human'` and
1053
+ // an omitted field both leave `autoDeliver` off the input — the gate is the engine's default,
1054
+ // so the wire never has to say "gate me" to be gated.
1055
+ if (b.deliverGate === 'auto')
1056
+ input.autoDeliver = true;
1058
1057
  if (b.repoRef !== undefined)
1059
1058
  input.repoRef = b.repoRef;
1060
1059
  if (b.workflow !== undefined)
@@ -1851,15 +1850,35 @@ runtime = {}) {
1851
1850
  text: z.string().min(1).max(65536),
1852
1851
  targets: z.array(z.string().min(1)).min(1).max(8).optional(),
1853
1852
  }).strict();
1853
+ // F-RECON-017: a message sent while a targeted seat is still answering the previous one is
1854
+ // REFUSED, not queued. The engine queues silently (the worker gets the text the instant the
1855
+ // previous turn's final block lands) and its reply frames carry no message correlation, so the
1856
+ // recon saw Q2's reply render under Q3's bubble. The daemon's own turn index (`chat-turns.ts`)
1857
+ // is the record: 409 `turn_in_flight` names the turn and the busy seats — a send that targets
1858
+ // only IDLE seats passes (a stalled seat never blocks a follow-up to the one that answered) —
1859
+ // and the 202 returns the `turnId` the reply frames are stamped with (`turn_id`) on `/ws`.
1854
1860
  app.post(`${V}/chats/:id/messages`, async (req, reply) => {
1855
1861
  const { id } = req.params;
1856
1862
  const parsed = ChatMessageSchema.safeParse(req.body);
1857
1863
  if (!parsed.success) {
1858
1864
  return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
1859
1865
  }
1866
+ const inFlight = chatTurns.inFlight(id, parsed.data.targets);
1867
+ if (inFlight !== null) {
1868
+ const ageS = Math.round(inFlight.ageMs / 1000);
1869
+ return reply.code(409).send({
1870
+ code: 'turn_in_flight',
1871
+ error: `${inFlight.busy.join(', ')} ${inFlight.busy.length === 1 ? 'is' : 'are'} still answering the previous ` +
1872
+ `message in this chat (turn ${inFlight.turnId}, ${ageS}s ago: “${inFlight.excerpt}”) — wait for the ` +
1873
+ `reply, or target only idle seats. Nothing was sent.`,
1874
+ chatId: id,
1875
+ turn: inFlight,
1876
+ });
1877
+ }
1860
1878
  try {
1861
1879
  const seats = await adapter.chatSend(id, parsed.data.text, parsed.data.targets);
1862
- return reply.code(202).send({ seats });
1880
+ const turn = chatTurns.begin(id, seats, parsed.data.text);
1881
+ return reply.code(202).send({ seats, ...(turn !== null ? { turnId: turn.turnId } : {}) });
1863
1882
  }
1864
1883
  catch (err) {
1865
1884
  const msg = message(err);
@@ -1924,6 +1943,7 @@ runtime = {}) {
1924
1943
  // (crew#502); the id stays parked until the engine's `chatClosed` is observed, so a delayed
1925
1944
  // close can never land on a chat that reused it (Copilot, #518).
1926
1945
  chatScopes.beginClose(id);
1946
+ chatTurns.closed(id);
1927
1947
  try {
1928
1948
  await adapter.chatClose(id);
1929
1949
  return { ok: true };