@adhdev/daemon-core 0.9.77-rc.5 → 0.9.77-rc.50
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.
- package/dist/boot/daemon-lifecycle.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +14 -4
- package/dist/commands/mesh-coordinator.d.ts +10 -0
- package/dist/commands/router.d.ts +4 -1
- package/dist/config/mesh-config.d.ts +1 -0
- package/dist/git/git-worktree.d.ts +15 -2
- package/dist/index.d.ts +8 -4
- package/dist/index.js +1959 -291
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1947 -291
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +10 -7
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +55 -0
- package/dist/mesh/mesh-ledger.d.ts +84 -4
- package/dist/mesh/mesh-sync.d.ts +4 -12
- package/dist/mesh/mesh-work-queue.d.ts +56 -1
- package/dist/mesh/p2p-relay-failure.d.ts +35 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/dist/repo-mesh-types.d.ts +2 -0
- package/dist/shared-types.d.ts +38 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +5 -0
- package/src/cli-adapters/provider-cli-adapter.ts +35 -4
- package/src/cli-adapters/provider-cli-shared.ts +14 -4
- package/src/commands/cli-manager.ts +0 -4
- package/src/commands/mesh-coordinator.ts +55 -7
- package/src/commands/router.ts +847 -26
- package/src/commands/stream-commands.ts +8 -1
- package/src/config/config.ts +2 -1
- package/src/config/mesh-config.ts +2 -0
- package/src/config/workspaces.ts +1 -1
- package/src/git/git-worktree.ts +56 -4
- package/src/index.d.ts +3 -0
- package/src/index.ts +20 -4
- package/src/mesh/coordinator-prompt.ts +21 -10
- package/src/mesh/mesh-events.ts +522 -22
- package/src/mesh/mesh-ledger-reconciliation.ts +115 -0
- package/src/mesh/mesh-ledger.ts +209 -8
- package/src/mesh/mesh-sync.ts +4 -34
- package/src/mesh/mesh-work-queue.ts +163 -10
- package/src/mesh/p2p-relay-failure.ts +152 -0
- package/src/providers/cli-provider-instance.ts +153 -30
- package/src/repo-mesh-types.ts +2 -0
- package/src/shared-types.ts +38 -0
|
@@ -8,20 +8,23 @@ export interface PendingMeshCoordinatorEvent {
|
|
|
8
8
|
}
|
|
9
9
|
/** Drain and return all pending coordinator events, clearing the queue. */
|
|
10
10
|
export declare function drainPendingMeshCoordinatorEvents(): PendingMeshCoordinatorEvent[];
|
|
11
|
-
export declare function tryAssignQueueTask(components:
|
|
12
|
-
cliManager: any;
|
|
13
|
-
}, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
|
|
11
|
+
export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
|
|
14
12
|
/**
|
|
15
13
|
* Triggers a queue check for all nodes in the mesh.
|
|
16
14
|
* Called when a new task is enqueued, in case nodes are already idle.
|
|
17
15
|
*/
|
|
18
|
-
export declare function triggerMeshQueue(components:
|
|
19
|
-
instanceManager: any;
|
|
20
|
-
cliManager: any;
|
|
21
|
-
}, meshId: string): void;
|
|
16
|
+
export declare function triggerMeshQueue(components: DaemonComponents, meshId: string): Promise<void>;
|
|
22
17
|
export declare function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>): {
|
|
23
18
|
success: boolean;
|
|
24
19
|
forwarded: number;
|
|
20
|
+
suppressed: boolean;
|
|
21
|
+
intentionalCleanupStop: boolean;
|
|
22
|
+
error?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
success: boolean;
|
|
25
|
+
forwarded: number;
|
|
26
|
+
suppressed?: undefined;
|
|
27
|
+
intentionalCleanupStop?: undefined;
|
|
25
28
|
error?: undefined;
|
|
26
29
|
} | {
|
|
27
30
|
success: boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AppendRemoteLedgerResult, MeshLedgerSlice, MeshLedgerSummary } from './mesh-ledger.js';
|
|
2
|
+
export type MeshLedgerReplicaStatus = 'local' | 'queried' | 'imported' | 'failed';
|
|
3
|
+
export interface MeshLedgerReplicaEvidence {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
daemonId?: string;
|
|
6
|
+
status: MeshLedgerReplicaStatus;
|
|
7
|
+
transport: 'local' | 'p2p_datachannel';
|
|
8
|
+
protocol: 'adhdev.mesh.ledger.slice.v1';
|
|
9
|
+
entriesReceived: number;
|
|
10
|
+
entriesImported: number;
|
|
11
|
+
skippedDuplicate: number;
|
|
12
|
+
rejectedInvalid: number;
|
|
13
|
+
hasMore: boolean;
|
|
14
|
+
nextAfterId: string | null;
|
|
15
|
+
lastTimestamp: string | null;
|
|
16
|
+
summary?: MeshLedgerSummary;
|
|
17
|
+
error?: string;
|
|
18
|
+
noFallbackReason?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MeshLedgerReconciliationEvidence {
|
|
21
|
+
protocol: 'adhdev.mesh.ledger.reconciliation.v1';
|
|
22
|
+
meshId: string;
|
|
23
|
+
generatedAt: string;
|
|
24
|
+
sourceOfTruth: {
|
|
25
|
+
kind: 'coordinator_local_jsonl';
|
|
26
|
+
p2pOnly: true;
|
|
27
|
+
cloudD1LedgerSync: false;
|
|
28
|
+
notes: string;
|
|
29
|
+
};
|
|
30
|
+
replicas: MeshLedgerReplicaEvidence[];
|
|
31
|
+
totals: {
|
|
32
|
+
replicas: number;
|
|
33
|
+
queried: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
entriesReceived: number;
|
|
36
|
+
entriesImported: number;
|
|
37
|
+
skippedDuplicate: number;
|
|
38
|
+
rejectedInvalid: number;
|
|
39
|
+
};
|
|
40
|
+
convergence: {
|
|
41
|
+
complete: boolean;
|
|
42
|
+
pendingNodes: string[];
|
|
43
|
+
failedNodes: string[];
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare function buildMeshLedgerReplicaEvidence(args: {
|
|
47
|
+
nodeId: string;
|
|
48
|
+
daemonId?: string;
|
|
49
|
+
transport: 'local' | 'p2p_datachannel';
|
|
50
|
+
slice?: MeshLedgerSlice;
|
|
51
|
+
importResult?: AppendRemoteLedgerResult;
|
|
52
|
+
status?: MeshLedgerReplicaStatus;
|
|
53
|
+
error?: string;
|
|
54
|
+
}): MeshLedgerReplicaEvidence;
|
|
55
|
+
export declare function buildMeshLedgerReconciliationEvidence(meshId: string, replicas: MeshLedgerReplicaEvidence[]): MeshLedgerReconciliationEvidence;
|
|
@@ -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_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_removed' | 'coordinator_started' | 'recovery_attempted';
|
|
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_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled';
|
|
17
17
|
export interface MeshLedgerEntry {
|
|
18
18
|
id: string;
|
|
19
19
|
meshId: string;
|
|
@@ -24,6 +24,43 @@ export interface MeshLedgerEntry {
|
|
|
24
24
|
providerType?: string;
|
|
25
25
|
payload: Record<string, unknown>;
|
|
26
26
|
}
|
|
27
|
+
export declare function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean;
|
|
28
|
+
export interface MeshTaskCompletionEvidence {
|
|
29
|
+
source: 'agent_status_event';
|
|
30
|
+
event: 'agent:generating_completed' | 'agent:ready';
|
|
31
|
+
nodeId: string;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
providerType?: string;
|
|
34
|
+
completedAt: string;
|
|
35
|
+
transcriptHandle: {
|
|
36
|
+
kind: 'provider_session' | 'runtime_session';
|
|
37
|
+
sessionId: string;
|
|
38
|
+
providerSessionId?: string;
|
|
39
|
+
finalSummaryAvailable: boolean;
|
|
40
|
+
};
|
|
41
|
+
git: {
|
|
42
|
+
status: 'deferred';
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
validation: {
|
|
46
|
+
status: 'deferred';
|
|
47
|
+
commandsRun: string[];
|
|
48
|
+
reason: string;
|
|
49
|
+
};
|
|
50
|
+
checkpoint: {
|
|
51
|
+
attempted: false;
|
|
52
|
+
reason: 'not_attempted_for_ordinary_completion';
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface BuildTaskCompletionEvidenceOptions {
|
|
56
|
+
event: MeshTaskCompletionEvidence['event'];
|
|
57
|
+
nodeId: string;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
providerType?: string;
|
|
60
|
+
providerSessionId?: string;
|
|
61
|
+
finalSummary?: string;
|
|
62
|
+
completedAt?: string;
|
|
63
|
+
}
|
|
27
64
|
export interface MeshLedgerSummary {
|
|
28
65
|
meshId: string;
|
|
29
66
|
totalEntries: number;
|
|
@@ -41,7 +78,44 @@ export interface ReadLedgerOptions {
|
|
|
41
78
|
since?: string;
|
|
42
79
|
kind?: MeshLedgerKind[];
|
|
43
80
|
}
|
|
81
|
+
export interface ReadLedgerSliceOptions {
|
|
82
|
+
/** Return entries strictly after this entry id. If not found, starts from the beginning of the filtered set. */
|
|
83
|
+
afterId?: string;
|
|
84
|
+
/** Return entries at or after this timestamp. */
|
|
85
|
+
since?: string;
|
|
86
|
+
/** Optional event kind filter. */
|
|
87
|
+
kind?: MeshLedgerKind[];
|
|
88
|
+
/** Maximum entries to return. Clamped to a bounded protocol maximum. */
|
|
89
|
+
limit?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface MeshLedgerCursor {
|
|
92
|
+
afterId: string | null;
|
|
93
|
+
nextAfterId: string | null;
|
|
94
|
+
limit: number;
|
|
95
|
+
hasMore: boolean;
|
|
96
|
+
}
|
|
97
|
+
export interface MeshLedgerSlice {
|
|
98
|
+
protocol: 'adhdev.mesh.ledger.slice.v1';
|
|
99
|
+
meshId: string;
|
|
100
|
+
entries: MeshLedgerEntry[];
|
|
101
|
+
cursor: MeshLedgerCursor;
|
|
102
|
+
summary: MeshLedgerSummary;
|
|
103
|
+
sourceOfTruth: {
|
|
104
|
+
kind: 'local_jsonl';
|
|
105
|
+
path: string;
|
|
106
|
+
bounded: true;
|
|
107
|
+
maxLimit: number;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export interface AppendRemoteLedgerResult {
|
|
111
|
+
accepted: number;
|
|
112
|
+
skippedDuplicate: number;
|
|
113
|
+
rejectedInvalid: number;
|
|
114
|
+
entries: MeshLedgerEntry[];
|
|
115
|
+
}
|
|
116
|
+
export declare const MAX_LEDGER_SLICE_LIMIT = 500;
|
|
44
117
|
export declare function getLedgerDir(): string;
|
|
118
|
+
export declare function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence;
|
|
45
119
|
/**
|
|
46
120
|
* Append a new entry to the mesh ledger.
|
|
47
121
|
* Handles file creation, rotation on size overflow, and atomic writes.
|
|
@@ -49,14 +123,20 @@ export declare function getLedgerDir(): string;
|
|
|
49
123
|
export declare const meshLedgerEvents: EventEmitter<[never]>;
|
|
50
124
|
export declare function appendLedgerEntry(meshId: string, partial: Omit<MeshLedgerEntry, 'id' | 'meshId' | 'timestamp'>): MeshLedgerEntry;
|
|
51
125
|
/**
|
|
52
|
-
* Append entries received
|
|
53
|
-
* This skips deduplicated entries and
|
|
126
|
+
* Append entries received over local-first/P2P ledger replication to the local ledger.
|
|
127
|
+
* This skips deduplicated entries and rejects malformed/cross-mesh entries.
|
|
54
128
|
*/
|
|
55
|
-
export declare function appendRemoteLedgerEntries(meshId: string, entries: MeshLedgerEntry[]):
|
|
129
|
+
export declare function appendRemoteLedgerEntries(meshId: string, entries: MeshLedgerEntry[]): AppendRemoteLedgerResult;
|
|
56
130
|
/**
|
|
57
131
|
* Read ledger entries with optional filtering.
|
|
58
132
|
*/
|
|
59
133
|
export declare function readLedgerEntries(meshId: string, opts?: ReadLedgerOptions): MeshLedgerEntry[];
|
|
134
|
+
/**
|
|
135
|
+
* Read a bounded, cursor-addressable ledger slice for local-first/P2P replication.
|
|
136
|
+
* The result is intentionally small and self-describing so coordinators can query
|
|
137
|
+
* remote daemons on demand without Cloud/D1 becoming a ledger data-plane.
|
|
138
|
+
*/
|
|
139
|
+
export declare function readLedgerSlice(meshId: string, opts?: ReadLedgerSliceOptions): MeshLedgerSlice;
|
|
60
140
|
/**
|
|
61
141
|
* Get a summary of mesh activity from the ledger.
|
|
62
142
|
*/
|
package/dist/mesh/mesh-sync.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mesh Sync — Sync local mesh
|
|
2
|
+
* Mesh Sync — Sync local mesh metadata to/from cloud D1
|
|
3
3
|
*
|
|
4
4
|
* When cloud is available, this module pushes local mesh config
|
|
5
5
|
* to the server and pulls remote meshes that were created from
|
|
6
6
|
* other machines. The local ~/.adhdev/meshes.json remains the
|
|
7
|
-
* canonical source; cloud is a
|
|
7
|
+
* canonical source; cloud is a membership/metadata layer only.
|
|
8
|
+
* Task/chat/ledger evidence remains local-first and must not be
|
|
9
|
+
* synchronized through Cloud/D1.
|
|
8
10
|
*
|
|
9
11
|
* This is called lazily (not on daemon startup) — only when the
|
|
10
12
|
* user explicitly opens the mesh page or runs `adhdev mesh sync`.
|
|
@@ -26,12 +28,6 @@ export interface MeshSyncTransport {
|
|
|
26
28
|
}>;
|
|
27
29
|
/** DELETE /api/v1/repo-meshes/:id */
|
|
28
30
|
deleteRemoteMesh(meshId: string): Promise<void>;
|
|
29
|
-
/** POST /api/v1/repo-meshes/:id/ledger/sync */
|
|
30
|
-
syncMeshLedger?(meshId: string, data: {
|
|
31
|
-
newEntries: any[];
|
|
32
|
-
}): Promise<{
|
|
33
|
-
missingEntries: any[];
|
|
34
|
-
}>;
|
|
35
31
|
}
|
|
36
32
|
export interface RemoteMeshRecord {
|
|
37
33
|
id: string;
|
|
@@ -55,7 +51,3 @@ export interface MeshSyncResult {
|
|
|
55
51
|
* Pull remote meshes that don't exist locally.
|
|
56
52
|
*/
|
|
57
53
|
export declare function syncMeshes(transport: MeshSyncTransport): Promise<MeshSyncResult>;
|
|
58
|
-
/**
|
|
59
|
-
* Sync the task ledger for a specific mesh.
|
|
60
|
-
*/
|
|
61
|
-
export declare function syncMeshLedger(meshId: string, transport: MeshSyncTransport): Promise<void>;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed';
|
|
1
|
+
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
2
|
+
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
3
|
+
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
4
|
+
export declare const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[];
|
|
5
|
+
export declare const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[];
|
|
2
6
|
export interface MeshWorkQueueEntry {
|
|
3
7
|
id: string;
|
|
4
8
|
meshId: string;
|
|
@@ -6,10 +10,28 @@ export interface MeshWorkQueueEntry {
|
|
|
6
10
|
status: MeshTaskStatus;
|
|
7
11
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
8
12
|
targetNodeId?: string;
|
|
13
|
+
/** If specified, only this runtime session can claim the task */
|
|
14
|
+
targetSessionId?: string;
|
|
9
15
|
/** The node that actually claimed and is executing the task */
|
|
10
16
|
assignedNodeId?: string;
|
|
11
17
|
/** The session currently executing the task */
|
|
12
18
|
assignedSessionId?: string;
|
|
19
|
+
/** Human/operator reason for terminal cancellation. */
|
|
20
|
+
cancelReason?: string;
|
|
21
|
+
cancelledAt?: string;
|
|
22
|
+
/** Human/operator reason for manually requeueing a task. */
|
|
23
|
+
requeueReason?: string;
|
|
24
|
+
requeuedAt?: string;
|
|
25
|
+
requeueCount?: number;
|
|
26
|
+
/** Last automatic queue session spin-up attempt, for mesh_view_queue/debug visibility. */
|
|
27
|
+
autoLaunch?: {
|
|
28
|
+
status: 'skipped' | 'started' | 'failed' | 'completed';
|
|
29
|
+
reason?: string;
|
|
30
|
+
nodeId?: string;
|
|
31
|
+
providerType?: string;
|
|
32
|
+
sessionId?: string;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
};
|
|
13
35
|
createdAt: string;
|
|
14
36
|
updatedAt: string;
|
|
15
37
|
}
|
|
@@ -18,6 +40,7 @@ export interface MeshWorkQueueEntry {
|
|
|
18
40
|
*/
|
|
19
41
|
export declare function enqueueTask(meshId: string, message: string, opts?: {
|
|
20
42
|
targetNodeId?: string;
|
|
43
|
+
targetSessionId?: string;
|
|
21
44
|
}): MeshWorkQueueEntry;
|
|
22
45
|
/**
|
|
23
46
|
* Get all tasks in the queue, optionally filtered by status.
|
|
@@ -34,15 +57,47 @@ export declare function claimNextTask(meshId: string, nodeId: string, sessionId:
|
|
|
34
57
|
* Used when a session completes, fails, or stalls.
|
|
35
58
|
*/
|
|
36
59
|
export declare function updateTaskStatus(meshId: string, taskId: string, status: MeshTaskStatus): MeshWorkQueueEntry | null;
|
|
60
|
+
export declare function recordTaskAutoLaunch(meshId: string, taskId: string, autoLaunch: Omit<NonNullable<MeshWorkQueueEntry['autoLaunch']>, 'updatedAt'>): MeshWorkQueueEntry | null;
|
|
61
|
+
/**
|
|
62
|
+
* Mark a queue task as manually cancelled without deleting audit history.
|
|
63
|
+
*/
|
|
64
|
+
export declare function cancelTask(meshId: string, taskId: string, opts?: {
|
|
65
|
+
reason?: string;
|
|
66
|
+
}): MeshWorkQueueEntry | null;
|
|
67
|
+
/**
|
|
68
|
+
* Return a queue task to pending for retry. By default, dead session targeting
|
|
69
|
+
* and assigned ownership are cleared so stale assignments do not strand again.
|
|
70
|
+
*/
|
|
71
|
+
export declare function requeueTask(meshId: string, taskId: string, opts?: {
|
|
72
|
+
reason?: string;
|
|
73
|
+
targetNodeId?: string;
|
|
74
|
+
targetSessionId?: string;
|
|
75
|
+
clearTargetNode?: boolean;
|
|
76
|
+
clearTargetSession?: boolean;
|
|
77
|
+
}): MeshWorkQueueEntry | null;
|
|
37
78
|
/**
|
|
38
79
|
* Update the status of the task currently assigned to a specific session.
|
|
39
80
|
*/
|
|
40
81
|
export declare function updateSessionTaskStatus(meshId: string, sessionId: string, status: MeshTaskStatus): MeshWorkQueueEntry | null;
|
|
41
82
|
export interface MeshWorkQueueStats {
|
|
83
|
+
total: number;
|
|
84
|
+
active: number;
|
|
85
|
+
historical: number;
|
|
42
86
|
pending: number;
|
|
43
87
|
assigned: number;
|
|
44
88
|
completed: number;
|
|
45
89
|
failed: number;
|
|
90
|
+
cancelled: number;
|
|
91
|
+
/** Source-of-truth active queue counters; only pending/assigned are live work. */
|
|
92
|
+
activeCounts: Record<MeshActiveTaskStatus, number>;
|
|
93
|
+
/** Terminal ledger records kept for audit/history; never count as active work. */
|
|
94
|
+
historicalCounts: Record<MeshHistoricalTaskStatus, number>;
|
|
95
|
+
activeAssignments: Array<{
|
|
96
|
+
id: string;
|
|
97
|
+
nodeId?: string;
|
|
98
|
+
sessionId?: string;
|
|
99
|
+
message: string;
|
|
100
|
+
}>;
|
|
46
101
|
}
|
|
47
102
|
/**
|
|
48
103
|
* Return aggregate queue statistics for the given mesh.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type P2pRelayFailureCode = 'p2p_unavailable' | 'p2p_timeout' | 'p2p_not_connected' | 'p2p_datachannel_closed' | 'p2p_no_route' | 'p2p_daemon_offline' | 'mesh_logic_or_provider_failure';
|
|
2
|
+
export interface P2pRelayFailureContext {
|
|
3
|
+
command?: string;
|
|
4
|
+
targetDaemonId?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface P2pRelayFailureClassification {
|
|
7
|
+
code: P2pRelayFailureCode;
|
|
8
|
+
reason: string;
|
|
9
|
+
transport: 'p2p' | 'unknown';
|
|
10
|
+
recoverable: boolean;
|
|
11
|
+
retryRecommended: boolean;
|
|
12
|
+
nextAction: string;
|
|
13
|
+
noFallbackReason: string;
|
|
14
|
+
}
|
|
15
|
+
export interface P2pRelayFailurePayload extends P2pRelayFailureClassification {
|
|
16
|
+
success: false;
|
|
17
|
+
error: string;
|
|
18
|
+
command?: string;
|
|
19
|
+
targetDaemonId?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function classifyP2pRelayFailure(error: unknown, _context?: P2pRelayFailureContext): P2pRelayFailureClassification;
|
|
22
|
+
export declare function isP2pRelayTransportFailure(error: unknown): boolean;
|
|
23
|
+
export declare function buildP2pRelayFailurePayload(error: unknown, context?: P2pRelayFailureContext): P2pRelayFailurePayload;
|
|
24
|
+
export declare class P2pRelayFailureError extends Error {
|
|
25
|
+
code: P2pRelayFailureCode;
|
|
26
|
+
reason: string;
|
|
27
|
+
transport: 'p2p' | 'unknown';
|
|
28
|
+
recoverable: boolean;
|
|
29
|
+
retryRecommended: boolean;
|
|
30
|
+
nextAction: string;
|
|
31
|
+
noFallbackReason: string;
|
|
32
|
+
command?: string;
|
|
33
|
+
targetDaemonId?: string;
|
|
34
|
+
constructor(message: string, context?: P2pRelayFailureContext);
|
|
35
|
+
}
|
|
@@ -100,6 +100,12 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
100
100
|
private completedDebounceTimer;
|
|
101
101
|
private completedDebouncePending;
|
|
102
102
|
private enforceFreshSessionLaunchIfNeeded;
|
|
103
|
+
private completionHasFinalAssistantMessage;
|
|
104
|
+
private hasAdapterPendingResponse;
|
|
105
|
+
private shouldSuppressStaleParsedBusyStatus;
|
|
106
|
+
private getCompletedFinalizationBlockReason;
|
|
107
|
+
private scheduleCompletedDebounceFlush;
|
|
108
|
+
private flushCompletedDebounceIfFinalized;
|
|
103
109
|
private maybeAutoApproveStatus;
|
|
104
110
|
private detectStatusTransition;
|
|
105
111
|
private pushEvent;
|
|
@@ -179,6 +179,8 @@ export interface LocalMeshNodeEntry {
|
|
|
179
179
|
workspace: string;
|
|
180
180
|
repoRoot?: string;
|
|
181
181
|
daemonId?: string;
|
|
182
|
+
/** Machine registry ID that owns this workspace, when known. */
|
|
183
|
+
machineId?: string;
|
|
182
184
|
userOverrides: Partial<RepoMeshNodeCapabilities>;
|
|
183
185
|
policy: RepoMeshNodePolicy;
|
|
184
186
|
/** For single-machine mesh: same daemon, different worktree */
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -293,10 +293,29 @@ export interface SessionEntry {
|
|
|
293
293
|
surfaceHidden?: boolean;
|
|
294
294
|
settings?: Record<string, any>;
|
|
295
295
|
meshQueueStats?: {
|
|
296
|
+
total?: number;
|
|
297
|
+
active?: number;
|
|
298
|
+
historical?: number;
|
|
296
299
|
pending: number;
|
|
297
300
|
assigned: number;
|
|
298
301
|
completed: number;
|
|
299
302
|
failed: number;
|
|
303
|
+
cancelled?: number;
|
|
304
|
+
activeCounts?: {
|
|
305
|
+
pending: number;
|
|
306
|
+
assigned: number;
|
|
307
|
+
};
|
|
308
|
+
historicalCounts?: {
|
|
309
|
+
completed: number;
|
|
310
|
+
failed: number;
|
|
311
|
+
cancelled: number;
|
|
312
|
+
};
|
|
313
|
+
activeAssignments?: Array<{
|
|
314
|
+
id: string;
|
|
315
|
+
nodeId?: string;
|
|
316
|
+
sessionId?: string;
|
|
317
|
+
message: string;
|
|
318
|
+
}>;
|
|
300
319
|
};
|
|
301
320
|
}
|
|
302
321
|
/**
|
|
@@ -337,10 +356,29 @@ export interface CompactSessionEntry {
|
|
|
337
356
|
summaryMetadata?: ProviderSummaryMetadata;
|
|
338
357
|
settings?: Record<string, any>;
|
|
339
358
|
meshQueueStats?: {
|
|
359
|
+
total?: number;
|
|
360
|
+
active?: number;
|
|
361
|
+
historical?: number;
|
|
340
362
|
pending: number;
|
|
341
363
|
assigned: number;
|
|
342
364
|
completed: number;
|
|
343
365
|
failed: number;
|
|
366
|
+
cancelled?: number;
|
|
367
|
+
activeCounts?: {
|
|
368
|
+
pending: number;
|
|
369
|
+
assigned: number;
|
|
370
|
+
};
|
|
371
|
+
historicalCounts?: {
|
|
372
|
+
completed: number;
|
|
373
|
+
failed: number;
|
|
374
|
+
cancelled: number;
|
|
375
|
+
};
|
|
376
|
+
activeAssignments?: Array<{
|
|
377
|
+
id: string;
|
|
378
|
+
nodeId?: string;
|
|
379
|
+
sessionId?: string;
|
|
380
|
+
message: string;
|
|
381
|
+
}>;
|
|
344
382
|
};
|
|
345
383
|
}
|
|
346
384
|
export type VersionUpdateReason = 'force_update_below' | 'major_minor_mismatch' | 'patch_mismatch' | 'daemon_ahead';
|
package/package.json
CHANGED
|
@@ -83,6 +83,9 @@ export interface DaemonInitConfig {
|
|
|
83
83
|
|
|
84
84
|
/** Fired before send_chat is dispatched — used for turn snapshot hooks */
|
|
85
85
|
onBeforeSendChat?: (params: { workspace: string; sessionId: string }) => void;
|
|
86
|
+
|
|
87
|
+
/** Relays a command to a remote mesh node daemon */
|
|
88
|
+
dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
// ─── Result ───
|
|
@@ -100,6 +103,7 @@ export interface DaemonComponents {
|
|
|
100
103
|
sessionRegistry: SessionRegistry;
|
|
101
104
|
detectedIdes: { value: IDEInfo[] };
|
|
102
105
|
refreshProviderAvailability: (providerType?: string) => Promise<void>;
|
|
106
|
+
dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
export interface DaemonDevSupportOptions {
|
|
@@ -331,6 +335,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
331
335
|
sessionRegistry,
|
|
332
336
|
detectedIdes: detectedIdesRef,
|
|
333
337
|
refreshProviderAvailability,
|
|
338
|
+
dispatchMeshCommand: config.dispatchMeshCommand,
|
|
334
339
|
};
|
|
335
340
|
|
|
336
341
|
// 11. Setup Mesh Event Forwarding
|
|
@@ -195,6 +195,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
195
195
|
|
|
196
196
|
// ─── CLI Scripts (script-based parsing) ───
|
|
197
197
|
private cliScripts: CliScripts;
|
|
198
|
+
/** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
|
|
199
|
+
private scriptState: unknown = null;
|
|
198
200
|
private runtimeSettings: Record<string, any> = {};
|
|
199
201
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
200
202
|
private accumulatedBuffer: string = '';
|
|
@@ -223,6 +225,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
223
225
|
currentTurnScope: TurnParseScope | null;
|
|
224
226
|
recentOutputBuffer: string;
|
|
225
227
|
accumulatedBuffer: string;
|
|
228
|
+
accumulatedRawBufferKey: string;
|
|
226
229
|
screenText: string;
|
|
227
230
|
currentStatus: CliSessionStatus['status'];
|
|
228
231
|
activeModal: { message: string; buttons: string[] } | null;
|
|
@@ -297,14 +300,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
297
300
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
298
301
|
}
|
|
299
302
|
|
|
303
|
+
private getAccumulatedRawBufferCacheKey(): string {
|
|
304
|
+
return this.accumulatedRawBuffer
|
|
305
|
+
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
|
|
306
|
+
.replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, '')
|
|
307
|
+
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
308
|
+
}
|
|
309
|
+
|
|
300
310
|
private getFreshParsedStatusCache(): any | null {
|
|
301
311
|
const cached = this.parsedStatusCache;
|
|
312
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
302
313
|
if (
|
|
303
314
|
cached
|
|
304
315
|
&& cached.responseBuffer === this.responseBuffer
|
|
305
316
|
&& cached.currentTurnScope === this.currentTurnScope
|
|
306
317
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
307
318
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
319
|
+
&& cached.accumulatedRawBufferKey === accumulatedRawBufferKey
|
|
308
320
|
&& cached.screenText === this.lastScreenText
|
|
309
321
|
&& cached.currentStatus === this.currentStatus
|
|
310
322
|
&& cached.activeModal === this.activeModal
|
|
@@ -448,6 +460,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
448
460
|
|
|
449
461
|
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
450
462
|
this.cliScripts = provider.scripts || {};
|
|
463
|
+
this.scriptState = typeof this.cliScripts.createState === 'function' ? (this.cliScripts.createState() ?? null) : null;
|
|
451
464
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
452
465
|
if (scriptNames.length > 0) {
|
|
453
466
|
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
@@ -477,6 +490,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
477
490
|
this.cliScripts = scripts;
|
|
478
491
|
this.parsedStatusCache = null;
|
|
479
492
|
this.parseErrorMessage = null;
|
|
493
|
+
// Initialize per-session state: createState() is called once here and on script reload.
|
|
494
|
+
// The returned object lives until the PTY exits (scriptState = null on exit).
|
|
495
|
+
this.scriptState = typeof scripts.createState === 'function' ? (scripts.createState() ?? null) : null;
|
|
480
496
|
const scriptNames = listCliScriptNames(scripts);
|
|
481
497
|
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
482
498
|
}
|
|
@@ -610,6 +626,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
610
626
|
this.ready = false;
|
|
611
627
|
this.startupParseGate = false;
|
|
612
628
|
this.spawnAt = 0;
|
|
629
|
+
this.scriptState = null;
|
|
613
630
|
this.onStatusChange?.();
|
|
614
631
|
});
|
|
615
632
|
|
|
@@ -1450,6 +1467,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1450
1467
|
|
|
1451
1468
|
// ─── Script Execution ──────────────────────────
|
|
1452
1469
|
|
|
1470
|
+
private invokeCliScript<T>(script: Function, input: any): T {
|
|
1471
|
+
const hasStateFactory = typeof this.cliScripts?.createState === 'function';
|
|
1472
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
1473
|
+
return expectsStateArgument
|
|
1474
|
+
? script(this.scriptState, input)
|
|
1475
|
+
: script(input);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1453
1478
|
private runParseSession(): ParsedSession | null {
|
|
1454
1479
|
if (typeof this.cliScripts?.parseSession !== 'function') {
|
|
1455
1480
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -1470,7 +1495,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1470
1495
|
scope: this.currentTurnScope,
|
|
1471
1496
|
runtimeSettings: this.runtimeSettings,
|
|
1472
1497
|
});
|
|
1473
|
-
const session = this.
|
|
1498
|
+
const session = this.invokeCliScript<ParsedSession | null>(
|
|
1499
|
+
this.cliScripts.parseSession,
|
|
1500
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) },
|
|
1501
|
+
);
|
|
1474
1502
|
this.parseErrorMessage = null;
|
|
1475
1503
|
return session && typeof session === 'object' ? session : null;
|
|
1476
1504
|
} catch (e: any) {
|
|
@@ -1485,7 +1513,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1485
1513
|
if (!this.cliScripts?.detectStatus) return null;
|
|
1486
1514
|
try {
|
|
1487
1515
|
const screenText = this.terminalScreen.getText();
|
|
1488
|
-
const status = this.cliScripts.detectStatus
|
|
1516
|
+
const status = this.invokeCliScript<string | null>(this.cliScripts.detectStatus, {
|
|
1489
1517
|
tail: text.slice(-500),
|
|
1490
1518
|
screenText,
|
|
1491
1519
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -1505,7 +1533,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1505
1533
|
try {
|
|
1506
1534
|
const screenText = this.terminalScreen.getText();
|
|
1507
1535
|
const buffer = screenText || this.accumulatedBuffer;
|
|
1508
|
-
return this.cliScripts.parseApproval
|
|
1536
|
+
return this.invokeCliScript<{ message: string; buttons: string[] } | null>(this.cliScripts.parseApproval, {
|
|
1509
1537
|
buffer,
|
|
1510
1538
|
screenText,
|
|
1511
1539
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -1570,12 +1598,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1570
1598
|
const screenText = this.readTerminalScreenText();
|
|
1571
1599
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
1572
1600
|
const cached = this.parsedStatusCache;
|
|
1601
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
1573
1602
|
if (
|
|
1574
1603
|
cached
|
|
1575
1604
|
&& cached.responseBuffer === this.responseBuffer
|
|
1576
1605
|
&& cached.currentTurnScope === this.currentTurnScope
|
|
1577
1606
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
1578
1607
|
&& cached.accumulatedBuffer === this.accumulatedBuffer
|
|
1608
|
+
&& cached.accumulatedRawBufferKey === accumulatedRawBufferKey
|
|
1579
1609
|
&& cached.screenText === parseScreenText
|
|
1580
1610
|
&& cached.currentStatus === this.currentStatus
|
|
1581
1611
|
&& cached.activeModal === this.activeModal
|
|
@@ -1615,6 +1645,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1615
1645
|
currentTurnScope: this.currentTurnScope,
|
|
1616
1646
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
1617
1647
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
1648
|
+
accumulatedRawBufferKey,
|
|
1618
1649
|
screenText: parseScreenText,
|
|
1619
1650
|
currentStatus: this.currentStatus,
|
|
1620
1651
|
activeModal: this.activeModal,
|
|
@@ -1640,7 +1671,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1640
1671
|
scope: this.currentTurnScope,
|
|
1641
1672
|
runtimeSettings: this.runtimeSettings,
|
|
1642
1673
|
});
|
|
1643
|
-
return await Promise.resolve(fn
|
|
1674
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
1644
1675
|
...input,
|
|
1645
1676
|
args: args && typeof args === 'object' ? { ...args } : {},
|
|
1646
1677
|
}));
|
|
@@ -48,11 +48,21 @@ export interface ParsedSession {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export interface CliScripts {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Optional state factory. Called once per CLI session start (or script reload).
|
|
53
|
+
* The returned object is passed as the first argument to detectStatus, parseApproval,
|
|
54
|
+
* and parseSession on every invocation, allowing scripts to maintain per-session state
|
|
55
|
+
* (e.g. last-seen status, approval fingerprints, stability counters).
|
|
56
|
+
*
|
|
57
|
+
* Scripts that don't define createState() receive null as the state argument,
|
|
58
|
+
* making this change fully backward compatible.
|
|
59
|
+
*/
|
|
60
|
+
createState?: () => unknown;
|
|
61
|
+
parseSession?: (state: unknown, input: CliScriptInput & { tail?: string; tailScreen?: CliScreenSnapshot }) => ParsedSession | null;
|
|
62
|
+
detectStatus?: (state: unknown, input: CliStatusInput) => string | null;
|
|
63
|
+
parseApproval?: (state: unknown, input: CliApprovalInput) => { message: string; buttons: string[] } | null;
|
|
54
64
|
resolveAction?: (data: any) => string;
|
|
55
|
-
[name: string]: ((input: any) => any) | undefined;
|
|
65
|
+
[name: string]: ((state: unknown, input: any) => any) | ((data: any) => any) | (() => unknown) | undefined;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
export interface CliScreenLine {
|
|
@@ -177,10 +177,6 @@ export function buildCoordinatorDelegatedCliLaunchOptions(
|
|
|
177
177
|
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
178
178
|
const env: Record<string, string> = { ...(input.env || {}), ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
179
179
|
|
|
180
|
-
if (cliType === 'hermes-cli' && !hasCliArg(cliArgs, '--ignore-user-config')) {
|
|
181
|
-
cliArgs.unshift('--ignore-user-config');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
180
|
if (cliType === 'claude-cli' && !hasCliArg(cliArgs, '--mcp-config')) {
|
|
185
181
|
cliArgs.unshift('--mcp-config', ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
186
182
|
}
|