wicked-crew 0.7.29 → 0.7.30

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 (97) hide show
  1. package/README.md +4 -1
  2. package/dist/api/chat-scope.d.ts.map +1 -1
  3. package/dist/api/chat-scope.js +5 -1
  4. package/dist/api/chat-scope.js.map +1 -1
  5. package/dist/api/governance-health.d.ts +25 -17
  6. package/dist/api/governance-health.d.ts.map +1 -1
  7. package/dist/api/governance-health.js +40 -9
  8. package/dist/api/governance-health.js.map +1 -1
  9. package/dist/api/routes.d.ts +22 -0
  10. package/dist/api/routes.d.ts.map +1 -1
  11. package/dist/api/routes.js +88 -34
  12. package/dist/api/routes.js.map +1 -1
  13. package/dist/api/seat-health.d.ts +38 -0
  14. package/dist/api/seat-health.d.ts.map +1 -1
  15. package/dist/api/seat-health.js +63 -0
  16. package/dist/api/seat-health.js.map +1 -1
  17. package/dist/api/seat-standing.d.ts +101 -0
  18. package/dist/api/seat-standing.d.ts.map +1 -0
  19. package/dist/api/seat-standing.js +138 -0
  20. package/dist/api/seat-standing.js.map +1 -0
  21. package/dist/api/server.d.ts.map +1 -1
  22. package/dist/api/server.js +17 -0
  23. package/dist/api/server.js.map +1 -1
  24. package/dist/cli/index.js +37 -5
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/core/adapter.d.ts +10 -0
  27. package/dist/core/adapter.d.ts.map +1 -1
  28. package/dist/core/adapter.js +26 -1
  29. package/dist/core/adapter.js.map +1 -1
  30. package/dist/core/bridge-path.d.ts +21 -0
  31. package/dist/core/bridge-path.d.ts.map +1 -1
  32. package/dist/core/bridge-path.js +37 -0
  33. package/dist/core/bridge-path.js.map +1 -1
  34. package/dist/core/bridge-reaper.d.ts.map +1 -1
  35. package/dist/core/bridge-reaper.js +7 -3
  36. package/dist/core/bridge-reaper.js.map +1 -1
  37. package/dist/core/governance-store.d.ts +22 -0
  38. package/dist/core/governance-store.d.ts.map +1 -1
  39. package/dist/core/governance-store.js +7 -0
  40. package/dist/core/governance-store.js.map +1 -1
  41. package/dist/interactive/bridge-pool.d.ts +29 -24
  42. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  43. package/dist/interactive/bridge-pool.js +65 -3
  44. package/dist/interactive/bridge-pool.js.map +1 -1
  45. package/dist/interactive/chat-events.d.ts.map +1 -1
  46. package/dist/interactive/chat-events.js +5 -2
  47. package/dist/interactive/chat-events.js.map +1 -1
  48. package/dist/interactive/council-outcome.d.ts +28 -0
  49. package/dist/interactive/council-outcome.d.ts.map +1 -0
  50. package/dist/interactive/council-outcome.js +57 -0
  51. package/dist/interactive/council-outcome.js.map +1 -0
  52. package/dist/interactive/demo-events.d.ts.map +1 -1
  53. package/dist/interactive/demo-events.js +5 -2
  54. package/dist/interactive/demo-events.js.map +1 -1
  55. package/dist/interactive/doc-delete-routes.d.ts.map +1 -1
  56. package/dist/interactive/doc-delete-routes.js +3 -2
  57. package/dist/interactive/doc-delete-routes.js.map +1 -1
  58. package/dist/interactive/draft-events.d.ts.map +1 -1
  59. package/dist/interactive/draft-events.js +5 -2
  60. package/dist/interactive/draft-events.js.map +1 -1
  61. package/dist/interactive/edit-events.d.ts.map +1 -1
  62. package/dist/interactive/edit-events.js +5 -2
  63. package/dist/interactive/edit-events.js.map +1 -1
  64. package/dist/projects/auto-refresh.d.ts +54 -0
  65. package/dist/projects/auto-refresh.d.ts.map +1 -0
  66. package/dist/projects/auto-refresh.js +76 -0
  67. package/dist/projects/auto-refresh.js.map +1 -0
  68. package/dist/projects/graph.d.ts +15 -2
  69. package/dist/projects/graph.d.ts.map +1 -1
  70. package/dist/projects/graph.js +18 -8
  71. package/dist/projects/graph.js.map +1 -1
  72. package/dist/projects/routes.d.ts.map +1 -1
  73. package/dist/projects/routes.js +12 -2
  74. package/dist/projects/routes.js.map +1 -1
  75. package/dist/projects/state-home.d.ts +6 -0
  76. package/dist/projects/state-home.d.ts.map +1 -1
  77. package/dist/projects/state-home.js +9 -1
  78. package/dist/projects/state-home.js.map +1 -1
  79. package/dist/skills/guards.d.ts +17 -6
  80. package/dist/skills/guards.d.ts.map +1 -1
  81. package/dist/skills/guards.js +42 -15
  82. package/dist/skills/guards.js.map +1 -1
  83. package/dist/skills/refs.d.ts +181 -17
  84. package/dist/skills/refs.d.ts.map +1 -1
  85. package/dist/skills/refs.js +345 -26
  86. package/dist/skills/refs.js.map +1 -1
  87. package/dist/skills/store.d.ts +22 -1
  88. package/dist/skills/store.d.ts.map +1 -1
  89. package/dist/skills/store.js +161 -23
  90. package/dist/skills/store.js.map +1 -1
  91. package/dist/studio/assets/index-CWUS9Ump.js +542 -0
  92. package/dist/studio/assets/{index-CK1uJWs2.css → index-_ydd-jKd.css} +1 -1
  93. package/dist/studio/index.html +2 -2
  94. package/dist/studio/testid-inventory.json +359 -4
  95. package/endpoint-manifest.json +8 -3
  96. package/package.json +4 -4
  97. package/dist/studio/assets/index-CeJTutxP.js +0 -540
@@ -17,6 +17,7 @@ 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
21
  import { ChatScopeIndex, chatScopeDeps, prepareChatScratch, removeChatScratch, resolveChatScope, } from './chat-scope.js';
21
22
  import { allowedRootsFor, isInsideRoot, openWithSystemDefault } from './open-path.js';
22
23
  import { InvalidDiffBaseError, NotARegularFileError, UnresolvableDiffBaseError, readFileCapped, worktreeDiff, } from './run-files.js';
@@ -73,13 +74,6 @@ const STATUS_ORDER = {
73
74
  function sortActionableFirst(views) {
74
75
  return [...views].sort((a, b) => (STATUS_ORDER[a.session.status] ?? 9) - (STATUS_ORDER[b.session.status] ?? 9));
75
76
  }
76
- /**
77
- * `codeGraphDb(repo)` for a repo surface, or `null` after answering **503** — a CURRENT engine that
78
- * resolved no repo-graph root (`code_graph_root_unresolvable`, wicked-core#406) is a daemon-
79
- * environment fault, not a bad request and not Fastify's generic 500. The mapping is the shared
80
- * `codeGraphErrorStatus`; anything it does not classify rethrows unchanged (the stale-addon error
81
- * keeps its pre-existing shape here).
82
- */
83
77
  function codeGraphDbOr503(repo, reply) {
84
78
  try {
85
79
  return codeGraphDb(repo);
@@ -707,16 +701,29 @@ runtime = {}) {
707
701
  // gains `signed_in`: the cheap file/env presence heuristic, computed against the LIVE
708
702
  // `WICKED_WORKER_HOME` env — the same value the engine reads at the next worker spawn, kept
709
703
  // current by `applyWorkerConfigRoot` at boot and on every settings change.
710
- app.get(`${V}/roster`, async () => {
704
+ //
705
+ // Each seat ALSO carries its STANDING (F-2R2-009): `auth` re-reads `signed_in` for what it means
706
+ // (a `signed_out` codex is benched on its first ballot; a `not_required` opencode answers on its
707
+ // free tier), and `council_eligible` says what a council would do with the seat as far as the
708
+ // daemon can tell. `POST /chats` seats its defaults through the SAME standing, so the roster and
709
+ // the chat never disagree about a seat.
710
+ const rosterWithStanding = () => {
711
711
  const workerRoot = process.env['WICKED_WORKER_HOME'];
712
- return {
713
- roster: CoreAdapter.roster().map((seat) => ({
712
+ return CoreAdapter.roster().map((seat) => {
713
+ const key = String(seat.key);
714
+ const health = seatHealth.healthFor(key);
715
+ const signed = signedIn(key, workerRoot === '' ? undefined : workerRoot);
716
+ return {
714
717
  ...seat,
715
- health: seatHealth.healthFor(String(seat.key)),
716
- signed_in: signedIn(String(seat.key), workerRoot === '' ? undefined : workerRoot),
717
- })),
718
- };
719
- });
718
+ health,
719
+ signed_in: signed,
720
+ // The bench is THIS daemon's council evidence (councilSeatFailed, bounded window) — the
721
+ // prediction learns from what the engine actually did with the seat (#533 review, F-1).
722
+ ...seatStanding(seat, signed, health, seatHealth.councilBenchFor(key)),
723
+ };
724
+ });
725
+ };
726
+ app.get(`${V}/roster`, async () => ({ roster: rosterWithStanding() }));
720
727
  // Open a file/folder with the OS default application (crew#273) — the studio Files tab's
721
728
  // click-to-open. The open MUST happen daemon-side (the SPA cannot spawn a process), which is
722
729
  // why the path is validated first: absolute, and inside one of the caller-visible roots — the
@@ -1559,26 +1566,43 @@ runtime = {}) {
1559
1566
  .code(500)
1560
1567
  .send({ error: `cannot prepare the chat's scratch root ${scope.cwd}: ${message(err)}` });
1561
1568
  }
1562
- // The default seats of a SCOPED chat are pre-filtered by admissibility (independent review,
1563
- // W5): the engine admits a seat to a scoped chat only when its ACP adapter asks permissions
1564
- // (`acp_input_governance`) or its record arms the kernel write floor (`os_sandbox`) — with the
1565
- // default roster that is claude and opencode — so a default project-scoped open does not show
1566
- // four refused seats. Explicit `clis` are passed through as asked; the engine refuses per seat
1567
- // with the reason.
1569
+ // The DEFAULT seats are the roster seats the daemon's own admission admits (F-2R2-007,
1570
+ // F-2R2-009): the seat's auth standing (a signed-out seat is refused up front, with the
1571
+ // reason, instead of failing its first turn — the same predicate `GET /roster` reports as
1572
+ // `council_eligible`) and, for a SCOPED chat, the engine's rule restated (only a seat whose
1573
+ // ACP adapter asks permissions or whose record arms the kernel write floor can be held to
1574
+ // read-only roots — with the default roster that is claude and opencode). Every seat dropped
1575
+ // here is NAMED on the response (`refused`) and in the thread (`chatSeatRefused`), so a
1576
+ // person can see why pi is missing. Explicit `clis` are passed through as asked; the engine
1577
+ // refuses per seat with its reason, which is copied into `refused` too.
1568
1578
  const scoped = scope.kind !== 'none';
1569
- const roster = CoreAdapter.roster();
1570
- const clis = b.clis ??
1571
- roster
1572
- .filter((s) => !scoped || s.acp?.acp_input_governance === true || s.acp?.os_sandbox === true)
1573
- .map((s) => s.key)
1574
- .filter((k) => typeof k === 'string');
1579
+ const refused = [];
1580
+ let clis;
1581
+ if (b.clis !== undefined) {
1582
+ clis = b.clis;
1583
+ }
1584
+ else {
1585
+ clis = [];
1586
+ for (const seat of rosterWithStanding()) {
1587
+ const key = String(seat.key);
1588
+ const admission = chatSeatAdmission(seat, seat.auth ?? 'unknown', scoped);
1589
+ if (admission.ok)
1590
+ clis.push(key);
1591
+ else
1592
+ refused.push({ cliKey: key, reason: admission.reason });
1593
+ }
1594
+ }
1575
1595
  if (clis.length === 0) {
1576
1596
  chatScopes.release(chatId, token);
1577
1597
  removeChatScratch(scope.cwd, chatScopes.base);
1578
1598
  return reply.code(409).send({
1579
- error: 'no seat in the roster can be held to a scoped chat (an ACP adapter admitted to input ' +
1580
- 'governance, or `os_sandbox = true` on its [cli.acp] record); open the chat unscoped, or ' +
1581
- 'name seats with `clis`',
1599
+ error: (scoped
1600
+ ? 'no seat in the roster can be held to a scoped chat (an ACP adapter admitted to input ' +
1601
+ 'governance, or `os_sandbox = true` on its [cli.acp] record) and take a turn; open the ' +
1602
+ 'chat unscoped, sign a seat in, or name seats with `clis`'
1603
+ : 'no seat in the roster can take a turn (every seat is signed out); sign a seat in from ' +
1604
+ 'the System page, or name seats with `clis`') + ' — see refused',
1605
+ refused,
1582
1606
  });
1583
1607
  }
1584
1608
  try {
@@ -1586,6 +1610,11 @@ runtime = {}) {
1586
1610
  codeGraphDb: engine.codeGraphDb,
1587
1611
  readRoots: engine.readRoots,
1588
1612
  });
1613
+ // A REQUESTED seat the engine refused joins the same list, with the engine's reason.
1614
+ for (const s of seats) {
1615
+ if (!s.ok)
1616
+ refused.push({ cliKey: s.cliKey, reason: s.error ?? 'the engine refused the seat' });
1617
+ }
1589
1618
  // Nothing warmed (independent review, W1): the engine holds no pool row and has dropped the
1590
1619
  // scope itself (no `chatClosed` will come) — report the PER-SEAT reasons, never an
1591
1620
  // engine-version guess; the root goes, the id is free again.
@@ -1595,6 +1624,7 @@ runtime = {}) {
1595
1624
  return reply.code(409).send({
1596
1625
  error: `chat ${chatId}: no seat warmed (${seats.length} failed) — see seats`,
1597
1626
  seats,
1627
+ refused,
1598
1628
  });
1599
1629
  }
1600
1630
  // Honest scope (Copilot, #518): a SCOPED chat is a promise — the roots are read-only, the
@@ -1663,10 +1693,23 @@ runtime = {}) {
1663
1693
  error: `chat ${chatId} was closed while it was being opened; open it again`,
1664
1694
  });
1665
1695
  }
1696
+ // The thread learns of every refused seat the way it learns of everything else — a frame
1697
+ // on /ws — AFTER the scope is published, so a reader never sees a refusal for a chat it
1698
+ // cannot yet look up.
1699
+ for (const r of refused) {
1700
+ runtime.broadcast?.({
1701
+ type: 'chatSeatRefused',
1702
+ chat: chatId,
1703
+ cliKey: r.cliKey,
1704
+ reason: r.reason,
1705
+ ...(b.projectId !== undefined ? { project_id: b.projectId } : {}),
1706
+ });
1707
+ }
1666
1708
  return reply.code(201).send({
1667
1709
  chatId,
1668
1710
  seats,
1669
1711
  scope,
1712
+ refused,
1670
1713
  ...(projectAttachError !== undefined ? { projectAttachError } : {}),
1671
1714
  });
1672
1715
  }
@@ -2791,7 +2834,7 @@ runtime = {}) {
2791
2834
  return reply.code(404).send({ error: 'Requirement not found' });
2792
2835
  return { requirement: detail };
2793
2836
  });
2794
- app.get(`${V}/repos/:id/graph`, async (req, reply) => {
2837
+ app.get(`${V}/repos/:id/graph`, { config: { manifest: { responseType: 'RepoGraphResponse', statusCodes: [200, 404, 500, 503] } } }, async (req, reply) => {
2795
2838
  const { id } = req.params;
2796
2839
  const repos = await adapter.listRepos();
2797
2840
  const repo = repos.find((r) => r.id === id);
@@ -2801,7 +2844,17 @@ runtime = {}) {
2801
2844
  if (dbPath === null)
2802
2845
  return reply;
2803
2846
  if (!existsSync(dbPath)) {
2804
- return reply.send({ graph: null });
2847
+ // `graph: null` alone cannot tell "not indexed" from "empty" (F-2R2-005): say WHY, with the
2848
+ // same finding text the repos wire carries when the engine has one.
2849
+ const finding = (repo.findings ?? []).find((f) => f.code === 'in_tree_code_graph_ignored' || f.code === 'code_graph_root_unresolvable');
2850
+ const unbuilt = {
2851
+ graph: null,
2852
+ reason: finding?.message ??
2853
+ `no code graph has been built for '${repo.name}' yet (nothing at ${dbPath}) — run onboarding ` +
2854
+ `(POST /api/v1/repos/${repo.id}/onboard) to index it`,
2855
+ ...(finding !== undefined ? { finding } : {}),
2856
+ };
2857
+ return reply.send(unbuilt);
2805
2858
  }
2806
2859
  try {
2807
2860
  const settings = await adapter.getSettings();
@@ -2822,13 +2875,14 @@ runtime = {}) {
2822
2875
  });
2823
2876
  const raw = JSON.parse(stdout);
2824
2877
  const fileCount = new Set(raw.nodes.map((n) => n.file)).size;
2825
- return reply.send({
2878
+ const built = {
2826
2879
  graph: {
2827
2880
  nodes: raw.nodes,
2828
2881
  edges: raw.edges,
2829
2882
  stats: { nodeCount: raw.nodes.length, edgeCount: raw.edges.length, fileCount },
2830
2883
  },
2831
- });
2884
+ };
2885
+ return reply.send(built);
2832
2886
  }
2833
2887
  catch (err) {
2834
2888
  return reply.code(500).send({ error: message(err) });