@adhdev/daemon-core 0.9.82-rc.136 → 0.9.82-rc.138

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 (63) hide show
  1. package/dist/chat/source-machine.d.ts +166 -0
  2. package/dist/chat/source-resolver.d.ts +104 -0
  3. package/dist/cli-adapters/cli-script-runner.d.ts +45 -0
  4. package/dist/cli-adapters/cli-state-engine.d.ts +169 -0
  5. package/dist/cli-adapters/provider-cli-adapter.d.ts +72 -74
  6. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  7. package/dist/cli-adapters/provider-cli-shared.d.ts +5 -0
  8. package/dist/config/chat-history.d.ts +1 -0
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +3507 -2288
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +3515 -2301
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/mesh/beads-db.d.ts +54 -0
  15. package/dist/mesh/contracts.d.ts +164 -0
  16. package/dist/mesh/mesh-active-work.d.ts +7 -1
  17. package/dist/mesh/mesh-events.d.ts +10 -4
  18. package/dist/mesh/mesh-ledger.d.ts +21 -1
  19. package/dist/mesh/mesh-refine-status.d.ts +2 -3
  20. package/dist/mesh/mesh-work-queue.d.ts +17 -0
  21. package/dist/mesh/worktree-bootstrap-config.d.ts +2 -4
  22. package/dist/providers/contracts.d.ts +19 -0
  23. package/dist/providers/read-chat-contract.d.ts +29 -0
  24. package/dist/providers/transcript-v2.d.ts +176 -0
  25. package/dist/repo-mesh-types.d.ts +5 -0
  26. package/dist/shared-types.d.ts +7 -0
  27. package/dist/status/snapshot.d.ts +1 -0
  28. package/dist/types.d.ts +5 -0
  29. package/package.json +1 -1
  30. package/src/chat/source-machine.ts +534 -0
  31. package/src/chat/source-resolver.ts +0 -0
  32. package/src/chat/subscription-updates.ts +9 -0
  33. package/src/cli-adapters/cli-script-runner.ts +145 -0
  34. package/src/cli-adapters/cli-state-engine.ts +1054 -0
  35. package/src/cli-adapters/provider-cli-adapter.d.ts +0 -1
  36. package/src/cli-adapters/provider-cli-adapter.ts +413 -1399
  37. package/src/cli-adapters/provider-cli-parse.ts +3 -0
  38. package/src/cli-adapters/provider-cli-shared.ts +17 -1
  39. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  40. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  41. package/src/commands/chat-commands.ts +715 -368
  42. package/src/commands/router.ts +22 -2
  43. package/src/config/chat-history.ts +43 -16
  44. package/src/git/git-worktree.ts +8 -1
  45. package/src/index.ts +3 -2
  46. package/src/mesh/beads-db.ts +305 -2
  47. package/src/mesh/contracts.ts +329 -0
  48. package/src/mesh/coordinator-prompt.ts +12 -17
  49. package/src/mesh/mesh-active-work.ts +162 -59
  50. package/src/mesh/mesh-events.ts +198 -53
  51. package/src/mesh/mesh-ledger.ts +321 -105
  52. package/src/mesh/mesh-refine-status.ts +2 -3
  53. package/src/mesh/mesh-work-queue.ts +116 -120
  54. package/src/mesh/worktree-bootstrap-config.ts +17 -4
  55. package/src/providers/contracts.ts +19 -0
  56. package/src/providers/provider-loader.ts +21 -7
  57. package/src/providers/provider-schema.ts +12 -0
  58. package/src/providers/read-chat-contract.ts +74 -14
  59. package/src/providers/transcript-v2.ts +567 -0
  60. package/src/repo-mesh-types.ts +10 -0
  61. package/src/shared-types.ts +7 -0
  62. package/src/status/snapshot.ts +35 -11
  63. package/src/types.ts +5 -0
@@ -2,17 +2,71 @@ import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
2
2
  export declare class BeadsDB {
3
3
  private static instance;
4
4
  private readonly db;
5
+ private readonly dbPath;
5
6
  private readonly migratedMeshIds;
7
+ private fingerprintSweepCounter;
8
+ private walWriteCounter;
9
+ private static readonly WAL_CHECK_INTERVAL;
10
+ private static readonly WAL_MAX_BYTES;
6
11
  private constructor();
7
12
  static getInstance(): BeadsDB;
8
13
  static resetForTests(): void;
9
14
  close(): void;
10
15
  transaction<T>(fn: () => T): T;
11
16
  private migrate;
17
+ hasCompletionFingerprint(fingerprint: string): boolean;
18
+ recordCompletionFingerprint(fingerprint: string, ttlMs: number): void;
19
+ sweepExpiredFingerprints(): void;
20
+ private maybeCheckpointWal;
12
21
  private ensureLegacyQueueMigrated;
13
22
  getQueueEntries(meshId: string, statuses?: MeshTaskStatus[]): MeshWorkQueueEntry[];
14
23
  getQueueRevision(meshId: string): string;
15
24
  replaceQueue(meshId: string, queue: MeshWorkQueueEntry[]): void;
16
25
  deleteQueue(meshId: string): void;
26
+ insertQueueEntry(entry: MeshWorkQueueEntry): void;
27
+ updateQueueEntry(entry: MeshWorkQueueEntry): void;
28
+ findQueueEntryById(meshId: string, id: string): MeshWorkQueueEntry | null;
29
+ hasActiveAssignment(meshId: string, sessionId: string, nodeId: string): boolean;
30
+ claimNextQueueTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null;
31
+ getQueueStatsByStatus(meshId: string): {
32
+ status: string;
33
+ count: number;
34
+ }[];
35
+ getActiveAssignmentDetails(meshId: string): Array<{
36
+ id: string;
37
+ nodeId?: string;
38
+ sessionId?: string;
39
+ message: string;
40
+ }>;
41
+ findAssignedBySession(meshId: string, sessionId: string, occurredAtIso?: string): MeshWorkQueueEntry | null;
17
42
  private toRow;
43
+ insertDirectDispatch(entry: {
44
+ taskId: string;
45
+ meshId: string;
46
+ nodeId?: string;
47
+ sessionId?: string;
48
+ providerType?: string;
49
+ message: string;
50
+ taskMode?: string;
51
+ via: string;
52
+ dispatchedToIdleSession?: boolean;
53
+ dispatchedAt: string;
54
+ }): void;
55
+ getActiveDirectDispatches(meshId: string): Array<{
56
+ taskId: string;
57
+ meshId: string;
58
+ nodeId: string | null;
59
+ sessionId: string | null;
60
+ providerType: string | null;
61
+ message: string;
62
+ taskMode: string | null;
63
+ via: string;
64
+ status: string;
65
+ dispatchedToIdleSession: boolean;
66
+ dispatchedAt: string;
67
+ updatedAt: string;
68
+ }>;
69
+ updateDirectDispatchStatus(meshId: string, sessionId: string, status: 'acked' | 'completed' | 'failed' | 'stale'): void;
70
+ cleanupTerminalDirectDispatches(olderThanMs: number): void;
71
+ markStaleDirectDispatches(meshId: string, olderThanMs: number): void;
18
72
  }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Mesh contract v2 — first-class identity, scopes, and protocol version.
3
+ *
4
+ * Replaces the implicit conventions that grew across mesh-events.ts,
5
+ * mesh-work-queue.ts, mesh-ledger.ts, and mesh-tools.ts where the same
6
+ * concept (a coordinator, a session, a task status) appeared in different
7
+ * shapes per file. The audit found:
8
+ *
9
+ * - PendingMeshCoordinatorEvent had no targetCoordinatorDaemonId. All
10
+ * events were broadcast to every coordinator that drained them. Two
11
+ * coordinators sharing a mesh would each receive every event,
12
+ * leading to duplicate completion handling and missed targeting.
13
+ * - drainPendingMeshCoordinatorEvents accepted only meshId. The caller's
14
+ * coordinator identity was never available, so per-coordinator
15
+ * routing was impossible by construction.
16
+ * - Session identifier keys diverged across stores: targetSessionId /
17
+ * assignedSessionId / instanceId / runtimeSessionId / providerSessionId.
18
+ * resolveEventSessionId() tried four fallbacks per call.
19
+ * - No protocol version on the JSONL ledger or BeadsDB. Schema
20
+ * evolutions had no guard rail.
21
+ * - mesh_reconcile_ledger existed as a routine recovery tool, not as
22
+ * an incident-response escape hatch. That itself signals the routing
23
+ * layer cannot be trusted.
24
+ *
25
+ * This module introduces the types; the actual wiring lands in B2 (data
26
+ * model + 3-way transactional store), B3 (MCP layer enforcement), and B4
27
+ * (frontend consumption). B1 is intentionally non-breaking — it adds the
28
+ * types and leaves runtime behaviour unchanged.
29
+ */
30
+ /** Provider type identifier (e.g. 'claude-cli', 'codex-cli', 'roo-code').
31
+ * Free-form string; no shared enum exists in daemon-core yet. */
32
+ type ProviderType = string;
33
+ export declare const MESH_PROTOCOL_VERSION_V1: "1.0";
34
+ export declare const MESH_PROTOCOL_VERSION_V2: "2.0";
35
+ export type MeshProtocolVersion = typeof MESH_PROTOCOL_VERSION_V1 | typeof MESH_PROTOCOL_VERSION_V2;
36
+ export declare const SUPPORTED_MESH_PROTOCOL_VERSIONS: readonly MeshProtocolVersion[];
37
+ export declare function isSupportedMeshProtocolVersion(value: unknown): value is MeshProtocolVersion;
38
+ /**
39
+ * Identity of a mesh coordinator. Required (non-optional) on every dispatch
40
+ * and drain operation in v2 — opaque/missing identity is the audit's
41
+ * smoking gun for routing failures and is structurally banned by the v2
42
+ * types.
43
+ *
44
+ * - daemonId: the machineId of the daemon hosting the coordinator. Stable
45
+ * across coordinator restarts on the same machine.
46
+ * - coordinatorRunId: a UUID generated when the coordinator process starts.
47
+ * Stable for the coordinator's lifetime, fresh on restart. Required so
48
+ * two coordinators on the same daemon (one CLI, one MCP) can be told
49
+ * apart without conflating their drains.
50
+ * - sessionId: optional CLI coordinator session identifier (instanceId).
51
+ * Present when the coordinator is itself a CLI session, absent for
52
+ * pure MCP coordinators.
53
+ */
54
+ export interface CoordinatorIdentity {
55
+ readonly daemonId: string;
56
+ readonly coordinatorRunId: string;
57
+ readonly sessionId?: string;
58
+ }
59
+ export declare function coordinatorIdentityEquals(a: CoordinatorIdentity, b: CoordinatorIdentity): boolean;
60
+ export declare function coordinatorIdentityKey(identity: CoordinatorIdentity): string;
61
+ /**
62
+ * Unified mesh session identifier. v1 referred to "the session" with five
63
+ * different field names depending on which store you were reading; v2
64
+ * collapses them into one explicit handle that carries every disambiguator
65
+ * a consumer might need.
66
+ *
67
+ * - nodeId: the mesh node this session belongs to (FK into mesh node table).
68
+ * - sessionId: provider-instance identifier (the runtime session id).
69
+ * - providerType: which provider category/type the session runs (e.g.
70
+ * 'claude-cli', 'codex-cli', 'roo-code').
71
+ * - coordinatorDaemonId: which coordinator dispatched the work that
72
+ * spawned this session. Used for completion event routing.
73
+ * - assignedAt: ms epoch when the session was bound to its current task.
74
+ */
75
+ export interface MeshSessionHandle {
76
+ readonly nodeId: string;
77
+ readonly sessionId: string;
78
+ readonly providerType: ProviderType;
79
+ readonly coordinatorDaemonId: string;
80
+ readonly assignedAt: number;
81
+ }
82
+ export declare function meshSessionHandleKey(handle: MeshSessionHandle): string;
83
+ /**
84
+ * Single canonical task status enum. v1 had at least three overlapping
85
+ * sets (queue / ledger / direct-dispatch). v2 consumers import from here.
86
+ */
87
+ export declare const MESH_TASK_STATUSES: readonly ["pending", "assigned", "in_progress", "completed", "failed", "cancelled"];
88
+ export type MeshTaskStatus = typeof MESH_TASK_STATUSES[number];
89
+ export declare function isMeshTaskStatus(value: unknown): value is MeshTaskStatus;
90
+ /**
91
+ * Explicit scope for pending coordinator events. v1 had no scope: every
92
+ * event was broadcast to every drainer. v2 forces producers to declare
93
+ * intent.
94
+ *
95
+ * - 'unicast': delivered to exactly one coordinator (intendedFor). Other
96
+ * coordinators' drains skip it.
97
+ * - 'broadcast': delivered to every coordinator on the mesh. Used for
98
+ * system-wide signals (e.g. mesh-wide policy changes). The v1 default
99
+ * becomes explicit here so audit tools can flag unintended broadcasts.
100
+ * - 'system': delivered to the daemon-level handler, not coordinators.
101
+ * Reserved for infrastructure events that no coordinator should see
102
+ * (e.g. ledger reconciliation outcomes).
103
+ */
104
+ export declare const MESH_EVENT_SCOPES: readonly ["unicast", "broadcast", "system"];
105
+ export type MeshEventScope = typeof MESH_EVENT_SCOPES[number];
106
+ export declare function isMeshEventScope(value: unknown): value is MeshEventScope;
107
+ /**
108
+ * v2 shape of a pending coordinator event. Strict supersets of the v1
109
+ * shape — every v1 field is preserved so existing readers do not break;
110
+ * v2 fields (scope, dispatchedBy, intendedFor, protocolVersion) are
111
+ * additive and consulted by v2-aware drainers only.
112
+ *
113
+ * Mixed v1/v2 events coexist during the rollout window. B2 fully cuts
114
+ * over once every coordinator drain is v2-aware.
115
+ */
116
+ export interface PendingMeshCoordinatorEventV2 {
117
+ readonly event: string;
118
+ readonly meshId: string;
119
+ readonly nodeLabel: string;
120
+ readonly nodeId?: string;
121
+ readonly workspace?: string;
122
+ readonly metadataEvent: Record<string, unknown>;
123
+ readonly coordinatorMessage?: string;
124
+ readonly queuedAt: number;
125
+ readonly protocolVersion: MeshProtocolVersion;
126
+ readonly scope: MeshEventScope;
127
+ readonly dispatchedBy: CoordinatorIdentity;
128
+ /**
129
+ * Required when scope === 'unicast', forbidden otherwise. Drainers MUST
130
+ * skip unicast events whose intendedFor does not equal their own identity.
131
+ */
132
+ readonly intendedFor?: CoordinatorIdentity;
133
+ }
134
+ /**
135
+ * v2 ledger entry additions. The originatingCoordinator field is the
136
+ * source of truth that lets completion events route back to the
137
+ * coordinator that dispatched the task. v1's "worker settings carried it
138
+ * if it happened to be set" approach is replaced.
139
+ */
140
+ export interface MeshLedgerOriginatingCoordinatorV2 {
141
+ readonly originatingCoordinator: CoordinatorIdentity;
142
+ readonly protocolVersion: MeshProtocolVersion;
143
+ }
144
+ export declare class MeshContractViolationError extends Error {
145
+ readonly violationPath: string;
146
+ readonly protocolVersion: MeshProtocolVersion;
147
+ constructor(protocolVersion: MeshProtocolVersion, violationPath: string, detail: string);
148
+ }
149
+ export declare function assertCoordinatorIdentity(raw: unknown, path: string): CoordinatorIdentity;
150
+ export declare function assertPendingMeshCoordinatorEventV2(raw: unknown, path?: string): PendingMeshCoordinatorEventV2;
151
+ /**
152
+ * Decide whether a v2 pending event should be delivered to the given drainer.
153
+ * Centralised so every drain implementation uses the same rule.
154
+ *
155
+ * - 'broadcast': always delivered.
156
+ * - 'system': never delivered to coordinators (system handler only).
157
+ * - 'unicast': delivered iff intendedFor matches drainer identity.
158
+ *
159
+ * v1 events (no scope / no protocolVersion) are treated as broadcast for
160
+ * backward compatibility during rollout; B3 tightens this so v1 events
161
+ * are quarantined to a dedicated drain endpoint instead.
162
+ */
163
+ export declare function shouldDeliverPendingEventToCoordinator(event: PendingMeshCoordinatorEventV2, drainer: CoordinatorIdentity): boolean;
164
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { MeshLedgerEntry } from './mesh-ledger.js';
2
- import type { MeshWorkQueueEntry } from './mesh-work-queue.js';
2
+ import type { MeshWorkQueueEntry, DirectDispatchRecord } from './mesh-work-queue.js';
3
3
  export type MeshActiveWorkSource = 'queue' | 'direct';
4
4
  export type MeshActiveWorkStatus = 'pending' | 'assigned' | 'generating' | 'idle' | 'failed' | 'awaiting_approval';
5
5
  export interface MeshActiveWorkRecord {
@@ -64,6 +64,12 @@ export interface BuildMeshActiveWorkOptions {
64
64
  meshId: string;
65
65
  queue?: MeshWorkQueueEntry[];
66
66
  ledgerEntries?: MeshLedgerEntry[];
67
+ /**
68
+ * Active direct dispatches from BeadsDB. When provided, these are used instead of
69
+ * scanning ledger entries for direct dispatches — eliminates the O(n_ledger) scan.
70
+ * Falls back to ledger scanning when not provided.
71
+ */
72
+ directDispatches?: DirectDispatchRecord[];
67
73
  nodes?: any[];
68
74
  now?: number;
69
75
  /** Include terminal direct rows (idle/failed) for handoff/recent-work surfaces. Defaults false. */
@@ -8,14 +8,20 @@ export interface PendingMeshCoordinatorEvent {
8
8
  metadataEvent: Record<string, unknown>;
9
9
  coordinatorMessage?: string;
10
10
  queuedAt: number;
11
+ /**
12
+ * When set, this event is intended for a specific coordinator daemon.
13
+ * Coordinators on other daemons should ignore it during drain.
14
+ * Absent on legacy events — treated as broadcast to any coordinator.
15
+ */
16
+ targetCoordinatorDaemonId?: string;
11
17
  }
12
18
  export declare function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean;
13
19
  /** Drain and return all pending coordinator events for meshId, removing them from disk. */
14
- export declare function drainPendingMeshCoordinatorEvents(meshId?: string): PendingMeshCoordinatorEvent[];
20
+ export declare function drainPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): PendingMeshCoordinatorEvent[];
15
21
  /** Peek at pending coordinator events without draining (non-destructive). */
16
- export declare function getPendingMeshCoordinatorEvents(meshId?: string): readonly PendingMeshCoordinatorEvent[];
17
- /** Explicitly clear all pending coordinator events for a mesh. */
18
- export declare function clearPendingMeshCoordinatorEvents(meshId?: string): void;
22
+ export declare function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): readonly PendingMeshCoordinatorEvent[];
23
+ /** Explicitly clear all pending coordinator events for a mesh (and coordinator if scoped). */
24
+ export declare function clearPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): void;
19
25
  export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
20
26
  /**
21
27
  * Triggers a queue check for all nodes in the mesh.
@@ -13,7 +13,7 @@
13
13
  * Safety: mode 0o600, atomic append via appendFileSync
14
14
  */
15
15
  import { EventEmitter } from 'events';
16
- export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward';
16
+ export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward';
17
17
  export interface MeshLedgerEntry {
18
18
  id: string;
19
19
  meshId: string;
@@ -151,6 +151,26 @@ export interface AppendRemoteLedgerResult {
151
151
  }
152
152
  export declare const MAX_LEDGER_SLICE_LIMIT = 500;
153
153
  export declare function getLedgerDir(): string;
154
+ /**
155
+ * Footer to append to worker task messages so workers output structured results
156
+ * that the daemon parses via extractJsonObjectFromSummary / normalizeMeshWorkerResult.
157
+ *
158
+ * Usage: append buildWorkerTaskFooter() to the task message in mesh_send_task /
159
+ * mesh_enqueue_task. The coordinator prompt rules instruct coordinators to do this.
160
+ */
161
+ export declare function buildWorkerTaskFooter(): string;
162
+ /**
163
+ * Compact the active ledger file for a mesh by moving old terminal entries
164
+ * (task_completed, task_failed, task_stalled, recovery_attempted older than 7 days)
165
+ * to <meshId>.archive.jsonl, keeping the active file lean.
166
+ *
167
+ * Non-terminal entries (dispatch, sessions, node lifecycle) are always retained.
168
+ * Called automatically from appendLedgerEntry when the file exceeds COMPACT_THRESHOLD_BYTES.
169
+ */
170
+ export declare function compactLedger(meshId: string): {
171
+ archivedCount: number;
172
+ retainedCount: number;
173
+ };
154
174
  export declare function normalizeMeshWorkerResult(input?: Record<string, unknown>, source?: MeshWorkerResultArtifact['source']): MeshWorkerResultArtifact;
155
175
  export declare function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence;
156
176
  /**
@@ -1,7 +1,8 @@
1
1
  import type { MeshLedgerEntry } from './mesh-ledger.js';
2
2
  import type { PendingMeshCoordinatorEvent } from './mesh-events.js';
3
+ import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
3
4
  export type MeshAsyncRefineJobStatus = 'accepted' | 'running' | 'completed' | 'failed';
4
- export interface MeshAsyncRefineJobSummary {
5
+ export interface MeshAsyncRefineJobSummary extends MeshAsyncJobLifecycle {
5
6
  jobId: string;
6
7
  interactionId?: string;
7
8
  status: MeshAsyncRefineJobStatus;
@@ -12,8 +13,6 @@ export interface MeshAsyncRefineJobSummary {
12
13
  workspace?: string;
13
14
  branch?: string;
14
15
  into?: string;
15
- startedAt?: string;
16
- completedAt?: string;
17
16
  retryOfJobId?: string;
18
17
  lastEvent?: string;
19
18
  lastLedgerKind?: string;
@@ -1,4 +1,5 @@
1
1
  import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
2
+ import { BeadsDB } from './beads-db.js';
2
3
  export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
3
4
  export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
4
5
  export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
@@ -127,3 +128,19 @@ export declare function getMeshQueueStats(meshId: string): MeshWorkQueueStats;
127
128
  export declare function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void;
128
129
  export declare function __clearMeshQueueForTests(meshId: string): void;
129
130
  export declare function __resetBeadsDBForTests(): void;
131
+ export type DirectDispatchRecord = ReturnType<BeadsDB['getActiveDirectDispatches']>[number];
132
+ export declare function insertDirectDispatch(meshId: string, data: {
133
+ taskId: string;
134
+ nodeId?: string;
135
+ sessionId?: string;
136
+ providerType?: string;
137
+ message: string;
138
+ taskMode?: string;
139
+ via: string;
140
+ dispatchedToIdleSession?: boolean;
141
+ dispatchedAt: string;
142
+ }): void;
143
+ export declare function getActiveDirectDispatches(meshId: string): DirectDispatchRecord[];
144
+ export declare function updateDirectDispatchStatus(meshId: string, sessionId: string, status: 'acked' | 'completed' | 'failed' | 'stale'): void;
145
+ export declare function cleanupTerminalDirectDispatches(olderThanMs?: number): void;
146
+ export declare function markStaleDirectDispatches(meshId: string, olderThanMs?: number): void;
@@ -1,4 +1,5 @@
1
1
  import { type MeshRefineValidationCommandPlan, type RepoMeshRefineValidationCommandConfig } from './refine-config.js';
2
+ import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
2
3
  export type WorktreeBootstrapStatus = 'ready' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
3
4
  export interface RepoMeshWorktreeBootstrapConfig {
4
5
  version: 1;
@@ -8,16 +9,13 @@ export interface RepoMeshWorktreeBootstrapConfig {
8
9
  commands?: RepoMeshRefineValidationCommandConfig[];
9
10
  staleInputs?: string[];
10
11
  }
11
- export interface WorktreeBootstrapState {
12
+ export interface WorktreeBootstrapState extends MeshAsyncJobLifecycle {
12
13
  status: WorktreeBootstrapStatus;
13
14
  required: boolean;
14
15
  configSource?: string;
15
16
  configSourceType?: 'repo_file' | 'mesh_policy' | 'unavailable' | 'invalid';
16
- startedAt?: string;
17
- completedAt?: string;
18
17
  lastCommand?: string;
19
18
  exitCode?: number | null;
20
- error?: string;
21
19
  commandsRun?: Array<Record<string, unknown>>;
22
20
  staleInputs?: string[];
23
21
  }
@@ -10,6 +10,14 @@ import type { ProviderSummaryMetadata } from '../shared-types.js';
10
10
  import type { ChatMessageKind } from './chat-message-normalization.js';
11
11
  export type ReadChatTurnStatus = 'open' | 'waiting_approval' | 'complete' | 'error';
12
12
  export interface ReadChatResult {
13
+ /**
14
+ * Declared chat contract version. Absent or `'1.0'` → legacy v1 payload
15
+ * (current shape). `'2.0'` → v2 payload conforming to transcript-v2.ts
16
+ * (ReadChatResultV2). Validators in read-chat-contract.ts route on this
17
+ * field. A1 only adds the field; A2 will make v2 the daemon-internal
18
+ * canonical form and reject unrecognised versions at provider load time.
19
+ */
20
+ contractVersion?: import('./transcript-v2.js').ChatContractVersion;
13
21
  messages: ChatMessage[];
14
22
  status: AgentStatus;
15
23
  activeModal?: ModalInfo | null;
@@ -590,6 +598,17 @@ export interface ProviderCanonicalHistoryConfig {
590
598
  * Omitted mode defaults to 'native-source'.
591
599
  */
592
600
  mode?: 'native-source' | 'materialized-mirror' | 'disabled';
601
+ /**
602
+ * Chat transcript contract version this provider's read_chat output
603
+ * conforms to. See transcript-v2.ts for the v2 invariants. Absent or `'1.0'`
604
+ * → legacy v1 payload (current behaviour). `'2.0'` → strict v2 payload
605
+ * (stable providerUnitKey/bubbleId/sequence, strict enums, honest coverage).
606
+ *
607
+ * A1 only surfaces this field; validators in read-chat-contract.ts route
608
+ * on it. A2 makes v2 the daemon-internal canonical form and rejects
609
+ * unrecognised values at provider load time.
610
+ */
611
+ contractVersion?: import('./transcript-v2.js').ChatContractVersion;
593
612
  }
594
613
  /**
595
614
  * Auto-implement spawn config — controls how the provider is spawned for autonomous AI-driven
@@ -1,2 +1,31 @@
1
1
  import type { ReadChatResult } from './contracts.js';
2
+ import { CHAT_CONTRACT_VERSION_V1, CHAT_CONTRACT_VERSION_V2, type ChatContractVersion, type ReadChatResultV2 } from './transcript-v2.js';
3
+ /**
4
+ * Read the producer-declared contract version from a raw read_chat payload.
5
+ * Returns v1 when absent or unrecognised, so legacy producers keep working
6
+ * through A1. A2 will tighten this to throw when an unsupported version is
7
+ * declared.
8
+ */
9
+ export declare function readPayloadContractVersion(raw: unknown): ChatContractVersion;
10
+ /**
11
+ * Validate a v2 payload. Thin wrapper around assertReadChatResultV2Payload
12
+ * that prefixes the contract violation with the caller's source label.
13
+ */
14
+ export declare function validateReadChatResultV2Payload(raw: unknown, source?: string): ReadChatResultV2;
15
+ /**
16
+ * Versioned entry point. Routes on the producer-declared contractVersion:
17
+ * - v2 → strict v2 validation (transcript-v2.ts invariants)
18
+ * - v1 (or absent) → legacy permissive validation
19
+ *
20
+ * Callers that have not yet been audited to handle v2 outputs should use
21
+ * validateReadChatResultPayload directly; that path stays bound to v1 shape
22
+ * during the A1 transition.
23
+ */
24
+ export declare function validateReadChatResultPayloadVersioned(raw: unknown, source?: string): {
25
+ version: typeof CHAT_CONTRACT_VERSION_V1;
26
+ payload: ReadChatResult & Record<string, unknown>;
27
+ } | {
28
+ version: typeof CHAT_CONTRACT_VERSION_V2;
29
+ payload: ReadChatResultV2;
30
+ };
2
31
  export declare function validateReadChatResultPayload(raw: unknown, source?: string): ReadChatResult & Record<string, unknown>;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Chat Transcript Contract v2
3
+ *
4
+ * First-class message identity, monotonic sequence, strict enums. Designed to
5
+ * replace the v1 contract (contracts.ts ReadChatResult + types.ts ChatMessage)
6
+ * during the A2 big-bang. This module defines the contract; it does not yet
7
+ * change runtime behaviour. v1 keeps working until A2 flips the switch.
8
+ *
9
+ * Invariants v2 enforces (and v1 does not):
10
+ * 1. Every ChatMessageV2 has providerUnitKey, bubbleId, sequence — none optional.
11
+ * 2. sequence is a monotonic integer per (sessionId, source) tuple. Producers
12
+ * never reuse it; consumers MAY assume strict ordering.
13
+ * 3. providerUnitKey is stable across re-reads: the same logical message
14
+ * always yields the same key even if its index shifts or content is
15
+ * edited mid-stream. Producers derive it from provider-owned identifiers
16
+ * (turn id, native message id), not from content hashes or array index.
17
+ * 4. timestamp is producer-asserted wall-clock; orderingTimestamp is monotonic
18
+ * and used for snapshot diffs. 1ms fallback increments are forbidden — if
19
+ * a producer cannot supply a stable monotonic value, the daemon assigns
20
+ * sequence at ingest and orderingTimestamp = ingestedAt.
21
+ * 5. visibility / source / role / kind are strict unions. No `(string & {})`
22
+ * escape hatches.
23
+ * 6. Coverage is exactly one of full/tail/current-turn. Mixing it with
24
+ * partialReason or unavailableReason is a contract violation.
25
+ * 7. workspace / sessionId are explicit on every ReadChatResultV2; the daemon
26
+ * no longer infers them from message bodies.
27
+ */
28
+ import type { MessagePart, ToolCallInfo } from './contracts.js';
29
+ export declare const CHAT_CONTRACT_VERSION_V1: "1.0";
30
+ export declare const CHAT_CONTRACT_VERSION_V2: "2.0";
31
+ export type ChatContractVersion = typeof CHAT_CONTRACT_VERSION_V1 | typeof CHAT_CONTRACT_VERSION_V2;
32
+ export declare const SUPPORTED_CHAT_CONTRACT_VERSIONS: readonly ChatContractVersion[];
33
+ export declare const CHAT_ROLES_V2: readonly ["user", "assistant", "system"];
34
+ export type ChatRoleV2 = typeof CHAT_ROLES_V2[number];
35
+ export declare const CHAT_MESSAGE_KINDS_V2: readonly ["standard", "thought", "tool", "terminal", "system"];
36
+ export type ChatMessageKindV2 = typeof CHAT_MESSAGE_KINDS_V2[number];
37
+ export declare const CHAT_VISIBILITIES_V2: readonly ["user", "debug", "internal", "hidden"];
38
+ export type ChatVisibilityV2 = typeof CHAT_VISIBILITIES_V2[number];
39
+ export declare const CHAT_AUDIENCES_V2: readonly ["chat", "debug", "trace", "internal"];
40
+ export type ChatAudienceV2 = typeof CHAT_AUDIENCES_V2[number];
41
+ export declare const CHAT_SOURCES_V2: readonly ["assistant_text", "tool_call", "terminal_command", "runtime_activity", "runtime_status", "provider_chrome", "control"];
42
+ export type ChatSourceV2 = typeof CHAT_SOURCES_V2[number];
43
+ export declare const CHAT_BUBBLE_STATES_V2: readonly ["draft", "streaming", "final", "removed"];
44
+ export type ChatBubbleStateV2 = typeof CHAT_BUBBLE_STATES_V2[number];
45
+ /**
46
+ * Stable identity of a single chat message. All fields are required in v2.
47
+ *
48
+ * - providerUnitKey: provider-owned canonical id. Must be derivable from
49
+ * provider primitives (native message id, turn id, bubble id), NOT from
50
+ * array index or content hash. Stable across re-reads of the same logical
51
+ * message even if neighbours change.
52
+ * - bubbleId: dashboard-owned bubble identity. May equal providerUnitKey when
53
+ * the provider already exposes a bubble-grained id; otherwise daemon
54
+ * derives it deterministically from providerUnitKey (e.g. `bubble:${key}`).
55
+ * - sequence: monotonic integer per (sessionId, source). Strictly increasing
56
+ * over the lifetime of the session. Used for snapshot diffs and dedup.
57
+ * - turnKey: stable identifier for the conversation turn this message belongs
58
+ * to. Required so consumers can group messages without re-deriving turns.
59
+ */
60
+ export interface MessageIdentityV2 {
61
+ providerUnitKey: string;
62
+ bubbleId: string;
63
+ sequence: number;
64
+ turnKey: string;
65
+ }
66
+ /**
67
+ * Producer-asserted wall-clock time (ms since epoch). Forward jumps are
68
+ * permitted; consumers MUST NOT rely on this for ordering.
69
+ */
70
+ export type ChatTimestampMs = number;
71
+ /**
72
+ * Monotonic ordering value (ms since epoch, but assigned by the daemon at
73
+ * ingest to guarantee monotonicity even if the producer's wall clock skews).
74
+ * Used by snapshot diffs in place of timestamp.
75
+ */
76
+ export type ChatOrderingTimestampMs = number;
77
+ export interface ChatMessageV2 extends MessageIdentityV2 {
78
+ role: ChatRoleV2;
79
+ kind: ChatMessageKindV2;
80
+ content: string | MessagePart[];
81
+ bubbleState: ChatBubbleStateV2;
82
+ timestamp: ChatTimestampMs;
83
+ orderingTimestamp: ChatOrderingTimestampMs;
84
+ visibility: ChatVisibilityV2;
85
+ audience: ChatAudienceV2;
86
+ source: ChatSourceV2;
87
+ /** Sender display name (for shared sessions). Empty string when unused. */
88
+ senderName: string;
89
+ /** Tool calls associated with this message. Empty array when none. */
90
+ toolCalls: ToolCallInfo[];
91
+ /** Producer-defined metadata. Daemon never inspects this except for passthrough. */
92
+ meta: Readonly<Record<string, unknown>>;
93
+ }
94
+ export interface WorkspaceContextV2 {
95
+ /** Absolute realpath of the workspace the session is reading from. */
96
+ workspacePath: string;
97
+ /** Workspace the session was intended to operate on (may differ during cross-workspace ops). */
98
+ intendedWorkspacePath: string;
99
+ }
100
+ export interface SessionContextV2 {
101
+ /** Provider-owned session id (e.g. claude.jsonl uuid, codex session_meta.id). */
102
+ providerSessionId: string;
103
+ /** Optional daemon-side history alias used to disambiguate within a workspace. */
104
+ historySessionId?: string;
105
+ }
106
+ /**
107
+ * v1 allowed `coverage='full'` to coexist with `partialReason`/`unavailableReason`,
108
+ * which is a contract lie. v2 makes the three states mutually exclusive.
109
+ */
110
+ export type CoverageHintV2 = {
111
+ kind: 'full';
112
+ } | {
113
+ kind: 'tail';
114
+ tailMessageCount: number;
115
+ } | {
116
+ kind: 'current-turn';
117
+ turnKey: string;
118
+ } | {
119
+ kind: 'partial';
120
+ partialReason: string;
121
+ } | {
122
+ kind: 'unavailable';
123
+ unavailableReason: string;
124
+ };
125
+ export type CanonicalHistoryModeV2 = 'native-source' | 'disabled' | 'materialized-mirror';
126
+ export interface CanonicalHistoryDeclarationV2 {
127
+ contractVersion: ChatContractVersion;
128
+ format: string;
129
+ mode: CanonicalHistoryModeV2;
130
+ watchPath?: string;
131
+ scripts: {
132
+ readSession: string;
133
+ listSessions: string;
134
+ };
135
+ }
136
+ export type ReadChatStatusV2 = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'panel_hidden' | 'streaming';
137
+ export type ReadChatTurnStatusV2 = 'open' | 'waiting_approval' | 'complete' | 'error';
138
+ export interface ReadChatResultV2 {
139
+ contractVersion: typeof CHAT_CONTRACT_VERSION_V2;
140
+ workspace: WorkspaceContextV2;
141
+ session: SessionContextV2;
142
+ coverage: CoverageHintV2;
143
+ status: ReadChatStatusV2;
144
+ messages: ChatMessageV2[];
145
+ currentTurnId: string;
146
+ turnStatus: ReadChatTurnStatusV2;
147
+ transcriptAuthority: 'provider' | 'daemon';
148
+ }
149
+ export declare class ChatContractViolationError extends Error {
150
+ readonly violationPath: string;
151
+ readonly contractVersion: ChatContractVersion;
152
+ constructor(contractVersion: ChatContractVersion, violationPath: string, detail: string);
153
+ }
154
+ /**
155
+ * Validate a v2 read_chat payload. Throws ChatContractViolationError on the
156
+ * first violation. v2 invariants beyond per-field checks:
157
+ * - messages[*].sequence strictly increasing
158
+ * - messages[*].orderingTimestamp monotonic non-decreasing
159
+ * - coverage union is well-formed
160
+ */
161
+ export declare function assertReadChatResultV2Payload(raw: unknown): ReadChatResultV2;
162
+ export declare function isSupportedChatContractVersion(value: unknown): value is ChatContractVersion;
163
+ /**
164
+ * Read a provider's declared chat contract version from its parsed
165
+ * provider.json `canonicalHistory.contractVersion` field.
166
+ *
167
+ * - Absent or unrecognized → treated as v1 (legacy behaviour preserved during
168
+ * the A1 transition). A2 will tighten this to reject unsupported versions
169
+ * at provider load time.
170
+ * - Present and supported → returned as-is.
171
+ *
172
+ * The returned version drives which validator (assertReadChatResultV2Payload
173
+ * vs the legacy validateReadChatResultPayload) processes the provider's
174
+ * read_chat output.
175
+ */
176
+ export declare function readDeclaredChatContractVersion(canonicalHistory: unknown): ChatContractVersion;
@@ -408,3 +408,8 @@ export interface RepoMeshLedgerStatus {
408
408
  entries: RepoMeshLedgerEntryStatus[];
409
409
  summary: RepoMeshLedgerSummaryStatus;
410
410
  }
411
+ export interface MeshAsyncJobLifecycle {
412
+ startedAt?: string;
413
+ completedAt?: string;
414
+ error?: string;
415
+ }
@@ -49,6 +49,13 @@ export interface ReadChatSyncResult {
49
49
  message: string;
50
50
  buttons: string[];
51
51
  } | null;
52
+ /**
53
+ * Chat source provenance from ChatSourceMachine (A2). Carries the
54
+ * selected source, transition cause, lock state, and legacy
55
+ * fallbackReason — opaque to the daemon-core, consumed by web-core
56
+ * for the source debug badge and SourceTimeline (A3).
57
+ */
58
+ messageSource?: Record<string, unknown>;
52
59
  }
53
60
  export interface ProviderSummaryItem {
54
61
  id: string;