@adhdev/daemon-core 0.9.82-rc.58 → 0.9.82-rc.59
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/index.d.ts +6 -4
- package/dist/index.js +368 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +361 -65
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-active-work.d.ts +48 -0
- package/dist/mesh/mesh-ledger.d.ts +37 -0
- package/dist/mesh/mesh-work-queue.d.ts +12 -0
- package/package.json +1 -1
- package/src/commands/router.ts +7 -1
- package/src/index.ts +6 -4
- package/src/mesh/mesh-active-work.ts +205 -0
- package/src/mesh/mesh-events.ts +28 -8
- package/src/mesh/mesh-ledger.ts +135 -0
- package/src/mesh/mesh-work-queue.ts +54 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
2
|
+
import type { MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
3
|
+
export type MeshActiveWorkSource = 'queue' | 'direct';
|
|
4
|
+
export type MeshActiveWorkStatus = 'pending' | 'assigned' | 'generating' | 'idle' | 'failed' | 'awaiting_approval';
|
|
5
|
+
export interface MeshActiveWorkRecord {
|
|
6
|
+
taskId: string;
|
|
7
|
+
source: MeshActiveWorkSource;
|
|
8
|
+
status: MeshActiveWorkStatus;
|
|
9
|
+
nodeId?: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
providerType?: string;
|
|
12
|
+
taskTitle: string;
|
|
13
|
+
taskSummary: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
taskMode?: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
dispatchedAt?: string;
|
|
19
|
+
elapsedMs: number;
|
|
20
|
+
terminal?: boolean;
|
|
21
|
+
terminalKind?: string;
|
|
22
|
+
terminalAt?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface MeshActiveWorkSummary {
|
|
25
|
+
totalActiveCount: number;
|
|
26
|
+
queueActiveCount: number;
|
|
27
|
+
directActiveCount: number;
|
|
28
|
+
awaitingApprovalCount: number;
|
|
29
|
+
generatingCount: number;
|
|
30
|
+
failedCount: number;
|
|
31
|
+
idleCount: number;
|
|
32
|
+
sourceCounts: Record<MeshActiveWorkSource, number>;
|
|
33
|
+
statusCounts: Record<MeshActiveWorkStatus, number>;
|
|
34
|
+
}
|
|
35
|
+
export interface BuildMeshActiveWorkOptions {
|
|
36
|
+
meshId: string;
|
|
37
|
+
queue?: MeshWorkQueueEntry[];
|
|
38
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
39
|
+
nodes?: any[];
|
|
40
|
+
now?: number;
|
|
41
|
+
/** Include terminal direct rows (idle/failed) for handoff/recent-work surfaces. Defaults false. */
|
|
42
|
+
includeTerminalDirect?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function buildMeshActiveWorkSummary(activeWork: MeshActiveWorkRecord[]): MeshActiveWorkSummary;
|
|
45
|
+
export declare function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): {
|
|
46
|
+
activeWork: MeshActiveWorkRecord[];
|
|
47
|
+
summary: MeshActiveWorkSummary;
|
|
48
|
+
};
|
|
@@ -25,6 +25,40 @@ export interface MeshLedgerEntry {
|
|
|
25
25
|
payload: Record<string, unknown>;
|
|
26
26
|
}
|
|
27
27
|
export declare function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean;
|
|
28
|
+
export type MeshWorkerResultStatus = 'completed' | 'failed' | 'blocked' | 'partial' | 'unknown';
|
|
29
|
+
export type MeshProcessArtifactKind = 'process' | 'log' | 'port' | 'window' | 'session' | 'file' | 'url' | 'other';
|
|
30
|
+
export interface MeshValidationResultArtifact {
|
|
31
|
+
command?: string;
|
|
32
|
+
status: 'passed' | 'failed' | 'skipped' | 'unknown';
|
|
33
|
+
durationMs?: number;
|
|
34
|
+
outputPath?: string;
|
|
35
|
+
summary?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface MeshProcessArtifact {
|
|
38
|
+
kind: MeshProcessArtifactKind;
|
|
39
|
+
id?: string;
|
|
40
|
+
label?: string;
|
|
41
|
+
locator?: string;
|
|
42
|
+
pid?: number;
|
|
43
|
+
port?: number;
|
|
44
|
+
url?: string;
|
|
45
|
+
path?: string;
|
|
46
|
+
sessionId?: string;
|
|
47
|
+
keepRunning?: boolean;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
export interface MeshWorkerResultArtifact {
|
|
51
|
+
status: MeshWorkerResultStatus;
|
|
52
|
+
classification?: string;
|
|
53
|
+
changedFiles: string[];
|
|
54
|
+
validationResults: MeshValidationResultArtifact[];
|
|
55
|
+
gitStatus?: Record<string, unknown>;
|
|
56
|
+
processArtifacts: MeshProcessArtifact[];
|
|
57
|
+
errors: string[];
|
|
58
|
+
nextAction?: string;
|
|
59
|
+
requiresUserAction: boolean;
|
|
60
|
+
source: 'explicit_metadata' | 'final_summary_json' | 'default';
|
|
61
|
+
}
|
|
28
62
|
export interface MeshTaskCompletionEvidence {
|
|
29
63
|
source: 'agent_status_event';
|
|
30
64
|
event: 'agent:generating_completed' | 'agent:ready';
|
|
@@ -38,6 +72,7 @@ export interface MeshTaskCompletionEvidence {
|
|
|
38
72
|
providerSessionId?: string;
|
|
39
73
|
finalSummaryAvailable: boolean;
|
|
40
74
|
};
|
|
75
|
+
workerResult: MeshWorkerResultArtifact;
|
|
41
76
|
git: {
|
|
42
77
|
status: 'deferred';
|
|
43
78
|
reason: string;
|
|
@@ -59,6 +94,7 @@ export interface BuildTaskCompletionEvidenceOptions {
|
|
|
59
94
|
providerType?: string;
|
|
60
95
|
providerSessionId?: string;
|
|
61
96
|
finalSummary?: string;
|
|
97
|
+
workerResult?: Record<string, unknown>;
|
|
62
98
|
completedAt?: string;
|
|
63
99
|
}
|
|
64
100
|
export interface MeshLedgerSummary {
|
|
@@ -115,6 +151,7 @@ export interface AppendRemoteLedgerResult {
|
|
|
115
151
|
}
|
|
116
152
|
export declare const MAX_LEDGER_SLICE_LIMIT = 500;
|
|
117
153
|
export declare function getLedgerDir(): string;
|
|
154
|
+
export declare function normalizeMeshWorkerResult(input?: Record<string, unknown>, source?: MeshWorkerResultArtifact['source']): MeshWorkerResultArtifact;
|
|
118
155
|
export declare function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence;
|
|
119
156
|
/**
|
|
120
157
|
* Append a new entry to the mesh ledger.
|
|
@@ -2,13 +2,24 @@ import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
|
|
|
2
2
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
3
3
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
4
4
|
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
5
|
+
export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
|
|
5
6
|
export declare const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[];
|
|
6
7
|
export declare const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[];
|
|
8
|
+
export declare const MESH_TASK_MODES: MeshTaskMode[];
|
|
9
|
+
export interface MeshTaskModeValidationResult {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
taskMode?: MeshTaskMode;
|
|
12
|
+
violations: string[];
|
|
13
|
+
allowedOperations?: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined;
|
|
16
|
+
export declare function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult;
|
|
7
17
|
export interface MeshWorkQueueEntry {
|
|
8
18
|
id: string;
|
|
9
19
|
meshId: string;
|
|
10
20
|
message: string;
|
|
11
21
|
status: MeshTaskStatus;
|
|
22
|
+
taskMode?: MeshTaskMode;
|
|
12
23
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
13
24
|
targetNodeId?: string;
|
|
14
25
|
/** If specified, only this runtime session can claim the task */
|
|
@@ -47,6 +58,7 @@ export interface MeshQueueMutationOptions {
|
|
|
47
58
|
export declare function enqueueTask(meshId: string, message: string, opts?: {
|
|
48
59
|
targetNodeId?: string;
|
|
49
60
|
targetSessionId?: string;
|
|
61
|
+
taskMode?: MeshTaskMode | string;
|
|
50
62
|
} & MeshQueueMutationOptions): MeshWorkQueueEntry;
|
|
51
63
|
/**
|
|
52
64
|
* Get all tasks in the queue, optionally filtered by status.
|
package/package.json
CHANGED
package/src/commands/router.ts
CHANGED
|
@@ -460,6 +460,11 @@ function reconcileInlineMeshCache(cached: any, incoming: any): any {
|
|
|
460
460
|
const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
|
|
461
461
|
if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
|
|
462
462
|
|
|
463
|
+
const cachedUpdatedAt = Date.parse(readStringValue(cached.updatedAt, cached.updated_at) || '');
|
|
464
|
+
const incomingUpdatedAt = Date.parse(readStringValue(incoming.updatedAt, incoming.updated_at) || '');
|
|
465
|
+
const preserveCachedMembership = Number.isFinite(cachedUpdatedAt)
|
|
466
|
+
&& (!Number.isFinite(incomingUpdatedAt) || cachedUpdatedAt > incomingUpdatedAt);
|
|
467
|
+
|
|
463
468
|
const cachedById = new Map<string, any>();
|
|
464
469
|
for (const node of cachedNodes) {
|
|
465
470
|
const nodeId = readInlineMeshNodeId(node);
|
|
@@ -469,12 +474,13 @@ function reconcileInlineMeshCache(cached: any, incoming: any): any {
|
|
|
469
474
|
const nodes = incomingNodes.map((incomingNode: any) => {
|
|
470
475
|
const nodeId = readInlineMeshNodeId(incomingNode);
|
|
471
476
|
const cachedNode = nodeId ? cachedById.get(nodeId) : undefined;
|
|
477
|
+
if (!cachedNode && preserveCachedMembership) return null;
|
|
472
478
|
if (!cachedNode) return incomingNode;
|
|
473
479
|
if (hasInlineMeshTransientNodeState(incomingNode)) {
|
|
474
480
|
return { ...cachedNode, ...incomingNode };
|
|
475
481
|
}
|
|
476
482
|
return { ...stripInlineMeshTransientNodeState(cachedNode), ...incomingNode };
|
|
477
|
-
});
|
|
483
|
+
}).filter(Boolean);
|
|
478
484
|
|
|
479
485
|
return {
|
|
480
486
|
...cached,
|
package/src/index.ts
CHANGED
|
@@ -178,16 +178,18 @@ export { syncMeshes } from './mesh/mesh-sync.js';
|
|
|
178
178
|
export type { MeshSyncTransport, MeshSyncResult, RemoteMeshRecord } from './mesh/mesh-sync.js';
|
|
179
179
|
|
|
180
180
|
// ── Mesh Task Ledger ──
|
|
181
|
-
export { appendLedgerEntry, appendRemoteLedgerEntries, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
182
|
-
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext } from './mesh/mesh-ledger.js';
|
|
181
|
+
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
182
|
+
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext, MeshTaskCompletionEvidence, MeshWorkerResultArtifact, MeshProcessArtifact, MeshValidationResultArtifact } from './mesh/mesh-ledger.js';
|
|
183
183
|
export { fastForwardMeshNode } from './mesh/mesh-fast-forward.js';
|
|
184
184
|
export type { MeshFastForwardNodeArgs, MeshFastForwardPlannedStep, MeshFastForwardResult } from './mesh/mesh-fast-forward.js';
|
|
185
185
|
export { buildMeshLedgerReconciliationEvidence, buildMeshLedgerReplicaEvidence } from './mesh/mesh-ledger-reconciliation.js';
|
|
186
186
|
export type { MeshLedgerReconciliationEvidence, MeshLedgerReplicaEvidence, MeshLedgerReplicaStatus } from './mesh/mesh-ledger-reconciliation.js';
|
|
187
187
|
|
|
188
188
|
// ── Mesh Work Queue (GUPP) ──
|
|
189
|
-
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats } from './mesh/mesh-work-queue.js';
|
|
190
|
-
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshWorkQueueStats, MeshQueueMutationOptions } from './mesh/mesh-work-queue.js';
|
|
189
|
+
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, normalizeMeshTaskMode, validateMeshTaskModeRequest } from './mesh/mesh-work-queue.js';
|
|
190
|
+
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult } from './mesh/mesh-work-queue.js';
|
|
191
|
+
export { buildMeshActiveWork, buildMeshActiveWorkSummary } from './mesh/mesh-active-work.js';
|
|
192
|
+
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource } from './mesh/mesh-active-work.js';
|
|
191
193
|
|
|
192
194
|
// ── Mesh Host Ownership ──
|
|
193
195
|
export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHostOwner, normalizeMeshDaemonRole, requireMeshHostQueueOwner, resolveMeshHostStatus } from './mesh/mesh-host-ownership.js';
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
2
|
+
import type { MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
3
|
+
|
|
4
|
+
export type MeshActiveWorkSource = 'queue' | 'direct';
|
|
5
|
+
export type MeshActiveWorkStatus = 'pending' | 'assigned' | 'generating' | 'idle' | 'failed' | 'awaiting_approval';
|
|
6
|
+
|
|
7
|
+
export interface MeshActiveWorkRecord {
|
|
8
|
+
taskId: string;
|
|
9
|
+
source: MeshActiveWorkSource;
|
|
10
|
+
status: MeshActiveWorkStatus;
|
|
11
|
+
nodeId?: string;
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
providerType?: string;
|
|
14
|
+
taskTitle: string;
|
|
15
|
+
taskSummary: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
taskMode?: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
dispatchedAt?: string;
|
|
21
|
+
elapsedMs: number;
|
|
22
|
+
terminal?: boolean;
|
|
23
|
+
terminalKind?: string;
|
|
24
|
+
terminalAt?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MeshActiveWorkSummary {
|
|
28
|
+
totalActiveCount: number;
|
|
29
|
+
queueActiveCount: number;
|
|
30
|
+
directActiveCount: number;
|
|
31
|
+
awaitingApprovalCount: number;
|
|
32
|
+
generatingCount: number;
|
|
33
|
+
failedCount: number;
|
|
34
|
+
idleCount: number;
|
|
35
|
+
sourceCounts: Record<MeshActiveWorkSource, number>;
|
|
36
|
+
statusCounts: Record<MeshActiveWorkStatus, number>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BuildMeshActiveWorkOptions {
|
|
40
|
+
meshId: string;
|
|
41
|
+
queue?: MeshWorkQueueEntry[];
|
|
42
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
43
|
+
nodes?: any[];
|
|
44
|
+
now?: number;
|
|
45
|
+
/** Include terminal direct rows (idle/failed) for handoff/recent-work surfaces. Defaults false. */
|
|
46
|
+
includeTerminalDirect?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DIRECT_DISPATCH_VIA = new Set(['p2p_direct', 'local_direct', 'mesh_send_task']);
|
|
50
|
+
const TERMINAL_LEDGER_KINDS = new Set(['task_completed', 'task_failed', 'task_stalled']);
|
|
51
|
+
|
|
52
|
+
function readString(value: unknown): string | undefined {
|
|
53
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function summarizeMessage(message: string): { title: string; summary: string } {
|
|
57
|
+
const oneLine = message.replace(/\s+/g, ' ').trim();
|
|
58
|
+
const title = oneLine.length > 96 ? `${oneLine.slice(0, 93)}...` : oneLine;
|
|
59
|
+
return { title: title || '(untitled task)', summary: oneLine };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function elapsedSince(value: string | undefined, now: number): number {
|
|
63
|
+
const started = value ? new Date(value).getTime() : Number.NaN;
|
|
64
|
+
return Number.isFinite(started) ? Math.max(0, now - started) : 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sessionStatusFromNodes(nodes: any[] | undefined, nodeId?: string, sessionId?: string): MeshActiveWorkStatus | undefined {
|
|
68
|
+
if (!nodeId || !sessionId || !Array.isArray(nodes)) return undefined;
|
|
69
|
+
const node = nodes.find(item => readString(item?.id) === nodeId || readString(item?.nodeId) === nodeId || readString(item?.node_id) === nodeId);
|
|
70
|
+
if (!node) return undefined;
|
|
71
|
+
const candidates: any[] = [];
|
|
72
|
+
for (const value of [node.sessions, node.activeSessions, node.active_sessions, node.lastProbe?.sessions, node.last_probe?.sessions, node.lastProbe?.status?.sessions, node.last_probe?.status?.sessions]) {
|
|
73
|
+
if (Array.isArray(value)) candidates.push(...value);
|
|
74
|
+
}
|
|
75
|
+
for (const value of [node.activeSession, node.active_session, node.currentSession, node.current_session, node.runtimeSession, node.runtime_session, node.session]) {
|
|
76
|
+
if (value && typeof value === 'object') candidates.push(value);
|
|
77
|
+
}
|
|
78
|
+
const session = candidates.find(item => {
|
|
79
|
+
const id = readString(item?.id) || readString(item?.sessionId) || readString(item?.session_id) || readString(item?.runtimeSessionId) || readString(item?.instanceId);
|
|
80
|
+
return id === sessionId;
|
|
81
|
+
});
|
|
82
|
+
if (!session) return undefined;
|
|
83
|
+
const raw = `${readString(session.status) || ''} ${readString(session.lifecycle) || ''} ${readString(session.state) || ''} ${readString(session.activeChat?.status) || ''}`.toLowerCase();
|
|
84
|
+
if (raw.includes('approval')) return 'awaiting_approval';
|
|
85
|
+
if (raw.includes('generating') || raw.includes('running') || raw.includes('busy')) return 'generating';
|
|
86
|
+
if (raw.includes('failed') || raw.includes('stopped') || raw.includes('terminated') || raw.includes('exited')) return 'failed';
|
|
87
|
+
if (raw.includes('idle') || raw.includes('waiting_input') || raw.includes('ready')) return 'idle';
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isDirectDispatch(entry: MeshLedgerEntry): boolean {
|
|
92
|
+
if (entry.kind !== 'task_dispatched') return false;
|
|
93
|
+
const payload = entry.payload || {};
|
|
94
|
+
if (payload.source === 'direct') return true;
|
|
95
|
+
const via = readString(payload.via);
|
|
96
|
+
return Boolean(via && DIRECT_DISPATCH_VIA.has(via) && payload.source !== 'queue');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function directDispatchTaskId(entry: MeshLedgerEntry): string {
|
|
100
|
+
return readString(entry.payload?.taskId) || entry.id;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function terminalMatchesDispatch(terminal: MeshLedgerEntry, dispatch: MeshLedgerEntry, taskId: string): boolean {
|
|
104
|
+
const terminalTaskId = readString(terminal.payload?.taskId);
|
|
105
|
+
if (terminalTaskId && terminalTaskId === taskId) return true;
|
|
106
|
+
if (terminalTaskId && terminalTaskId !== taskId) return false;
|
|
107
|
+
if (dispatch.sessionId && terminal.sessionId === dispatch.sessionId) return true;
|
|
108
|
+
return Boolean(dispatch.nodeId && terminal.nodeId === dispatch.nodeId && !dispatch.sessionId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function statusFromTerminal(entry: MeshLedgerEntry): MeshActiveWorkStatus {
|
|
112
|
+
if (entry.kind === 'task_approval_needed') return 'awaiting_approval';
|
|
113
|
+
if (entry.kind === 'task_completed') return 'idle';
|
|
114
|
+
return 'failed';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildMeshActiveWorkSummary(activeWork: MeshActiveWorkRecord[]): MeshActiveWorkSummary {
|
|
118
|
+
const statusCounts: Record<MeshActiveWorkStatus, number> = {
|
|
119
|
+
pending: 0,
|
|
120
|
+
assigned: 0,
|
|
121
|
+
generating: 0,
|
|
122
|
+
idle: 0,
|
|
123
|
+
failed: 0,
|
|
124
|
+
awaiting_approval: 0,
|
|
125
|
+
};
|
|
126
|
+
const sourceCounts: Record<MeshActiveWorkSource, number> = { queue: 0, direct: 0 };
|
|
127
|
+
for (const item of activeWork) {
|
|
128
|
+
sourceCounts[item.source] += 1;
|
|
129
|
+
statusCounts[item.status] += 1;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
totalActiveCount: activeWork.length,
|
|
133
|
+
queueActiveCount: sourceCounts.queue,
|
|
134
|
+
directActiveCount: sourceCounts.direct,
|
|
135
|
+
awaitingApprovalCount: statusCounts.awaiting_approval,
|
|
136
|
+
generatingCount: statusCounts.generating,
|
|
137
|
+
failedCount: statusCounts.failed,
|
|
138
|
+
idleCount: statusCounts.idle,
|
|
139
|
+
sourceCounts,
|
|
140
|
+
statusCounts,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeWork: MeshActiveWorkRecord[]; summary: MeshActiveWorkSummary } {
|
|
145
|
+
const now = opts.now ?? Date.now();
|
|
146
|
+
const records: MeshActiveWorkRecord[] = [];
|
|
147
|
+
|
|
148
|
+
for (const task of opts.queue || []) {
|
|
149
|
+
if (task.status !== 'pending' && task.status !== 'assigned') continue;
|
|
150
|
+
const { title, summary } = summarizeMessage(task.message || '');
|
|
151
|
+
records.push({
|
|
152
|
+
taskId: task.id,
|
|
153
|
+
source: 'queue',
|
|
154
|
+
status: task.status,
|
|
155
|
+
nodeId: task.assignedNodeId || task.targetNodeId,
|
|
156
|
+
sessionId: task.assignedSessionId || task.targetSessionId,
|
|
157
|
+
taskTitle: title,
|
|
158
|
+
taskSummary: summary,
|
|
159
|
+
message: task.message,
|
|
160
|
+
taskMode: task.taskMode,
|
|
161
|
+
createdAt: task.createdAt,
|
|
162
|
+
updatedAt: task.updatedAt,
|
|
163
|
+
dispatchedAt: task.dispatchTimestamp,
|
|
164
|
+
elapsedMs: elapsedSince(task.dispatchTimestamp || task.createdAt, now),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
169
|
+
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
170
|
+
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
|
171
|
+
const taskId = directDispatchTaskId(dispatch);
|
|
172
|
+
const terminal = terminals
|
|
173
|
+
.filter(entry => new Date(entry.timestamp).getTime() >= new Date(dispatch.timestamp).getTime())
|
|
174
|
+
.find(entry => terminalMatchesDispatch(entry, dispatch, taskId));
|
|
175
|
+
const terminalStatus = terminal ? statusFromTerminal(terminal) : undefined;
|
|
176
|
+
const liveStatus = sessionStatusFromNodes(opts.nodes, dispatch.nodeId, dispatch.sessionId);
|
|
177
|
+
const status = terminalStatus || liveStatus || 'assigned';
|
|
178
|
+
const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
|
|
179
|
+
if (terminalRow && opts.includeTerminalDirect !== true) continue;
|
|
180
|
+
const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
|
|
181
|
+
const { title, summary } = summarizeMessage(message);
|
|
182
|
+
records.push({
|
|
183
|
+
taskId,
|
|
184
|
+
source: 'direct',
|
|
185
|
+
status,
|
|
186
|
+
nodeId: dispatch.nodeId,
|
|
187
|
+
sessionId: dispatch.sessionId,
|
|
188
|
+
providerType: dispatch.providerType || readString(dispatch.payload?.providerType),
|
|
189
|
+
taskTitle: readString(dispatch.payload?.taskTitle) || title,
|
|
190
|
+
taskSummary: readString(dispatch.payload?.taskSummary) || summary,
|
|
191
|
+
message,
|
|
192
|
+
taskMode: readString(dispatch.payload?.taskMode),
|
|
193
|
+
createdAt: dispatch.timestamp,
|
|
194
|
+
updatedAt: terminal?.timestamp || dispatch.timestamp,
|
|
195
|
+
dispatchedAt: dispatch.timestamp,
|
|
196
|
+
elapsedMs: elapsedSince(dispatch.timestamp, now),
|
|
197
|
+
terminal: terminalRow,
|
|
198
|
+
terminalKind: terminal?.kind,
|
|
199
|
+
terminalAt: terminal?.timestamp,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
records.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
204
|
+
return { activeWork: records, summary: buildMeshActiveWorkSummary(records) };
|
|
205
|
+
}
|
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -25,6 +25,10 @@ interface RemoteIdleSession {
|
|
|
25
25
|
const REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
26
26
|
const remoteIdleSessions = new Map<string, RemoteIdleSession>(); // key: `${nodeId}:${sessionId}`
|
|
27
27
|
|
|
28
|
+
function readWorkerResultMetadata(event: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
29
|
+
return readRecord(event.workerResult) || readRecord(event.meshWorkerResult) || readRecord(event.structuredResult);
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
function sweepExpiredRemoteIdleSessions(): void {
|
|
29
33
|
const now = Date.now();
|
|
30
34
|
for (const [key, session] of remoteIdleSessions) {
|
|
@@ -107,6 +111,12 @@ function readNonEmptyString(value: unknown): string {
|
|
|
107
111
|
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
115
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
116
|
+
? value as Record<string, unknown>
|
|
117
|
+
: undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
110
120
|
function resolveEventSessionId(event: Record<string, unknown>, fallback?: unknown): string {
|
|
111
121
|
return readNonEmptyString(event.targetSessionId)
|
|
112
122
|
|| readNonEmptyString(event.sessionId)
|
|
@@ -759,6 +769,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
759
769
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
760
770
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
761
771
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
772
|
+
const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
|
|
773
|
+
const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
|
|
774
|
+
const workerResult = readWorkerResultMetadata(args.metadataEvent);
|
|
762
775
|
const completedTask = sessionId
|
|
763
776
|
? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
|
|
764
777
|
: null;
|
|
@@ -775,15 +788,17 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
775
788
|
nodeLabel: args.nodeLabel,
|
|
776
789
|
taskId: completedTask.id,
|
|
777
790
|
completedViaReady: true,
|
|
778
|
-
providerSessionId
|
|
779
|
-
finalSummary
|
|
791
|
+
providerSessionId,
|
|
792
|
+
finalSummary,
|
|
793
|
+
workerResult,
|
|
780
794
|
evidence: buildTaskCompletionEvidence({
|
|
781
795
|
event: 'agent:ready',
|
|
782
796
|
nodeId,
|
|
783
797
|
sessionId,
|
|
784
798
|
providerType: providerType || undefined,
|
|
785
|
-
providerSessionId
|
|
786
|
-
finalSummary
|
|
799
|
+
providerSessionId,
|
|
800
|
+
finalSummary,
|
|
801
|
+
workerResult,
|
|
787
802
|
}),
|
|
788
803
|
},
|
|
789
804
|
});
|
|
@@ -826,14 +841,18 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
826
841
|
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined;
|
|
827
842
|
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined;
|
|
828
843
|
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || undefined;
|
|
844
|
+
const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
|
|
845
|
+
const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
|
|
846
|
+
const workerResult = readWorkerResultMetadata(args.metadataEvent);
|
|
829
847
|
const completionEvidence = ledgerKind === 'task_completed' && ledgerNodeId && ledgerSessionId
|
|
830
848
|
? buildTaskCompletionEvidence({
|
|
831
849
|
event: 'agent:generating_completed',
|
|
832
850
|
nodeId: ledgerNodeId,
|
|
833
851
|
sessionId: ledgerSessionId,
|
|
834
852
|
providerType: ledgerProviderType,
|
|
835
|
-
providerSessionId
|
|
836
|
-
finalSummary
|
|
853
|
+
providerSessionId,
|
|
854
|
+
finalSummary,
|
|
855
|
+
workerResult,
|
|
837
856
|
})
|
|
838
857
|
: undefined;
|
|
839
858
|
appendLedgerEntry(args.meshId, {
|
|
@@ -845,8 +864,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
845
864
|
event: args.event,
|
|
846
865
|
nodeLabel: args.nodeLabel,
|
|
847
866
|
taskId: completedTaskForLedger?.id || undefined,
|
|
848
|
-
providerSessionId
|
|
849
|
-
finalSummary
|
|
867
|
+
providerSessionId,
|
|
868
|
+
finalSummary,
|
|
869
|
+
workerResult,
|
|
850
870
|
completionDiagnostic: args.metadataEvent.completionDiagnostic && typeof args.metadataEvent.completionDiagnostic === 'object'
|
|
851
871
|
? args.metadataEvent.completionDiagnostic
|
|
852
872
|
: undefined,
|