sensorium-mcp 3.1.2 → 3.1.3-board.2

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 (217) hide show
  1. package/Install-Sensorium.ps1 +61 -0
  2. package/dist/board/api/api.js +61 -0
  3. package/dist/board/api/client.js +96 -0
  4. package/dist/board/components/BottomNav.js +32 -0
  5. package/dist/board/components/Card.js +44 -0
  6. package/dist/board/components/CommentComposer.js +26 -0
  7. package/dist/board/components/CommentList.js +39 -0
  8. package/dist/board/components/Dropdown.js +66 -0
  9. package/dist/board/components/Lane.js +40 -0
  10. package/dist/board/components/PriorityChip.js +17 -0
  11. package/dist/board/components/ProfileSummary.js +39 -0
  12. package/dist/board/components/StateBanner.js +22 -0
  13. package/dist/board/components/StatusChip.js +21 -0
  14. package/dist/board/components/TokenEntry.js +42 -0
  15. package/dist/board/config.js +56 -0
  16. package/dist/board/index.html +18 -0
  17. package/dist/board/main.js +37 -0
  18. package/dist/board/mock/api.mock.js +193 -0
  19. package/dist/board/mock/fixtures.js +164 -0
  20. package/dist/board/schema/policy.js +192 -0
  21. package/dist/board/state/auth.js +55 -0
  22. package/dist/board/state/useBoard.js +149 -0
  23. package/dist/board/state/useBuilder.js +98 -0
  24. package/dist/board/state/useCard.js +67 -0
  25. package/dist/board/styles/board.css +1760 -0
  26. package/dist/board/styles/tokens.css +71 -0
  27. package/dist/board/vendor/vue.esm-browser.js +9 -0
  28. package/dist/board/views/App.js +65 -0
  29. package/dist/board/views/BoardView.js +84 -0
  30. package/dist/board/views/BuilderView.js +303 -0
  31. package/dist/board/views/CardView.js +245 -0
  32. package/dist/board/views/SettingsView.js +145 -0
  33. package/dist/config/agent-settings.d.ts +2 -0
  34. package/dist/config/agent-settings.d.ts.map +1 -1
  35. package/dist/config/agent-settings.js +23 -0
  36. package/dist/config/agent-settings.js.map +1 -1
  37. package/dist/config/index.d.ts +1 -0
  38. package/dist/config/index.d.ts.map +1 -1
  39. package/dist/config/index.js +12 -0
  40. package/dist/config/index.js.map +1 -1
  41. package/dist/config/misc-settings.d.ts +14 -0
  42. package/dist/config/misc-settings.d.ts.map +1 -1
  43. package/dist/config/misc-settings.js +53 -0
  44. package/dist/config/misc-settings.js.map +1 -1
  45. package/dist/dashboard/board-static.d.ts +23 -0
  46. package/dist/dashboard/board-static.d.ts.map +1 -0
  47. package/dist/dashboard/board-static.js +131 -0
  48. package/dist/dashboard/board-static.js.map +1 -0
  49. package/dist/dashboard/routes/board.d.ts +73 -0
  50. package/dist/dashboard/routes/board.d.ts.map +1 -0
  51. package/dist/dashboard/routes/board.js +623 -0
  52. package/dist/dashboard/routes/board.js.map +1 -0
  53. package/dist/dashboard/routes/claude.d.ts.map +1 -1
  54. package/dist/dashboard/routes/claude.js +10 -1
  55. package/dist/dashboard/routes/claude.js.map +1 -1
  56. package/dist/dashboard/routes/settings.d.ts +2 -0
  57. package/dist/dashboard/routes/settings.d.ts.map +1 -1
  58. package/dist/dashboard/routes/settings.js +24 -1
  59. package/dist/dashboard/routes/settings.js.map +1 -1
  60. package/dist/dashboard/routes/threads-read.d.ts +7 -3
  61. package/dist/dashboard/routes/threads-read.d.ts.map +1 -1
  62. package/dist/dashboard/routes/threads-read.js +19 -4
  63. package/dist/dashboard/routes/threads-read.js.map +1 -1
  64. package/dist/dashboard/routes/types.d.ts +6 -0
  65. package/dist/dashboard/routes/types.d.ts.map +1 -1
  66. package/dist/dashboard/routes/types.js.map +1 -1
  67. package/dist/dashboard/routes.d.ts.map +1 -1
  68. package/dist/dashboard/routes.js +42 -1
  69. package/dist/dashboard/routes.js.map +1 -1
  70. package/dist/dashboard/spa.html +12 -12
  71. package/dist/data/board/board-schema.d.ts +22 -0
  72. package/dist/data/board/board-schema.d.ts.map +1 -0
  73. package/dist/data/board/board-schema.js +103 -0
  74. package/dist/data/board/board-schema.js.map +1 -0
  75. package/dist/data/board/board-types.d.ts +117 -0
  76. package/dist/data/board/board-types.d.ts.map +1 -0
  77. package/dist/data/board/board-types.js +9 -0
  78. package/dist/data/board/board-types.js.map +1 -0
  79. package/dist/data/board/card.repository.d.ts +64 -0
  80. package/dist/data/board/card.repository.d.ts.map +1 -0
  81. package/dist/data/board/card.repository.js +272 -0
  82. package/dist/data/board/card.repository.js.map +1 -0
  83. package/dist/data/board/comment.repository.d.ts +12 -0
  84. package/dist/data/board/comment.repository.d.ts.map +1 -0
  85. package/dist/data/board/comment.repository.js +31 -0
  86. package/dist/data/board/comment.repository.js.map +1 -0
  87. package/dist/data/board/lane.repository.d.ts +18 -0
  88. package/dist/data/board/lane.repository.d.ts.map +1 -0
  89. package/dist/data/board/lane.repository.js +44 -0
  90. package/dist/data/board/lane.repository.js.map +1 -0
  91. package/dist/data/board/profile-defaults.d.ts +19 -0
  92. package/dist/data/board/profile-defaults.d.ts.map +1 -0
  93. package/dist/data/board/profile-defaults.js +118 -0
  94. package/dist/data/board/profile-defaults.js.map +1 -0
  95. package/dist/data/memory/migration-runner.d.ts.map +1 -1
  96. package/dist/data/memory/migration-runner.js +4 -0
  97. package/dist/data/memory/migration-runner.js.map +1 -1
  98. package/dist/data/memory/narrative-store.d.ts +14 -0
  99. package/dist/data/memory/narrative-store.d.ts.map +1 -1
  100. package/dist/data/memory/narrative-store.js +36 -0
  101. package/dist/data/memory/narrative-store.js.map +1 -1
  102. package/dist/data/memory/narrative.d.ts.map +1 -1
  103. package/dist/data/memory/narrative.js +13 -1
  104. package/dist/data/memory/narrative.js.map +1 -1
  105. package/dist/data/memory/schema.d.ts.map +1 -1
  106. package/dist/data/memory/schema.js +4 -0
  107. package/dist/data/memory/schema.js.map +1 -1
  108. package/dist/index.js +84 -2
  109. package/dist/index.js.map +1 -1
  110. package/dist/integrations/openai/speech.d.ts.map +1 -1
  111. package/dist/integrations/openai/speech.js +52 -19
  112. package/dist/integrations/openai/speech.js.map +1 -1
  113. package/dist/lib/http-retry.d.ts +91 -0
  114. package/dist/lib/http-retry.d.ts.map +1 -0
  115. package/dist/lib/http-retry.js +177 -0
  116. package/dist/lib/http-retry.js.map +1 -0
  117. package/dist/lib/usage-metering/pricing.js +1 -1
  118. package/dist/lib/usage-metering/pricing.js.map +1 -1
  119. package/dist/lib/utils.d.ts +10 -0
  120. package/dist/lib/utils.d.ts.map +1 -1
  121. package/dist/lib/utils.js +22 -0
  122. package/dist/lib/utils.js.map +1 -1
  123. package/dist/server/http-server.d.ts +3 -1
  124. package/dist/server/http-server.d.ts.map +1 -1
  125. package/dist/server/http-server.js +6 -2
  126. package/dist/server/http-server.js.map +1 -1
  127. package/dist/services/board/board-signals.d.ts +25 -0
  128. package/dist/services/board/board-signals.d.ts.map +1 -0
  129. package/dist/services/board/board-signals.js +83 -0
  130. package/dist/services/board/board-signals.js.map +1 -0
  131. package/dist/services/board/card-briefing.d.ts +17 -0
  132. package/dist/services/board/card-briefing.d.ts.map +1 -0
  133. package/dist/services/board/card-briefing.js +86 -0
  134. package/dist/services/board/card-briefing.js.map +1 -0
  135. package/dist/services/board/orchestrator.service.d.ts +55 -0
  136. package/dist/services/board/orchestrator.service.d.ts.map +1 -0
  137. package/dist/services/board/orchestrator.service.js +198 -0
  138. package/dist/services/board/orchestrator.service.js.map +1 -0
  139. package/dist/services/board/release-worker.d.ts +32 -0
  140. package/dist/services/board/release-worker.d.ts.map +1 -0
  141. package/dist/services/board/release-worker.js +49 -0
  142. package/dist/services/board/release-worker.js.map +1 -0
  143. package/dist/services/board/report-capture.d.ts +48 -0
  144. package/dist/services/board/report-capture.d.ts.map +1 -0
  145. package/dist/services/board/report-capture.js +172 -0
  146. package/dist/services/board/report-capture.js.map +1 -0
  147. package/dist/services/board/worker-spawn.d.ts +30 -0
  148. package/dist/services/board/worker-spawn.d.ts.map +1 -0
  149. package/dist/services/board/worker-spawn.js +90 -0
  150. package/dist/services/board/worker-spawn.js.map +1 -0
  151. package/dist/services/broker-client.d.ts +30 -0
  152. package/dist/services/broker-client.d.ts.map +1 -1
  153. package/dist/services/broker-client.js +23 -0
  154. package/dist/services/broker-client.js.map +1 -1
  155. package/dist/services/codex-spawn.service.d.ts +2 -2
  156. package/dist/services/codex-spawn.service.d.ts.map +1 -1
  157. package/dist/services/codex-spawn.service.js +54 -54
  158. package/dist/services/codex-spawn.service.js.map +1 -1
  159. package/dist/services/http-retry.d.ts +1 -90
  160. package/dist/services/http-retry.d.ts.map +1 -1
  161. package/dist/services/http-retry.js +1 -176
  162. package/dist/services/http-retry.js.map +1 -1
  163. package/dist/services/mcp-config.service.d.ts.map +1 -1
  164. package/dist/services/mcp-config.service.js +5 -1
  165. package/dist/services/mcp-config.service.js.map +1 -1
  166. package/dist/services/passive-notice.service.d.ts +39 -0
  167. package/dist/services/passive-notice.service.d.ts.map +1 -0
  168. package/dist/services/passive-notice.service.js +274 -0
  169. package/dist/services/passive-notice.service.js.map +1 -0
  170. package/dist/services/spawn-common.d.ts.map +1 -1
  171. package/dist/services/spawn-common.js +14 -1
  172. package/dist/services/spawn-common.js.map +1 -1
  173. package/dist/services/telegram.d.ts +7 -5
  174. package/dist/services/telegram.d.ts.map +1 -1
  175. package/dist/services/telegram.js +18 -13
  176. package/dist/services/telegram.js.map +1 -1
  177. package/dist/services/usage/usage-inbox-notifier.d.ts +50 -0
  178. package/dist/services/usage/usage-inbox-notifier.d.ts.map +1 -0
  179. package/dist/services/usage/usage-inbox-notifier.js +118 -0
  180. package/dist/services/usage/usage-inbox-notifier.js.map +1 -0
  181. package/dist/services/usage/usage-poller.service.d.ts +41 -0
  182. package/dist/services/usage/usage-poller.service.d.ts.map +1 -0
  183. package/dist/services/usage/usage-poller.service.js +66 -0
  184. package/dist/services/usage/usage-poller.service.js.map +1 -0
  185. package/dist/services/usage/usage-snapshot-provider.d.ts +29 -0
  186. package/dist/services/usage/usage-snapshot-provider.d.ts.map +1 -0
  187. package/dist/services/usage/usage-snapshot-provider.js +59 -0
  188. package/dist/services/usage/usage-snapshot-provider.js.map +1 -0
  189. package/dist/services/usage/usage-types.d.ts +23 -0
  190. package/dist/services/usage/usage-types.d.ts.map +1 -0
  191. package/dist/services/usage/usage-types.js +14 -0
  192. package/dist/services/usage/usage-types.js.map +1 -0
  193. package/dist/services/worker-cleanup.service.d.ts +6 -1
  194. package/dist/services/worker-cleanup.service.d.ts.map +1 -1
  195. package/dist/services/worker-cleanup.service.js +123 -10
  196. package/dist/services/worker-cleanup.service.js.map +1 -1
  197. package/dist/tools/delegate-tool.d.ts.map +1 -1
  198. package/dist/tools/delegate-tool.js +34 -2
  199. package/dist/tools/delegate-tool.js.map +1 -1
  200. package/dist/tools/utility-tools.d.ts.map +1 -1
  201. package/dist/tools/utility-tools.js +7 -3
  202. package/dist/tools/utility-tools.js.map +1 -1
  203. package/dist/tools/wait/consolidation-strategy.d.ts +42 -0
  204. package/dist/tools/wait/consolidation-strategy.d.ts.map +1 -0
  205. package/dist/tools/wait/consolidation-strategy.js +50 -0
  206. package/dist/tools/wait/consolidation-strategy.js.map +1 -0
  207. package/dist/tools/wait/drive-handler.d.ts +5 -7
  208. package/dist/tools/wait/drive-handler.d.ts.map +1 -1
  209. package/dist/tools/wait/drive-handler.js +30 -48
  210. package/dist/tools/wait/drive-handler.js.map +1 -1
  211. package/dist/tools/wait/message-delivery.d.ts.map +1 -1
  212. package/dist/tools/wait/message-delivery.js +13 -0
  213. package/dist/tools/wait/message-delivery.js.map +1 -1
  214. package/package.json +2 -2
  215. package/templates/atomic-build-spec.default.md +57 -0
  216. package/templates/buildless-vue-preview.default.md +47 -0
  217. package/templates/phased-build.default.md +59 -0
@@ -41,11 +41,25 @@ $ConfigFile = Join-Path $DataDir "install.config.json"
41
41
  $StartupDir = [Environment]::GetFolderPath("Startup")
42
42
  $StartupLauncher = Join-Path $StartupDir "SensoriumSupervisor.cmd"
43
43
 
44
+ # Isolated MCP runtime: a PRIVATE npm cache + a repo-independent cwd for the MCP,
45
+ # both living under $DataDir (never a dev checkout). This severs the shared-cache /
46
+ # shared-cwd coupling that lets a worker's `npm ci`/build in a dev repo interfere
47
+ # with (and kill) the live MCP — WITHOUT pinning the version, so `npx ...@latest`
48
+ # still auto-updates the MCP on each restart. npx also stages its packages in
49
+ # <cache>/_npx, so a private npm cache isolates npx resolution too.
50
+ $McpRuntimeRoot = Join-Path $DataDir "mcp-runtime"
51
+ $McpRuntimeCache = Join-Path $McpRuntimeRoot "npm-cache"
52
+
44
53
  # ── Config File ──────────────────────────────────────────────────────────────
45
54
  $Defaults = @{
46
55
  SecureVaultProfile = "SENSORIUM"
47
56
  UpdateMode = "production"
48
57
  MCPStartCommand = "npx -y sensorium-mcp@latest"
58
+ # Opt-in runtime isolation. When "true", the MCP keeps its `npx ...@latest`
59
+ # start command (so auto-update still works) but is given its OWN npm cache
60
+ # ($McpRuntimeCache) and a repo-independent cwd ($McpRuntimeRoot), decoupled
61
+ # from any dev checkout's shared npm cache / cwd. "false" = legacy behaviour.
62
+ IsolateMcpRuntime = "false"
49
63
  }
50
64
 
51
65
  function Load-Config {
@@ -78,6 +92,7 @@ $Config = Load-Config
78
92
  $SecureVaultProfile = $Config["SecureVaultProfile"]
79
93
  $UpdateMode = $Config["UpdateMode"]
80
94
  $EffectiveMCPStartCommand = $Config["MCPStartCommand"]
95
+ $IsolateMcpRuntime = $Config["IsolateMcpRuntime"] -eq "true"
81
96
 
82
97
  # Resolve the supervisor release channel from the same tag the node server
83
98
  # follows: the "@<tag>" in MCPStartCommand. Only @refactor uses the branch
@@ -306,10 +321,44 @@ function Test-SecureVaultAvailable {
306
321
  catch { return $false }
307
322
  }
308
323
 
324
+ function Initialize-IsolatedMcpRuntime {
325
+ <#
326
+ Prepares the PRIVATE MCP runtime area: a dedicated npm cache and a
327
+ repo-independent cwd, both under $McpRuntimeRoot. It does NOT install or pin
328
+ any package — the MCP keeps its `npx ...@latest` start command so auto-update
329
+ still works; isolation comes purely from the private cache + cwd handed to the
330
+ supervisor (and thus to npx) via Start-Supervisor. Idempotent; only creates dirs.
331
+ #>
332
+ Write-Host "Isolated MCP runtime enabled — preparing private cache + cwd..." -ForegroundColor Cyan
333
+ Write-Host " Runtime cwd : $McpRuntimeRoot"
334
+ Write-Host " npm cache : $McpRuntimeCache (npx stages under $McpRuntimeCache\_npx)"
335
+
336
+ try {
337
+ New-Item -ItemType Directory -Path $McpRuntimeRoot -Force | Out-Null
338
+ New-Item -ItemType Directory -Path $McpRuntimeCache -Force | Out-Null
339
+ }
340
+ catch {
341
+ Write-Host "[WARN] Could not create isolated runtime dirs: $($_.Exception.Message) — keeping legacy behaviour." -ForegroundColor Yellow
342
+ return $false
343
+ }
344
+ return $true
345
+ }
346
+
309
347
  function Start-Supervisor {
310
348
  $env:SUPERVISOR_UPDATE_MODE = $UpdateMode
311
349
  $env:MCP_START_COMMAND = $EffectiveMCPStartCommand
312
350
 
351
+ # In isolated mode, hand the supervisor a private npm cache and a repo-independent
352
+ # cwd. The supervisor inherits its whole environment and passes it to the MCP, so
353
+ # NPM_CONFIG_CACHE reaches the `npx ...@latest` child automatically (isolating both
354
+ # the npm cache and npx's <cache>\_npx staging); MCP_WORKING_DIR is consumed by the
355
+ # supervisor to set the MCP's spawn cwd. The `@latest` command is unchanged, so
356
+ # auto-update keeps working — only WHERE npx resolves/caches changes.
357
+ if ($IsolateMcpRuntime) {
358
+ $env:NPM_CONFIG_CACHE = $McpRuntimeCache
359
+ $env:MCP_WORKING_DIR = $McpRuntimeRoot
360
+ }
361
+
313
362
  $useSecureVault = (-not [string]::IsNullOrWhiteSpace($SecureVaultProfile)) -and (Test-SecureVaultAvailable)
314
363
 
315
364
  if ($useSecureVault) {
@@ -414,6 +463,7 @@ else {
414
463
  Write-Host "SecureVault profile: $(if ([string]::IsNullOrWhiteSpace($SecureVaultProfile)) { '<disabled>' } else { $SecureVaultProfile })"
415
464
  Write-Host "Watcher mode: $UpdateMode"
416
465
  Write-Host "MCP start command: $EffectiveMCPStartCommand"
466
+ Write-Host "MCP runtime isolation: $(if ($IsolateMcpRuntime) { 'ENABLED' } else { 'disabled (legacy npx)' })"
417
467
  Write-Host "Binary : $Binary"
418
468
  Write-Host ""
419
469
 
@@ -428,6 +478,17 @@ New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
428
478
  # Step 3 — fetch release metadata and download only when needed
429
479
  Sync-ReleaseBinary
430
480
 
481
+ # Step 3b — prepare the isolated MCP runtime area (opt-in): a private npm cache +
482
+ # repo-independent cwd. The `npx ...@latest` command is unchanged (auto-update
483
+ # preserved). On any failure this disables the isolation env in Start-Supervisor,
484
+ # so the install degrades safely to legacy behaviour.
485
+ if ($IsolateMcpRuntime) {
486
+ if (-not (Initialize-IsolatedMcpRuntime)) {
487
+ $IsolateMcpRuntime = $false
488
+ Write-Host "[WARN] Isolation disabled for this run — using the shared npm cache / inherited cwd." -ForegroundColor Yellow
489
+ }
490
+ }
491
+
431
492
  # Step 4 — install launcher and start
432
493
  Install-StartupLauncher
433
494
  Start-Supervisor
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Data-adapter switch. ❌ FROZEN SEAM — the ONLY seam is data.
3
+ *
4
+ * MODE decides the implementation ONCE, lazily:
5
+ * - preview → mock/api.mock.js is DYNAMICALLY imported (so not a single mock
6
+ * byte is loaded in production).
7
+ * - production → api/client.js (real fetch).
8
+ *
9
+ * Every method returns a Promise and forwards to the resolved adapter, so callers
10
+ * (state/ composables) never know or care which one is live.
11
+ *
12
+ * @typedef {import('./contract.js').BoardApi} BoardApi
13
+ */
14
+
15
+ import { MODE } from "../config.js";
16
+
17
+ let implPromise = null;
18
+
19
+ /** @returns {Promise<BoardApi>} */
20
+ function load() {
21
+ if (!implPromise) {
22
+ implPromise =
23
+ MODE === "preview"
24
+ ? import("../mock/api.mock.js").then((m) => m.mockApi)
25
+ : import("./client.js").then((m) => m.client);
26
+ }
27
+ return implPromise;
28
+ }
29
+
30
+ const METHODS = [
31
+ "listCards",
32
+ "getCard",
33
+ "createCard",
34
+ "updateCard",
35
+ "deleteCard",
36
+ "stopCard",
37
+ "addComment",
38
+ "listLanes",
39
+ "getProfiles",
40
+ "threadRunning",
41
+ // Card-builder "How" schema + presets. Preview is served by mock/api.mock.js
42
+ // (from the editable schema/policy.js). TODO(P1): api/client.js must implement
43
+ // getPolicySchema()/getPresets() against the real board API before production
44
+ // uses the schema-driven builder; until then production calls reject and
45
+ // useBuilder() degrades to a schema-less builder (no crash).
46
+ "getPolicySchema",
47
+ "getPresets",
48
+ // Lane management (Settings). Preview served by the mock; TODO(P1): implement
49
+ // in api/client.js before production exposes lane CRUD.
50
+ "createLane",
51
+ "updateLane",
52
+ "deleteLane",
53
+ ];
54
+
55
+ /** @type {BoardApi} */
56
+ const api = /** @type {any} */ ({});
57
+ for (const name of METHODS) {
58
+ api[name] = (...args) => load().then((impl) => impl[name](...args));
59
+ }
60
+
61
+ export { api };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Production data adapter. ❌ FROZEN SEAM.
3
+ *
4
+ * Real `fetch` to the board API. Root-relative `/api/...` paths (never a host or
5
+ * port) so the app works unchanged behind the Cloudflare Tunnel. Bearer token is
6
+ * injected from state/auth.js. This is the ONLY module that talks to the network.
7
+ *
8
+ * @typedef {import('./contract.js').BoardApi} BoardApi
9
+ * @typedef {import('./contract.js').BoardCard} BoardCard
10
+ */
11
+
12
+ import { API_BASE } from "../config.js";
13
+ import { getToken } from "../state/auth.js";
14
+
15
+ /**
16
+ * @param {string} method
17
+ * @param {string} path
18
+ * @param {unknown} [body]
19
+ */
20
+ async function req(method, path, body) {
21
+ const headers = {};
22
+ const token = getToken();
23
+ if (token) headers.Authorization = `Bearer ${token}`;
24
+ if (body !== undefined) headers["Content-Type"] = "application/json";
25
+
26
+ const res = await fetch(`${API_BASE}${path}`, {
27
+ method,
28
+ headers,
29
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
30
+ });
31
+
32
+ const text = await res.text();
33
+ let data = null;
34
+ if (text) {
35
+ try {
36
+ data = JSON.parse(text);
37
+ } catch {
38
+ data = { error: text };
39
+ }
40
+ }
41
+ if (!res.ok) {
42
+ const msg = (data && data.error) || `${res.status} ${res.statusText}`;
43
+ const err = new Error(msg);
44
+ err.status = res.status;
45
+ throw err;
46
+ }
47
+ return data;
48
+ }
49
+
50
+ /** @type {BoardApi} */
51
+ export const client = {
52
+ listCards({ includeArchived = false, lane } = {}) {
53
+ const p = new URLSearchParams();
54
+ if (includeArchived) p.set("includeArchived", "1");
55
+ if (lane != null) p.set("lane", String(lane));
56
+ const qs = p.toString();
57
+ return req("GET", `/api/board/cards${qs ? `?${qs}` : ""}`).then((d) => d.cards);
58
+ },
59
+ getCard(id) {
60
+ return req("GET", `/api/board/cards/${id}`);
61
+ },
62
+ createCard(card) {
63
+ return req("POST", "/api/board/cards", card).then((d) => d.card);
64
+ },
65
+ updateCard(id, patch) {
66
+ return req("PATCH", `/api/board/cards/${id}`, patch).then((d) => d.card);
67
+ },
68
+ deleteCard(id) {
69
+ return req("DELETE", `/api/board/cards/${id}`).then(() => undefined);
70
+ },
71
+ stopCard(id) {
72
+ return req("POST", `/api/board/cards/${id}/stop`, {}).then((d) => d.card);
73
+ },
74
+ addComment(id, content) {
75
+ return req("POST", `/api/board/cards/${id}/comments`, { content }).then((d) => d.comment);
76
+ },
77
+ listLanes() {
78
+ return req("GET", "/api/board/lanes").then((d) => d.lanes);
79
+ },
80
+ getProfiles() {
81
+ return req("GET", "/api/board/profiles").then((d) => d.profiles);
82
+ },
83
+ threadRunning(threadId) {
84
+ return req("GET", `/api/threads/${threadId}/running`).then((d) => !!d.running);
85
+ },
86
+ // TODO(P1): serve the card-builder "How" schema/presets from the real board
87
+ // API (e.g. GET /api/board/policy). Until then these reject explicitly so the
88
+ // production failure is legible; useBuilder() swallows the rejection and falls
89
+ // back to a schema-less builder (no crash).
90
+ getPolicySchema() {
91
+ return Promise.reject(new Error("getPolicySchema not implemented — P1"));
92
+ },
93
+ getPresets() {
94
+ return Promise.reject(new Error("getPresets not implemented — P1"));
95
+ },
96
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Bottom navigation — ✅ EDITABLE. Pure: `active` in, `navigate` out.
3
+ * Thumb-reach primary nav (board / new / settings).
4
+ */
5
+
6
+ export default {
7
+ name: "BottomNav",
8
+ props: { active: { type: String, default: "board" } },
9
+ emits: ["navigate"],
10
+ setup(props, { emit }) {
11
+ const items = [
12
+ { name: "board", to: "/board", label: "Board", icon: "▦" },
13
+ { name: "settings", to: "/settings", label: "Settings", icon: "⚙" },
14
+ ];
15
+ return { items, emit };
16
+ },
17
+ template: `
18
+ <nav class="bottom-nav">
19
+ <button
20
+ v-for="it in items"
21
+ :key="it.name"
22
+ type="button"
23
+ class="bottom-nav__item"
24
+ :class="{ 'is-active': active === it.name }"
25
+ @click="emit('navigate', it.to)"
26
+ >
27
+ <span class="bottom-nav__icon" aria-hidden="true">{{ it.icon }}</span>
28
+ <span class="bottom-nav__label">{{ it.label }}</span>
29
+ </button>
30
+ </nav>
31
+ `,
32
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Card tile — ✅ EDITABLE. Pure: `card` + `laneName` in, `open` out.
3
+ * Dispatch design: floating dark card — title, then chip row with priority,
4
+ * type, live worker pulse / blocked / archived.
5
+ */
6
+
7
+ import StatusChip from "./StatusChip.js";
8
+ import PriorityChip from "./PriorityChip.js";
9
+
10
+ export default {
11
+ name: "Card",
12
+ components: { StatusChip, PriorityChip },
13
+ props: {
14
+ card: { type: Object, required: true },
15
+ laneName: { type: Function, default: (id) => `Thread ${id}` },
16
+ },
17
+ emits: ["open"],
18
+ setup(props, { emit }) {
19
+ function elapsed() {
20
+ if (!props.card.startedAt) return "";
21
+ const mins = Math.max(0, Math.round((Date.now() - new Date(props.card.startedAt)) / 60000));
22
+ return mins >= 60 ? Math.floor(mins / 60) + "h " + (mins % 60) + "m" : mins + "m";
23
+ }
24
+ return { emit, elapsed };
25
+ },
26
+ template: `
27
+ <article class="card" role="listitem" tabindex="0"
28
+ @click="emit('open', card)"
29
+ @keydown.enter="emit('open', card)">
30
+ <h3 class="card__title">{{ card.title }}</h3>
31
+ <p v-if="card.description" class="card__desc">{{ card.description }}</p>
32
+ <div class="card__foot">
33
+ <PriorityChip :priority="card.priority" />
34
+ <span class="chip chip--muted">{{ card.type }}</span>
35
+ <span v-if="card.status === 'in_progress'" class="chip chip--live">
36
+ <span class="dot dot--pulse"></span>working<template v-if="card.startedAt"> · {{ elapsed() }}</template>
37
+ </span>
38
+ <span v-else-if="card.blockedReason" class="chip chip--warn">blocked</span>
39
+ <span v-if="card.archivedAt" class="chip chip--muted">archived</span>
40
+ <span class="card__lane">{{ laneName(card.laneThreadId) }}</span>
41
+ </div>
42
+ </article>
43
+ `,
44
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Comment composer — ✅ EDITABLE. Pure: emits `submit(text)`; clears on send.
3
+ */
4
+
5
+ import { ref } from "../vendor/vue.esm-browser.js";
6
+
7
+ export default {
8
+ name: "CommentComposer",
9
+ emits: ["submit"],
10
+ setup(props, { emit }) {
11
+ const text = ref("");
12
+ function send() {
13
+ const t = text.value.trim();
14
+ if (!t) return;
15
+ emit("submit", t);
16
+ text.value = "";
17
+ }
18
+ return { text, send };
19
+ },
20
+ template: `
21
+ <form class="composer" @submit.prevent="send">
22
+ <textarea class="input input--area" v-model="text" rows="2" placeholder="Add a comment…"></textarea>
23
+ <button class="btn btn--primary" type="submit" :disabled="!text.trim()">Comment</button>
24
+ </form>
25
+ `,
26
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Comment history — ✅ EDITABLE. Pure: `comments` in.
3
+ * Dispatch design: activity feed — avatar tile + floating bubble per entry.
4
+ */
5
+
6
+ const AVATARS = { worker: "⚙", operator: "Y", board: "◆" };
7
+
8
+ export default {
9
+ name: "CommentList",
10
+ props: { comments: { type: Array, default: () => [] } },
11
+ setup() {
12
+ function when(iso) {
13
+ try {
14
+ return new Date(iso).toLocaleString();
15
+ } catch {
16
+ return iso;
17
+ }
18
+ }
19
+ function avatar(author) {
20
+ return AVATARS[author] || "•";
21
+ }
22
+ return { when, avatar };
23
+ },
24
+ template: `
25
+ <ul class="comments">
26
+ <li v-for="c in comments" :key="c.id" class="comment" :class="'comment--' + c.author">
27
+ <span class="comment__avatar" aria-hidden="true">{{ avatar(c.author) }}</span>
28
+ <div class="comment__bubble">
29
+ <div class="comment__meta">
30
+ <span class="comment__author">{{ c.author }}</span>
31
+ <time class="comment__time">{{ when(c.createdAt) }}</time>
32
+ </div>
33
+ <p class="comment__body">{{ c.content }}</p>
34
+ </div>
35
+ </li>
36
+ <li v-if="!comments.length" class="comment comment--empty">No comments yet.</li>
37
+ </ul>
38
+ `,
39
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Custom dropdown — ✅ EDITABLE. Replaces native <select> (default styles are
3
+ * not overridable cross-platform). Pure: modelValue in, update:modelValue out.
4
+ * Options: array of strings or {value,label}.
5
+ */
6
+
7
+ import { computed, onMounted, onUnmounted, ref } from "../vendor/vue.esm-browser.js";
8
+
9
+ export default {
10
+ name: "Dropdown",
11
+ props: {
12
+ modelValue: { default: null },
13
+ options: { type: Array, default: () => [] },
14
+ placeholder: { type: String, default: "Select…" },
15
+ },
16
+ emits: ["update:modelValue"],
17
+ setup(props, { emit }) {
18
+ const open = ref(false);
19
+ const rootEl = ref(null);
20
+ const norm = computed(() =>
21
+ props.options.map((o) => (o && typeof o === "object" ? o : { value: o, label: String(o) }))
22
+ );
23
+ const current = computed(() => norm.value.find((o) => o.value === props.modelValue));
24
+ function pick(o) {
25
+ emit("update:modelValue", o.value);
26
+ open.value = false;
27
+ }
28
+ function onDoc(e) {
29
+ if (rootEl.value && !rootEl.value.contains(e.target)) open.value = false;
30
+ }
31
+ function onKey(e) {
32
+ if (e.key === "Escape") open.value = false;
33
+ }
34
+ onMounted(() => {
35
+ document.addEventListener("click", onDoc, true);
36
+ document.addEventListener("keydown", onKey);
37
+ });
38
+ onUnmounted(() => {
39
+ document.removeEventListener("click", onDoc, true);
40
+ document.removeEventListener("keydown", onKey);
41
+ });
42
+ return { open, rootEl, norm, current, pick };
43
+ },
44
+ template: `
45
+ <div class="dropdown" ref="rootEl">
46
+ <button type="button" class="dropdown__btn" :class="{ 'is-open': open }"
47
+ aria-haspopup="listbox" :aria-expanded="open" @click="open = !open">
48
+ <span class="dropdown__value" :class="{ 'is-placeholder': !current }">
49
+ {{ current ? current.label : placeholder }}
50
+ </span>
51
+ <span class="dropdown__chevron" aria-hidden="true">▾</span>
52
+ </button>
53
+ <ul v-if="open" class="dropdown__menu" role="listbox">
54
+ <li v-for="o in norm" :key="String(o.value)">
55
+ <button type="button" class="dropdown__opt" role="option"
56
+ :aria-selected="o.value === modelValue"
57
+ :class="{ 'is-selected': o.value === modelValue }"
58
+ @click="pick(o)">
59
+ <span class="dropdown__optlabel">{{ o.label }}</span>
60
+ <span v-if="o.value === modelValue" class="dropdown__check">✓</span>
61
+ </button>
62
+ </li>
63
+ </ul>
64
+ </div>
65
+ `,
66
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Kanban column — ✅ EDITABLE. Pure: title/status/cards/laneName in, `open` out.
3
+ * Dispatch design: transparent column, colored status dot + name + count pill.
4
+ */
5
+
6
+ import Card from "./Card.js";
7
+
8
+ export default {
9
+ name: "Lane",
10
+ components: { Card },
11
+ props: {
12
+ title: { type: String, required: true },
13
+ status: { type: String, required: true },
14
+ cards: { type: Array, default: () => [] },
15
+ laneName: { type: Function, default: (id) => `Thread ${id}` },
16
+ },
17
+ emits: ["open"],
18
+ setup(props, { emit }) {
19
+ return { emit };
20
+ },
21
+ template: `
22
+ <section class="lane" :class="'lane--' + status" role="listitem">
23
+ <header class="lane__head">
24
+ <span class="lane__dot"></span>
25
+ <h2 class="lane__title">{{ title }}</h2>
26
+ <span class="lane__count">{{ cards.length }}</span>
27
+ </header>
28
+ <div class="lane__body">
29
+ <Card
30
+ v-for="c in cards"
31
+ :key="c.cardId"
32
+ :card="c"
33
+ :lane-name="laneName"
34
+ @open="emit('open', $event)"
35
+ />
36
+ <p v-if="!cards.length" class="lane__empty">Nothing here</p>
37
+ </div>
38
+ </section>
39
+ `,
40
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Priority chip — ✅ EDITABLE. Pure: `priority` (int) in.
3
+ * Higher int = higher urgency (matches dispatch ORDER BY priority DESC).
4
+ * Dispatch design: tinted pill — Low / Medium / High / Urgent.
5
+ */
6
+
7
+ const LABELS = ["Low", "Medium", "High", "Urgent"];
8
+
9
+ export default {
10
+ name: "PriorityChip",
11
+ props: { priority: { type: Number, default: 0 } },
12
+ setup(props) {
13
+ const level = () => Math.max(0, Math.min(3, props.priority));
14
+ return { level, LABELS };
15
+ },
16
+ template: `<span class="chip chip--prio" :class="'chip--prio-' + level()">{{ LABELS[level()] }}</span>`,
17
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Execution-profile summary — ✅ EDITABLE. Pure: `profile` in.
3
+ * Renders the set flags as readable chips; nothing set → a muted note.
4
+ */
5
+
6
+ import { computed } from "../vendor/vue.esm-browser.js";
7
+
8
+ const FLAG_LABELS = {
9
+ createBranch: "branch",
10
+ runTests: "tests",
11
+ build: "build",
12
+ openPr: "PR",
13
+ push: "push",
14
+ expertReview: "expert review",
15
+ operatorReview: "operator review",
16
+ };
17
+
18
+ export default {
19
+ name: "ProfileSummary",
20
+ props: { profile: { type: Object, default: () => ({}) } },
21
+ setup(props) {
22
+ const items = computed(() => {
23
+ const p = props.profile || {};
24
+ const out = [];
25
+ if (p.agentType) out.push(p.agentType);
26
+ for (const [k, label] of Object.entries(FLAG_LABELS)) if (p[k]) out.push(label);
27
+ if (p.ciCheck && p.ciCheck !== "none") out.push("ci:" + p.ciCheck);
28
+ if (Array.isArray(p.skills) && p.skills.length) out.push(p.skills.length + " skill(s)");
29
+ return out;
30
+ });
31
+ return { items };
32
+ },
33
+ template: `
34
+ <div class="profile">
35
+ <span v-for="(it, i) in items" :key="i" class="chip chip--live">{{ it }}</span>
36
+ <span v-if="!items.length" class="profile__empty">Default profile (no flags set).</span>
37
+ </div>
38
+ `,
39
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Loading / error banner — ✅ EDITABLE. Pure: `loading`/`error` in, `retry` out.
3
+ */
4
+
5
+ export default {
6
+ name: "StateBanner",
7
+ props: {
8
+ loading: { type: Boolean, default: false },
9
+ error: { type: [String, null], default: null },
10
+ },
11
+ emits: ["retry"],
12
+ setup(props, { emit }) {
13
+ return { emit };
14
+ },
15
+ template: `
16
+ <div v-if="error" class="notice notice--error">
17
+ <span>{{ error }}</span>
18
+ <button class="btn btn--ghost btn--sm" type="button" @click="emit('retry')">Retry</button>
19
+ </div>
20
+ <div v-else-if="loading" class="notice notice--info">Loading…</div>
21
+ `,
22
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Status chip — ✅ EDITABLE. Pure: `status` in.
3
+ * Dispatch design: tinted pill in the status accent color.
4
+ */
5
+
6
+ const LABELS = {
7
+ backlog: "Backlog",
8
+ in_progress: "In progress",
9
+ decision_needed: "Decision needed",
10
+ blocked: "Blocked",
11
+ done: "Done",
12
+ };
13
+
14
+ export default {
15
+ name: "StatusChip",
16
+ props: { status: { type: String, required: true } },
17
+ setup(props) {
18
+ return { LABELS };
19
+ },
20
+ template: `<span class="chip chip--status" :class="'chip--status-' + status">{{ LABELS[status] || status }}</span>`,
21
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Token-entry gate — ✅ EDITABLE. Shown in production when no token is stored.
3
+ * Uses useAuth() to persist; no fetch here.
4
+ */
5
+
6
+ import { ref } from "../vendor/vue.esm-browser.js";
7
+ import { useAuth } from "../state/auth.js";
8
+
9
+ export default {
10
+ name: "TokenEntry",
11
+ setup() {
12
+ const auth = useAuth();
13
+ const value = ref("");
14
+ function save() {
15
+ if (value.value.trim()) auth.setToken(value.value.trim());
16
+ }
17
+ return { value, save };
18
+ },
19
+ template: `
20
+ <section class="token-gate">
21
+ <div class="token-gate__card">
22
+ <div class="token-gate__logo"><span class="app-bar__dot"></span></div>
23
+ <h1 class="token-gate__title">Dispatch</h1>
24
+ <p class="token-gate__sub">Enter the board API token to continue.</p>
25
+ <form @submit.prevent="save">
26
+ <input
27
+ class="input"
28
+ type="password"
29
+ v-model="value"
30
+ placeholder="Bearer token"
31
+ autocomplete="off"
32
+ autofocus
33
+ />
34
+ <button class="btn btn--primary token-gate__btn" type="submit" :disabled="!value.trim()">
35
+ Continue
36
+ </button>
37
+ </form>
38
+ <p class="token-gate__hint">The token is stored only in this browser.</p>
39
+ </div>
40
+ </section>
41
+ `,
42
+ };