@adhdev/daemon-core 0.9.82-rc.57 → 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 +431 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +424 -66
- 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/dist/providers/cli-provider-instance.d.ts +1 -0
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-runtime.ts +3 -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 +42 -8
- package/src/mesh/mesh-ledger.ts +135 -0
- package/src/mesh/mesh-work-queue.ts +54 -1
- package/src/providers/cli-provider-instance.ts +66 -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.
|
|
@@ -101,6 +101,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
101
101
|
private completedDebouncePending;
|
|
102
102
|
private enforceFreshSessionLaunchIfNeeded;
|
|
103
103
|
private completionHasFinalAssistantMessage;
|
|
104
|
+
private buildCompletedFinalizationDiagnostic;
|
|
104
105
|
private hasAdapterPendingResponse;
|
|
105
106
|
private shouldSuppressStaleParsedBusyStatus;
|
|
106
107
|
private getCompletedFinalizationBlockReason;
|
package/package.json
CHANGED
|
@@ -36,7 +36,9 @@ export function resolveCliSpawnPlan(options: {
|
|
|
36
36
|
: spawnConfig.command;
|
|
37
37
|
const binaryPath = findBinary(configuredCommand);
|
|
38
38
|
const isWin = os.platform() === 'win32';
|
|
39
|
-
const allArgs = [...spawnConfig.args, ...extraArgs]
|
|
39
|
+
const allArgs = [...spawnConfig.args, ...extraArgs].map((arg) =>
|
|
40
|
+
typeof arg === 'string' ? arg.replace(/\{\{workingDir\}\}/g, workingDir) : arg,
|
|
41
|
+
);
|
|
40
42
|
|
|
41
43
|
let shellCmd: string;
|
|
42
44
|
let shellArgs: string[];
|
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)
|
|
@@ -135,10 +145,21 @@ function isMeshCoordinatorEvent(eventName: unknown): eventName is string {
|
|
|
135
145
|
}
|
|
136
146
|
|
|
137
147
|
function formatCompletionMetadata(event: Record<string, unknown>): string {
|
|
148
|
+
const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === 'object'
|
|
149
|
+
? event.completionDiagnostic as Record<string, unknown>
|
|
150
|
+
: null;
|
|
151
|
+
const diagnosticReason = completionDiagnostic
|
|
152
|
+
? readNonEmptyString(completionDiagnostic.blockReason) || 'present'
|
|
153
|
+
: '';
|
|
154
|
+
const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === 'boolean'
|
|
155
|
+
? String(completionDiagnostic.finalAssistantPresent)
|
|
156
|
+
: '';
|
|
138
157
|
const parts = [
|
|
139
158
|
readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
|
|
140
159
|
readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
|
|
141
160
|
readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
|
|
161
|
+
diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : '',
|
|
162
|
+
finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : '',
|
|
142
163
|
].filter(Boolean);
|
|
143
164
|
return parts.length > 0 ? ` (${parts.join('; ')})` : '';
|
|
144
165
|
}
|
|
@@ -748,6 +769,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
748
769
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
749
770
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
750
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);
|
|
751
775
|
const completedTask = sessionId
|
|
752
776
|
? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
|
|
753
777
|
: null;
|
|
@@ -764,15 +788,17 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
764
788
|
nodeLabel: args.nodeLabel,
|
|
765
789
|
taskId: completedTask.id,
|
|
766
790
|
completedViaReady: true,
|
|
767
|
-
providerSessionId
|
|
768
|
-
finalSummary
|
|
791
|
+
providerSessionId,
|
|
792
|
+
finalSummary,
|
|
793
|
+
workerResult,
|
|
769
794
|
evidence: buildTaskCompletionEvidence({
|
|
770
795
|
event: 'agent:ready',
|
|
771
796
|
nodeId,
|
|
772
797
|
sessionId,
|
|
773
798
|
providerType: providerType || undefined,
|
|
774
|
-
providerSessionId
|
|
775
|
-
finalSummary
|
|
799
|
+
providerSessionId,
|
|
800
|
+
finalSummary,
|
|
801
|
+
workerResult,
|
|
776
802
|
}),
|
|
777
803
|
},
|
|
778
804
|
});
|
|
@@ -815,14 +841,18 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
815
841
|
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined;
|
|
816
842
|
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined;
|
|
817
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);
|
|
818
847
|
const completionEvidence = ledgerKind === 'task_completed' && ledgerNodeId && ledgerSessionId
|
|
819
848
|
? buildTaskCompletionEvidence({
|
|
820
849
|
event: 'agent:generating_completed',
|
|
821
850
|
nodeId: ledgerNodeId,
|
|
822
851
|
sessionId: ledgerSessionId,
|
|
823
852
|
providerType: ledgerProviderType,
|
|
824
|
-
providerSessionId
|
|
825
|
-
finalSummary
|
|
853
|
+
providerSessionId,
|
|
854
|
+
finalSummary,
|
|
855
|
+
workerResult,
|
|
826
856
|
})
|
|
827
857
|
: undefined;
|
|
828
858
|
appendLedgerEntry(args.meshId, {
|
|
@@ -834,8 +864,12 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
834
864
|
event: args.event,
|
|
835
865
|
nodeLabel: args.nodeLabel,
|
|
836
866
|
taskId: completedTaskForLedger?.id || undefined,
|
|
837
|
-
providerSessionId
|
|
838
|
-
finalSummary
|
|
867
|
+
providerSessionId,
|
|
868
|
+
finalSummary,
|
|
869
|
+
workerResult,
|
|
870
|
+
completionDiagnostic: args.metadataEvent.completionDiagnostic && typeof args.metadataEvent.completionDiagnostic === 'object'
|
|
871
|
+
? args.metadataEvent.completionDiagnostic
|
|
872
|
+
: undefined,
|
|
839
873
|
evidence: completionEvidence,
|
|
840
874
|
},
|
|
841
875
|
});
|