@adhdev/daemon-core 0.9.82-rc.15 → 0.9.82-rc.151

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 (182) hide show
  1. package/dist/boot/process-hardening.d.ts +50 -0
  2. package/dist/chat/source-machine.d.ts +166 -0
  3. package/dist/chat/source-resolver.d.ts +104 -0
  4. package/dist/chat/subscription-updates.d.ts +1 -0
  5. package/dist/cli-adapter-types.d.ts +5 -1
  6. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  7. package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
  8. package/dist/cli-adapters/provider-cli-adapter.d.ts +104 -63
  9. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  10. package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
  11. package/dist/commands/handler.d.ts +66 -0
  12. package/dist/commands/router.d.ts +22 -0
  13. package/dist/config/chat-history.d.ts +5 -0
  14. package/dist/config/config.d.ts +5 -0
  15. package/dist/config/mesh-config.d.ts +68 -1
  16. package/dist/git/git-commands.d.ts +5 -1
  17. package/dist/index.d.ts +20 -6
  18. package/dist/index.js +13153 -3031
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +13099 -3023
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/installer.d.ts +1 -4
  23. package/dist/ipc/local-ipc-server.d.ts +91 -0
  24. package/dist/launch.d.ts +1 -1
  25. package/dist/logging/async-batch-writer.d.ts +10 -0
  26. package/dist/mesh/beads-db.d.ts +72 -0
  27. package/dist/mesh/contracts.d.ts +164 -0
  28. package/dist/mesh/coordinator-registry.d.ts +25 -0
  29. package/dist/mesh/mesh-active-work.d.ts +90 -0
  30. package/dist/mesh/mesh-events.d.ts +77 -5
  31. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  32. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  33. package/dist/mesh/mesh-ledger.d.ts +58 -1
  34. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  35. package/dist/mesh/mesh-work-queue.d.ts +44 -5
  36. package/dist/mesh/preview-freshness.d.ts +18 -0
  37. package/dist/mesh/refine-config.d.ts +193 -0
  38. package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
  39. package/dist/providers/approval-utils.d.ts +9 -0
  40. package/dist/providers/cli-provider-instance.d.ts +6 -1
  41. package/dist/providers/contracts.d.ts +50 -5
  42. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  43. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  44. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  45. package/dist/providers/native-history/index.d.ts +11 -0
  46. package/dist/providers/provider-loader.d.ts +19 -1
  47. package/dist/providers/read-chat-contract.d.ts +29 -0
  48. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  49. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  50. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  51. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +64 -0
  52. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  53. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  54. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  55. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  56. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  57. package/dist/providers/sdk/v1/index.d.ts +30 -0
  58. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  59. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  60. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  61. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  62. package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
  63. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  64. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  65. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  66. package/dist/providers/transcript-v2.d.ts +176 -0
  67. package/dist/repo-mesh-types.d.ts +67 -0
  68. package/dist/shared-types.d.ts +12 -0
  69. package/dist/status/reporter.d.ts +2 -0
  70. package/dist/status/snapshot.d.ts +1 -0
  71. package/dist/types.d.ts +5 -0
  72. package/package.json +6 -2
  73. package/src/boot/daemon-lifecycle.ts +17 -10
  74. package/src/boot/process-hardening.ts +89 -0
  75. package/src/chat/source-machine.ts +534 -0
  76. package/src/chat/source-resolver.ts +0 -0
  77. package/src/chat/subscription-updates.ts +14 -1
  78. package/src/cli-adapter-types.d.ts +1 -0
  79. package/src/cli-adapter-types.ts +3 -1
  80. package/src/cli-adapters/cli-script-runner.ts +421 -0
  81. package/src/cli-adapters/cli-state-engine.ts +1086 -0
  82. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  83. package/src/cli-adapters/provider-cli-adapter.ts +664 -1137
  84. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  85. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  86. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  87. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  88. package/src/cli-adapters/provider-cli-shared.ts +57 -11
  89. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  90. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  91. package/src/commands/chat-commands.ts +1451 -52
  92. package/src/commands/cli-manager.ts +184 -3
  93. package/src/commands/handler.ts +547 -2
  94. package/src/commands/mesh-coordinator.ts +13 -143
  95. package/src/commands/router.ts +3273 -428
  96. package/src/config/chat-history.ts +79 -24
  97. package/src/config/config.ts +12 -0
  98. package/src/config/mesh-config.ts +249 -2
  99. package/src/config/recent-activity.ts +8 -2
  100. package/src/daemon/dev-cli-debug.ts +10 -1
  101. package/src/detection/ide-detector.ts +26 -16
  102. package/src/git/git-commands.ts +17 -5
  103. package/src/git/git-worktree.ts +8 -1
  104. package/src/index.ts +72 -5
  105. package/src/installer.d.ts +1 -1
  106. package/src/installer.ts +8 -6
  107. package/src/ipc/local-ipc-server.ts +278 -0
  108. package/src/launch.d.ts +1 -1
  109. package/src/launch.ts +37 -28
  110. package/src/logging/async-batch-writer.ts +55 -0
  111. package/src/logging/logger.ts +2 -1
  112. package/src/mesh/beads-db.ts +479 -0
  113. package/src/mesh/contracts.ts +329 -0
  114. package/src/mesh/coordinator-prompt.ts +40 -22
  115. package/src/mesh/coordinator-registry.ts +75 -0
  116. package/src/mesh/mesh-active-work.ts +437 -0
  117. package/src/mesh/mesh-events.ts +820 -61
  118. package/src/mesh/mesh-fast-forward.ts +430 -0
  119. package/src/mesh/mesh-host-ownership.ts +73 -0
  120. package/src/mesh/mesh-ledger.ts +457 -104
  121. package/src/mesh/mesh-refine-status.ts +144 -0
  122. package/src/mesh/mesh-work-queue.ts +216 -158
  123. package/src/mesh/preview-freshness.ts +118 -0
  124. package/src/mesh/refine-config.ts +366 -0
  125. package/src/mesh/worktree-bootstrap-config.ts +247 -0
  126. package/src/providers/approval-utils.ts +39 -5
  127. package/src/providers/chat-message-normalization.ts +4 -11
  128. package/src/providers/cli-provider-instance.ts +380 -44
  129. package/src/providers/contracts.ts +52 -5
  130. package/src/providers/ide-provider-instance.ts +17 -3
  131. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  132. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  133. package/src/providers/native-history/codex-cli-transcript.ts +419 -0
  134. package/src/providers/native-history/index.ts +23 -0
  135. package/src/providers/provider-loader.ts +294 -28
  136. package/src/providers/provider-schema.ts +31 -13
  137. package/src/providers/read-chat-contract.ts +76 -16
  138. package/src/providers/sdk/README.md +49 -0
  139. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  140. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  141. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  142. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +245 -0
  143. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  144. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  145. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  146. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  147. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  148. package/src/providers/sdk/v1/index.ts +152 -0
  149. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  150. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  151. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  152. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +444 -0
  153. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  154. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  155. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  156. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  157. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  158. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  159. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  160. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  161. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +119 -0
  162. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  163. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  164. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  165. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  166. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  167. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  168. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  169. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  170. package/src/providers/sdk/v1/types/common/index.ts +210 -0
  171. package/src/providers/sdk/v1/validators/index.ts +19 -0
  172. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  173. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  174. package/src/providers/transcript-v2.ts +567 -0
  175. package/src/providers/version-archive.ts +38 -20
  176. package/src/repo-mesh-types.ts +77 -0
  177. package/src/shared-types.ts +9 -0
  178. package/src/status/builders.ts +23 -6
  179. package/src/status/reporter.ts +15 -0
  180. package/src/status/snapshot.ts +35 -11
  181. package/src/system/host-memory.ts +29 -12
  182. package/src/types.ts +5 -0
@@ -0,0 +1,144 @@
1
+ import type { MeshLedgerEntry } from './mesh-ledger.js';
2
+ import type { PendingMeshCoordinatorEvent } from './mesh-events.js';
3
+ import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
4
+
5
+ export type MeshAsyncRefineJobStatus = 'accepted' | 'running' | 'completed' | 'failed';
6
+
7
+ export interface MeshAsyncRefineJobSummary extends MeshAsyncJobLifecycle {
8
+ jobId: string;
9
+ interactionId?: string;
10
+ status: MeshAsyncRefineJobStatus;
11
+ meshId?: string;
12
+ nodeId?: string;
13
+ targetNodeId?: string;
14
+ targetDaemonId?: string;
15
+ workspace?: string;
16
+ branch?: string;
17
+ into?: string;
18
+ retryOfJobId?: string;
19
+ lastEvent?: string;
20
+ lastLedgerKind?: string;
21
+ lastUpdatedAt?: string;
22
+ instruction: string;
23
+ }
24
+
25
+ function readString(value: unknown): string | undefined {
26
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
27
+ }
28
+
29
+ function readRecord(value: unknown): Record<string, unknown> | undefined {
30
+ return value && typeof value === 'object' && !Array.isArray(value)
31
+ ? value as Record<string, unknown>
32
+ : undefined;
33
+ }
34
+
35
+ function eventStatus(event: string | undefined, fallback?: string): MeshAsyncRefineJobStatus | undefined {
36
+ if (event === 'refine:accepted') return 'accepted';
37
+ if (event === 'refine:completed') return 'completed';
38
+ if (event === 'refine:failed') return 'failed';
39
+ if (fallback === 'completed' || fallback === 'failed' || fallback === 'accepted') return fallback;
40
+ return undefined;
41
+ }
42
+
43
+ function ledgerStatus(kind: string, fallback?: string): MeshAsyncRefineJobStatus {
44
+ if (kind === 'task_completed') return 'completed';
45
+ if (kind === 'task_failed') return 'failed';
46
+ if (fallback === 'accepted') return 'accepted';
47
+ return 'running';
48
+ }
49
+
50
+ function instructionForStatus(status: MeshAsyncRefineJobStatus): string {
51
+ if (status === 'accepted') return 'Refine job is accepted; wait for asyncRefineJobs or pendingCoordinatorEvents to report running/completed/failed.';
52
+ if (status === 'running') return 'Refine job is running; do not poll the ledger repeatedly. Watch asyncRefineJobs or pendingCoordinatorEvents for the terminal result.';
53
+ if (status === 'completed') return 'Refine job completed; inspect branch convergence and cleanup evidence before reporting final merge state.';
54
+ return 'Refine job failed; inspect result/finalBranchConvergenceState in mesh_task_history, fix the blocker, then rerun mesh_refine_node when ready.';
55
+ }
56
+
57
+ function mergeJob(
58
+ jobs: Map<string, MeshAsyncRefineJobSummary>,
59
+ patch: Partial<MeshAsyncRefineJobSummary> & { jobId?: string },
60
+ ): void {
61
+ const jobId = readString(patch.jobId);
62
+ if (!jobId) return;
63
+ const previous = jobs.get(jobId);
64
+ const status = patch.status || previous?.status || 'running';
65
+ const definedPatch = Object.fromEntries(
66
+ Object.entries(patch).filter(([, value]) => value !== undefined),
67
+ ) as Partial<MeshAsyncRefineJobSummary>;
68
+ jobs.set(jobId, {
69
+ ...previous,
70
+ ...definedPatch,
71
+ jobId,
72
+ status,
73
+ instruction: instructionForStatus(status),
74
+ });
75
+ }
76
+
77
+ export function buildMeshAsyncRefineJobs(args: {
78
+ meshId?: string;
79
+ ledgerEntries?: MeshLedgerEntry[];
80
+ pendingEvents?: PendingMeshCoordinatorEvent[];
81
+ }): MeshAsyncRefineJobSummary[] {
82
+ const jobs = new Map<string, MeshAsyncRefineJobSummary>();
83
+
84
+ for (const entry of args.ledgerEntries || []) {
85
+ const payload = readRecord(entry.payload);
86
+ if (payload?.source !== 'refine_mesh_node_async_job') continue;
87
+ const refineJob = readRecord(payload.refineJob);
88
+ const result = readRecord(payload.result);
89
+ const finalState = readRecord(payload.finalBranchConvergenceState) || readRecord(result?.finalBranchConvergenceState);
90
+ const jobId = readString(refineJob?.jobId);
91
+ if (!jobId) continue;
92
+ const status = ledgerStatus(entry.kind, readString(refineJob?.status));
93
+ mergeJob(jobs, {
94
+ jobId,
95
+ interactionId: readString(refineJob?.interactionId),
96
+ status,
97
+ meshId: readString(refineJob?.meshId) || args.meshId,
98
+ nodeId: readString(refineJob?.nodeId) || entry.nodeId,
99
+ targetNodeId: readString(refineJob?.nodeId) || entry.nodeId,
100
+ targetDaemonId: readString(refineJob?.targetDaemonId),
101
+ workspace: readString(refineJob?.workspace),
102
+ branch: readString(result?.branch) || readString(finalState?.branch),
103
+ into: readString(result?.into) || readString(finalState?.baseBranch),
104
+ startedAt: readString(refineJob?.startedAt),
105
+ completedAt: readString(refineJob?.completedAt),
106
+ retryOfJobId: readString(refineJob?.retryOfJobId) || readString(payload.retryOfJobId),
107
+ lastLedgerKind: entry.kind,
108
+ lastUpdatedAt: entry.timestamp,
109
+ });
110
+ }
111
+
112
+ for (const event of args.pendingEvents || []) {
113
+ const metadata = readRecord(event.metadataEvent);
114
+ if (metadata?.source !== 'refine_mesh_node_async_job') continue;
115
+ const result = readRecord(metadata.result);
116
+ const finalState = readRecord(result?.finalBranchConvergenceState);
117
+ const jobId = readString(metadata.jobId);
118
+ if (!jobId) continue;
119
+ const status = eventStatus(event.event, readString(metadata.status));
120
+ mergeJob(jobs, {
121
+ jobId,
122
+ interactionId: readString(metadata.interactionId),
123
+ ...(status ? { status } : {}),
124
+ meshId: readString(metadata.meshId) || event.meshId || args.meshId,
125
+ nodeId: readString(metadata.nodeId) || event.nodeId,
126
+ targetNodeId: readString(metadata.nodeId) || event.nodeId,
127
+ targetDaemonId: readString(metadata.targetDaemonId),
128
+ workspace: readString(metadata.workspace) || event.workspace,
129
+ branch: readString(result?.branch) || readString(finalState?.branch),
130
+ into: readString(result?.into) || readString(finalState?.baseBranch),
131
+ startedAt: readString(metadata.startedAt),
132
+ completedAt: readString(metadata.completedAt),
133
+ retryOfJobId: readString(metadata.retryOfJobId),
134
+ lastEvent: event.event,
135
+ lastUpdatedAt: new Date(event.queuedAt).toISOString(),
136
+ });
137
+ }
138
+
139
+ return Array.from(jobs.values()).sort((a, b) => {
140
+ const aTime = new Date(a.lastUpdatedAt || a.startedAt || '').getTime();
141
+ const bTime = new Date(b.lastUpdatedAt || b.startedAt || '').getTime();
142
+ return (Number.isFinite(bTime) ? bTime : 0) - (Number.isFinite(aTime) ? aTime : 0);
143
+ });
144
+ }
@@ -1,20 +1,70 @@
1
- import { existsSync, writeFileSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
1
  import { randomUUID } from 'crypto';
4
- import { getLedgerDir } from './mesh-ledger.js';
2
+ import { requireMeshHostQueueOwner } from './mesh-host-ownership.js';
3
+ import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
4
+ import { BeadsDB } from './beads-db.js';
5
5
 
6
6
  export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
7
7
  export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
8
8
  export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
9
+ export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
9
10
 
10
11
  export const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[] = ['pending', 'assigned'];
11
12
  export const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[] = ['completed', 'failed', 'cancelled'];
13
+ export const MESH_TASK_MODES: MeshTaskMode[] = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'];
14
+
15
+ export interface MeshTaskModeValidationResult {
16
+ valid: boolean;
17
+ taskMode?: MeshTaskMode;
18
+ violations: string[];
19
+ allowedOperations?: string[];
20
+ }
21
+
22
+ const LIVE_DEBUG_READONLY_FORBIDDEN: Array<{ label: string; pattern: RegExp }> = [
23
+ { label: 'source_edit', pattern: /\b(edit|modify|patch|apply\s+patch|write\s+(?:to\s+)?(?:file|source)|overwrite|delete\s+file|remove\s+file|create\s+file|touch\s+file)\b/i },
24
+ { label: 'git_mutation', pattern: /\b(?:git\s+(?:add|commit|push|reset|rebase|clean|checkout|switch|merge|tag|restore|rm|mv|stash|worktree\s+(?:add|remove|move))|push\b)/i },
25
+ { label: 'checkpoint', pattern: /\b(checkpoint|mesh_checkpoint)\b/i },
26
+ { label: 'deploy_or_version_bump', pattern: /\b(deploy|wrangler\s+deploy|version[-\s]?bump|npm\s+version|release|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i },
27
+ { label: 'destructive_shell', pattern: /\b(rm\s+-rf|mv\s+\S+\s+\S+|truncate\s|tee\s+\S+|sed\s+-i|shred\b)\b/i },
28
+ { label: 'package_install', pattern: /\b(npm\s+(?:install|i|add|link|uninstall|remove)|yarn\s+(?:add|remove|link)|pnpm\s+(?:add|remove|link)|pip\s+install|brew\s+install|apt\s+install|cargo\s+install)\b/i },
29
+ { label: 'container_mutation', pattern: /\b(docker\s+(?:build|run|exec|push|tag|rmi|rm|create|start|stop|kill)|kubectl\s+(?:apply|delete|patch|replace|create|scale))\b/i },
30
+ ];
31
+
32
+ export function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined {
33
+ if (typeof value !== 'string') return undefined;
34
+ const normalized = value.trim() as MeshTaskMode;
35
+ return (MESH_TASK_MODES as string[]).includes(normalized) ? normalized : undefined;
36
+ }
37
+
38
+ export function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult {
39
+ const taskMode = normalizeMeshTaskMode(mode);
40
+ if (!taskMode) {
41
+ return { valid: true, violations: [] };
42
+ }
43
+ if (taskMode !== 'live_debug_readonly') {
44
+ return { valid: true, taskMode, violations: [] };
45
+ }
46
+ const violations = LIVE_DEBUG_READONLY_FORBIDDEN
47
+ .filter(rule => rule.pattern.test(message || ''))
48
+ .map(rule => rule.label);
49
+ return {
50
+ valid: violations.length === 0,
51
+ taskMode,
52
+ violations,
53
+ allowedOperations: [
54
+ 'process/log/window/port/session inspection',
55
+ 'read-only filesystem listing/reading',
56
+ 'status probes and keep-running handle reporting',
57
+ 'diagnostic summaries without source edits, commits, checkpoints, pushes, deploys, resets, rebases, or destructive cleanups',
58
+ ],
59
+ };
60
+ }
12
61
 
13
62
  export interface MeshWorkQueueEntry {
14
63
  id: string;
15
64
  meshId: string;
16
65
  message: string;
17
66
  status: MeshTaskStatus;
67
+ taskMode?: MeshTaskMode;
18
68
  /** If specified, only this node can claim the task (used by legacy mesh_send_task) */
19
69
  targetNodeId?: string;
20
70
  /** If specified, only this runtime session can claim the task */
@@ -45,25 +95,20 @@ export interface MeshWorkQueueEntry {
45
95
  updatedAt: string;
46
96
  }
47
97
 
48
- function getQueuePath(meshId: string): string {
49
- const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
50
- return join(getLedgerDir(), `${safe}.queue.json`);
98
+ export interface MeshQueueMutationOptions {
99
+ ownerRole?: RepoMeshDaemonRole;
100
+ }
101
+
102
+ function withQueueLock<T>(_meshId: string, fn: () => T): T {
103
+ return BeadsDB.getInstance().transaction(fn);
51
104
  }
52
105
 
53
106
  function readQueue(meshId: string): MeshWorkQueueEntry[] {
54
- const path = getQueuePath(meshId);
55
- if (!existsSync(path)) return [];
56
- try {
57
- const content = readFileSync(path, 'utf-8');
58
- return JSON.parse(content) as MeshWorkQueueEntry[];
59
- } catch {
60
- return [];
61
- }
107
+ return BeadsDB.getInstance().getQueueEntries(meshId);
62
108
  }
63
109
 
64
110
  function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
65
- const path = getQueuePath(meshId);
66
- writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
111
+ BeadsDB.getInstance().replaceQueue(meshId, queue);
67
112
  }
68
113
 
69
114
  /**
@@ -72,21 +117,25 @@ function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
72
117
  export function enqueueTask(
73
118
  meshId: string,
74
119
  message: string,
75
- opts?: { targetNodeId?: string; targetSessionId?: string }
120
+ opts?: { targetNodeId?: string; targetSessionId?: string; taskMode?: MeshTaskMode | string } & MeshQueueMutationOptions,
76
121
  ): MeshWorkQueueEntry {
77
- const queue = readQueue(meshId);
122
+ requireMeshHostQueueOwner(opts);
123
+ const modeValidation = validateMeshTaskModeRequest(opts?.taskMode, message);
124
+ if (!modeValidation.valid) {
125
+ throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(', ')})`);
126
+ }
78
127
  const entry: MeshWorkQueueEntry = {
79
128
  id: randomUUID(),
80
129
  meshId,
81
130
  message,
82
131
  status: 'pending',
132
+ taskMode: modeValidation.taskMode,
83
133
  targetNodeId: opts?.targetNodeId,
84
134
  targetSessionId: opts?.targetSessionId,
85
135
  createdAt: new Date().toISOString(),
86
136
  updatedAt: new Date().toISOString(),
87
137
  };
88
- queue.push(entry);
89
- writeQueue(meshId, queue);
138
+ BeadsDB.getInstance().insertQueueEntry(entry);
90
139
  return entry;
91
140
  }
92
141
 
@@ -94,51 +143,18 @@ export function enqueueTask(
94
143
  * Get all tasks in the queue, optionally filtered by status.
95
144
  */
96
145
  export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }): MeshWorkQueueEntry[] {
97
- let queue = readQueue(meshId);
98
- if (opts?.status?.length) {
99
- const statuses = new Set(opts.status);
100
- queue = queue.filter(q => statuses.has(q.status));
101
- }
102
- return queue;
146
+ return BeadsDB.getInstance().getQueueEntries(meshId, opts?.status?.length ? opts.status : undefined);
147
+ }
148
+
149
+ export function getMeshQueueRevision(meshId: string): string {
150
+ return BeadsDB.getInstance().getQueueRevision(meshId);
103
151
  }
104
152
 
105
153
  /**
106
154
  * Find the next pending task that this node is allowed to claim, and mark it as assigned.
107
155
  */
108
156
  export function claimNextTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null {
109
- const queue = readQueue(meshId);
110
-
111
- // A worker must finish or fail its current queued assignment before it can
112
- // claim another one. maxParallelTasks limits total mesh concurrency; it is
113
- // not permission for one node/session to accumulate multiple assigned items.
114
- const hasActiveAssignment = queue.some(q => q.status === 'assigned' && (
115
- q.assignedSessionId === sessionId || q.assignedNodeId === nodeId
116
- ));
117
- if (hasActiveAssignment) return null;
118
-
119
- // Find highest priority task:
120
- // 1. Pending tasks explicitly targeted at this runtime session
121
- // 2. Pending tasks explicitly targeted at this node (but not another session)
122
- // 3. Pending tasks with no target node/session
123
- let targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetSessionId === sessionId);
124
- if (targetIdx === -1) {
125
- targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetNodeId === nodeId && !q.targetSessionId);
126
- }
127
- if (targetIdx === -1) {
128
- targetIdx = queue.findIndex(q => q.status === 'pending' && !q.targetNodeId && !q.targetSessionId);
129
- }
130
-
131
- if (targetIdx === -1) return null;
132
-
133
- const entry = queue[targetIdx];
134
- entry.status = 'assigned';
135
- entry.assignedNodeId = nodeId;
136
- entry.assignedSessionId = sessionId;
137
- entry.dispatchTimestamp = new Date().toISOString();
138
- entry.updatedAt = new Date().toISOString();
139
-
140
- writeQueue(meshId, queue);
141
- return entry;
157
+ return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId);
142
158
  }
143
159
 
144
160
  /**
@@ -149,15 +165,16 @@ export function updateTaskStatus(
149
165
  meshId: string,
150
166
  taskId: string,
151
167
  status: MeshTaskStatus,
168
+ opts?: MeshQueueMutationOptions,
152
169
  ): MeshWorkQueueEntry | null {
153
- const queue = readQueue(meshId);
154
- const idx = queue.findIndex(q => q.id === taskId);
155
- if (idx === -1) return null;
156
-
157
- queue[idx].status = status;
158
- queue[idx].updatedAt = new Date().toISOString();
159
- writeQueue(meshId, queue);
160
- return queue[idx];
170
+ requireMeshHostQueueOwner(opts);
171
+ return withQueueLock(meshId, () => {
172
+ const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
173
+ if (!entry) return null;
174
+ entry.status = status;
175
+ BeadsDB.getInstance().updateQueueEntry(entry);
176
+ return entry;
177
+ });
161
178
  }
162
179
 
163
180
  export function recordTaskAutoLaunch(
@@ -165,17 +182,14 @@ export function recordTaskAutoLaunch(
165
182
  taskId: string,
166
183
  autoLaunch: Omit<NonNullable<MeshWorkQueueEntry['autoLaunch']>, 'updatedAt'>,
167
184
  ): MeshWorkQueueEntry | null {
168
- const queue = readQueue(meshId);
169
- const idx = queue.findIndex(q => q.id === taskId);
170
- if (idx === -1) return null;
171
- const now = new Date().toISOString();
172
- queue[idx].autoLaunch = {
173
- ...autoLaunch,
174
- updatedAt: now,
175
- };
176
- queue[idx].updatedAt = now;
177
- writeQueue(meshId, queue);
178
- return queue[idx];
185
+ return withQueueLock(meshId, () => {
186
+ const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
187
+ if (!entry) return null;
188
+ const now = new Date().toISOString();
189
+ entry.autoLaunch = { ...autoLaunch, updatedAt: now };
190
+ BeadsDB.getInstance().updateQueueEntry(entry);
191
+ return entry;
192
+ });
179
193
  }
180
194
 
181
195
  /**
@@ -184,19 +198,19 @@ export function recordTaskAutoLaunch(
184
198
  export function cancelTask(
185
199
  meshId: string,
186
200
  taskId: string,
187
- opts?: { reason?: string },
201
+ opts?: { reason?: string } & MeshQueueMutationOptions,
188
202
  ): MeshWorkQueueEntry | null {
189
- const queue = readQueue(meshId);
190
- const idx = queue.findIndex(q => q.id === taskId);
191
- if (idx === -1) return null;
192
-
193
- const now = new Date().toISOString();
194
- queue[idx].status = 'cancelled';
195
- queue[idx].updatedAt = now;
196
- queue[idx].cancelledAt = now;
197
- if (opts?.reason) queue[idx].cancelReason = opts.reason;
198
- writeQueue(meshId, queue);
199
- return queue[idx];
203
+ requireMeshHostQueueOwner(opts);
204
+ return withQueueLock(meshId, () => {
205
+ const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
206
+ if (!entry) return null;
207
+ const now = new Date().toISOString();
208
+ entry.status = 'cancelled';
209
+ entry.cancelledAt = now;
210
+ if (opts?.reason) entry.cancelReason = opts.reason;
211
+ BeadsDB.getInstance().updateQueueEntry(entry);
212
+ return entry;
213
+ });
200
214
  }
201
215
 
202
216
  /**
@@ -212,29 +226,27 @@ export function requeueTask(
212
226
  targetSessionId?: string;
213
227
  clearTargetNode?: boolean;
214
228
  clearTargetSession?: boolean;
215
- },
229
+ } & MeshQueueMutationOptions,
216
230
  ): MeshWorkQueueEntry | null {
217
- const queue = readQueue(meshId);
218
- const idx = queue.findIndex(q => q.id === taskId);
219
- if (idx === -1) return null;
220
-
221
- const entry = queue[idx];
222
- const now = new Date().toISOString();
223
- entry.status = 'pending';
224
- delete entry.assignedNodeId;
225
- delete entry.assignedSessionId;
226
- delete entry.cancelledAt;
227
- delete entry.cancelReason;
228
- if (opts?.clearTargetNode) delete entry.targetNodeId;
229
- if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
230
- if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
231
- if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
232
- entry.updatedAt = now;
233
- entry.requeuedAt = now;
234
- entry.requeueCount = (entry.requeueCount || 0) + 1;
235
- if (opts?.reason) entry.requeueReason = opts.reason;
236
- writeQueue(meshId, queue);
237
- return entry;
231
+ requireMeshHostQueueOwner(opts);
232
+ return withQueueLock(meshId, () => {
233
+ const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
234
+ if (!entry) return null;
235
+ entry.status = 'pending';
236
+ delete entry.assignedNodeId;
237
+ delete entry.assignedSessionId;
238
+ delete entry.cancelledAt;
239
+ delete entry.cancelReason;
240
+ if (opts?.clearTargetNode) delete entry.targetNodeId;
241
+ if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
242
+ if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
243
+ if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
244
+ entry.requeuedAt = new Date().toISOString();
245
+ entry.requeueCount = (entry.requeueCount || 0) + 1;
246
+ if (opts?.reason) entry.requeueReason = opts.reason;
247
+ BeadsDB.getInstance().updateQueueEntry(entry);
248
+ return entry;
249
+ });
238
250
  }
239
251
 
240
252
  /**
@@ -244,29 +256,16 @@ export function updateSessionTaskStatus(
244
256
  meshId: string,
245
257
  sessionId: string,
246
258
  status: MeshTaskStatus,
259
+ opts?: { occurredAt?: string },
247
260
  ): MeshWorkQueueEntry | null {
248
- const queue = readQueue(meshId);
249
- // Collect all assigned tasks for this session, then pick the one with the
250
- // most recent dispatchTimestamp (or updatedAt fallback for legacy entries).
251
- // This prevents completing the wrong task when multiple tasks were assigned
252
- // to the same session in rapid succession.
253
- let bestIdx = -1;
254
- let bestTime = 0;
255
- for (let i = queue.length - 1; i >= 0; i--) {
256
- if (queue[i].assignedSessionId === sessionId && queue[i].status === 'assigned') {
257
- const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
258
- if (time > bestTime) {
259
- bestTime = time;
260
- bestIdx = i;
261
- }
262
- }
263
- }
264
- if (bestIdx === -1) return null;
265
-
266
- queue[bestIdx].status = status;
267
- queue[bestIdx].updatedAt = new Date().toISOString();
268
- writeQueue(meshId, queue);
269
- return queue[bestIdx];
261
+ return withQueueLock(meshId, () => {
262
+ const occurredAtIso = opts?.occurredAt ? new Date(opts.occurredAt).toISOString() : undefined;
263
+ const entry = BeadsDB.getInstance().findAssignedBySession(meshId, sessionId, occurredAtIso);
264
+ if (!entry) return null;
265
+ entry.status = status;
266
+ BeadsDB.getInstance().updateQueueEntry(entry);
267
+ return entry;
268
+ });
270
269
  }
271
270
 
272
271
  export interface MeshWorkQueueStats {
@@ -294,14 +293,16 @@ export interface MeshWorkQueueStats {
294
293
  * Return aggregate queue statistics for the given mesh.
295
294
  */
296
295
  export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
297
- const queue = readQueue(meshId);
298
- const pending = queue.filter(q => q.status === 'pending').length;
299
- const assigned = queue.filter(q => q.status === 'assigned').length;
300
- const completed = queue.filter(q => q.status === 'completed').length;
301
- const failed = queue.filter(q => q.status === 'failed').length;
302
- const cancelled = queue.filter(q => q.status === 'cancelled').length;
296
+ const rows = BeadsDB.getInstance().getQueueStatsByStatus(meshId);
297
+ const counts: Record<string, number> = {};
298
+ for (const r of rows) counts[r.status] = r.count;
299
+ const pending = counts['pending'] ?? 0;
300
+ const assigned = counts['assigned'] ?? 0;
301
+ const completed = counts['completed'] ?? 0;
302
+ const failed = counts['failed'] ?? 0;
303
+ const cancelled = counts['cancelled'] ?? 0;
303
304
  return {
304
- total: queue.length,
305
+ total: pending + assigned + completed + failed + cancelled,
305
306
  active: pending + assigned,
306
307
  historical: completed + failed + cancelled,
307
308
  pending,
@@ -309,22 +310,79 @@ export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
309
310
  completed,
310
311
  failed,
311
312
  cancelled,
312
- activeCounts: {
313
- pending,
314
- assigned,
315
- },
316
- historicalCounts: {
317
- completed,
318
- failed,
319
- cancelled,
320
- },
321
- activeAssignments: queue
322
- .filter(q => q.status === 'assigned')
323
- .map(q => ({
324
- id: q.id,
325
- nodeId: q.assignedNodeId,
326
- sessionId: q.assignedSessionId,
327
- message: q.message,
328
- })),
313
+ activeCounts: { pending, assigned },
314
+ historicalCounts: { completed, failed, cancelled },
315
+ activeAssignments: BeadsDB.getInstance().getActiveAssignmentDetails(meshId),
329
316
  };
330
317
  }
318
+
319
+ export function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void {
320
+ BeadsDB.getInstance().transaction(() => {
321
+ BeadsDB.getInstance().replaceQueue(meshId, queue);
322
+ });
323
+ }
324
+
325
+ export function __clearMeshQueueForTests(meshId: string): void {
326
+ BeadsDB.getInstance().deleteQueue(meshId);
327
+ }
328
+
329
+ export function __resetBeadsDBForTests(): void {
330
+ BeadsDB.resetForTests();
331
+ }
332
+
333
+ // ── Direct Dispatch Tracking ─────────────────────────────────────────────────
334
+ // Persists direct (non-queue) task dispatches so buildMeshActiveWork can read
335
+ // active work from BeadsDB instead of scanning ledger JSONL entries.
336
+
337
+ export type DirectDispatchRecord = ReturnType<BeadsDB['getActiveDirectDispatches']>[number];
338
+
339
+ export function insertDirectDispatch(
340
+ meshId: string,
341
+ data: {
342
+ taskId: string;
343
+ nodeId?: string;
344
+ sessionId?: string;
345
+ providerType?: string;
346
+ message: string;
347
+ taskMode?: string;
348
+ via: string;
349
+ dispatchedToIdleSession?: boolean;
350
+ dispatchedAt: string;
351
+ },
352
+ ): void {
353
+ try {
354
+ BeadsDB.getInstance().insertDirectDispatch({ ...data, meshId });
355
+ } catch (e: any) {
356
+ process.stderr.write(`[adhdev-mesh] insertDirectDispatch failed for task ${data.taskId}: ${e?.message || e}\n`);
357
+ }
358
+ }
359
+
360
+ export function getActiveDirectDispatches(meshId: string): DirectDispatchRecord[] {
361
+ try {
362
+ return BeadsDB.getInstance().getActiveDirectDispatches(meshId);
363
+ } catch {
364
+ return [];
365
+ }
366
+ }
367
+
368
+ export function updateDirectDispatchStatus(
369
+ meshId: string,
370
+ sessionId: string,
371
+ status: 'acked' | 'completed' | 'failed' | 'stale',
372
+ ): void {
373
+ try {
374
+ BeadsDB.getInstance().updateDirectDispatchStatus(meshId, sessionId, status);
375
+ } catch { /* best-effort */ }
376
+ }
377
+
378
+ export function cleanupTerminalDirectDispatches(olderThanMs = 7 * 24 * 60 * 60_000): void {
379
+ try {
380
+ BeadsDB.getInstance().cleanupTerminalDirectDispatches(olderThanMs);
381
+ } catch { /* best-effort */ }
382
+ }
383
+
384
+ export function markStaleDirectDispatches(meshId: string, olderThanMs = 60 * 60_000): void {
385
+ try {
386
+ BeadsDB.getInstance().markStaleDirectDispatches(meshId, olderThanMs);
387
+ } catch { /* best-effort */ }
388
+ }