@adhdev/daemon-core 0.9.77-rc.49 → 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/commands/router.d.ts +1 -0
- package/dist/index.js +161 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +161 -12
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +8 -0
- package/dist/mesh/mesh-ledger.d.ts +38 -0
- package/package.json +1 -1
- package/src/commands/router.ts +54 -2
- package/src/mesh/mesh-events.ts +90 -4
- package/src/mesh/mesh-ledger.ts +88 -1
|
@@ -17,6 +17,14 @@ export declare function triggerMeshQueue(components: DaemonComponents, meshId: s
|
|
|
17
17
|
export declare function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>): {
|
|
18
18
|
success: boolean;
|
|
19
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;
|
|
20
28
|
error?: undefined;
|
|
21
29
|
} | {
|
|
22
30
|
success: boolean;
|
|
@@ -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;
|
|
@@ -78,6 +115,7 @@ export interface AppendRemoteLedgerResult {
|
|
|
78
115
|
}
|
|
79
116
|
export declare const MAX_LEDGER_SLICE_LIMIT = 500;
|
|
80
117
|
export declare function getLedgerDir(): string;
|
|
118
|
+
export declare function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence;
|
|
81
119
|
/**
|
|
82
120
|
* Append a new entry to the mesh ledger.
|
|
83
121
|
* Handles file creation, rotation on size overflow, and atomic writes.
|
package/package.json
CHANGED
package/src/commands/router.ts
CHANGED
|
@@ -882,6 +882,36 @@ export class DaemonCommandRouter {
|
|
|
882
882
|
return record?.lifecycle === 'stopped' || record?.lifecycle === 'failed' || record?.lifecycle === 'interrupted';
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
+
private async recordIntentionalMeshSessionStop(args: {
|
|
886
|
+
meshId: string;
|
|
887
|
+
nodeId: string;
|
|
888
|
+
node: any;
|
|
889
|
+
sessionId: string;
|
|
890
|
+
mode: RepoMeshSessionCleanupMode;
|
|
891
|
+
source: 'mesh_cleanup_sessions' | 'mesh_remove_node';
|
|
892
|
+
action: 'stop_session' | 'delete_session_force';
|
|
893
|
+
}): Promise<void> {
|
|
894
|
+
try {
|
|
895
|
+
const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
|
|
896
|
+
appendLedgerEntry(args.meshId, {
|
|
897
|
+
kind: 'session_stopped',
|
|
898
|
+
nodeId: args.nodeId,
|
|
899
|
+
sessionId: args.sessionId,
|
|
900
|
+
payload: {
|
|
901
|
+
intentional: true,
|
|
902
|
+
reason: 'operator_cleanup',
|
|
903
|
+
intentionalStopReason: 'operator_cleanup',
|
|
904
|
+
source: args.source,
|
|
905
|
+
cleanupMode: args.mode,
|
|
906
|
+
action: args.action,
|
|
907
|
+
workspace: typeof args.node?.workspace === 'string' ? args.node.workspace : undefined,
|
|
908
|
+
},
|
|
909
|
+
});
|
|
910
|
+
} catch (e: any) {
|
|
911
|
+
LOG.warn('MeshCleanup', `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
885
915
|
private async cleanupMeshSessions(args: {
|
|
886
916
|
meshId: string;
|
|
887
917
|
nodeId: string;
|
|
@@ -889,6 +919,7 @@ export class DaemonCommandRouter {
|
|
|
889
919
|
mode: RepoMeshSessionCleanupMode;
|
|
890
920
|
sessionIds?: string[];
|
|
891
921
|
dryRun?: boolean;
|
|
922
|
+
source?: 'mesh_cleanup_sessions' | 'mesh_remove_node';
|
|
892
923
|
}): Promise<{ success: boolean; [key: string]: unknown }> {
|
|
893
924
|
if (args.mode === 'preserve') {
|
|
894
925
|
return { success: true, mode: 'preserve', matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -908,6 +939,21 @@ export class DaemonCommandRouter {
|
|
|
908
939
|
const deleteUnsupportedSessionIds: string[] = [];
|
|
909
940
|
const recordsRemainSessionIds: string[] = [];
|
|
910
941
|
const errors: Array<{ sessionId: string; error: string }> = [];
|
|
942
|
+
const cleanupSource = args.source || 'mesh_cleanup_sessions';
|
|
943
|
+
const markedIntentionalStopSessionIds = new Set<string>();
|
|
944
|
+
const markIntentionalStop = async (sessionId: string, action: 'stop_session' | 'delete_session_force') => {
|
|
945
|
+
if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
|
|
946
|
+
markedIntentionalStopSessionIds.add(sessionId);
|
|
947
|
+
await this.recordIntentionalMeshSessionStop({
|
|
948
|
+
meshId: args.meshId,
|
|
949
|
+
nodeId: args.nodeId,
|
|
950
|
+
node: args.node,
|
|
951
|
+
sessionId,
|
|
952
|
+
mode: args.mode,
|
|
953
|
+
source: cleanupSource,
|
|
954
|
+
action,
|
|
955
|
+
});
|
|
956
|
+
};
|
|
911
957
|
const matchedBySurfaceKind = {
|
|
912
958
|
live_runtime: 0,
|
|
913
959
|
recovery_snapshot: 0,
|
|
@@ -932,7 +978,10 @@ export class DaemonCommandRouter {
|
|
|
932
978
|
try {
|
|
933
979
|
if (args.mode === 'stop') {
|
|
934
980
|
if (!completed) {
|
|
935
|
-
if (!args.dryRun)
|
|
981
|
+
if (!args.dryRun) {
|
|
982
|
+
await markIntentionalStop(sessionId, 'stop_session');
|
|
983
|
+
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
984
|
+
}
|
|
936
985
|
stoppedSessionIds.push(sessionId);
|
|
937
986
|
} else {
|
|
938
987
|
skippedSessionIds.push(sessionId);
|
|
@@ -951,6 +1000,7 @@ export class DaemonCommandRouter {
|
|
|
951
1000
|
}
|
|
952
1001
|
|
|
953
1002
|
if (args.mode === 'stop_and_delete') {
|
|
1003
|
+
if (!completed) await markIntentionalStop(sessionId, 'delete_session_force');
|
|
954
1004
|
if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
|
|
955
1005
|
deletedSessionIds.push(sessionId);
|
|
956
1006
|
continue;
|
|
@@ -963,6 +1013,7 @@ export class DaemonCommandRouter {
|
|
|
963
1013
|
recordsRemainSessionIds.push(sessionId);
|
|
964
1014
|
if (args.mode === 'stop_and_delete' && !completed) {
|
|
965
1015
|
try {
|
|
1016
|
+
await markIntentionalStop(sessionId, 'stop_session');
|
|
966
1017
|
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
967
1018
|
stoppedSessionIds.push(sessionId);
|
|
968
1019
|
} catch (stopError: any) {
|
|
@@ -1989,6 +2040,7 @@ export class DaemonCommandRouter {
|
|
|
1989
2040
|
mode,
|
|
1990
2041
|
sessionIds,
|
|
1991
2042
|
dryRun: args?.dryRun === true,
|
|
2043
|
+
source: 'mesh_cleanup_sessions',
|
|
1992
2044
|
});
|
|
1993
2045
|
return result;
|
|
1994
2046
|
} catch (e: any) {
|
|
@@ -2137,7 +2189,7 @@ export class DaemonCommandRouter {
|
|
|
2137
2189
|
);
|
|
2138
2190
|
let sessionCleanup: Record<string, unknown> | undefined;
|
|
2139
2191
|
if (node && sessionCleanupMode !== 'preserve') {
|
|
2140
|
-
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
2192
|
+
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: 'mesh_remove_node' });
|
|
2141
2193
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
2142
2194
|
}
|
|
2143
2195
|
|
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { loadConfig } from '../config/config.js';
|
|
|
3
3
|
import { getMesh, getMeshByRepo } from '../config/mesh-config.js';
|
|
4
4
|
import { detectCLI } from '../detection/cli-detector.js';
|
|
5
5
|
import { LOG } from '../logging/logger.js';
|
|
6
|
-
import { appendLedgerEntry, getSessionRecoveryContext } from './mesh-ledger.js';
|
|
6
|
+
import { appendLedgerEntry, buildTaskCompletionEvidence, getSessionRecoveryContext, isIntentionalCleanupStopEntry, readLedgerEntries } from './mesh-ledger.js';
|
|
7
7
|
import type { MeshLedgerKind, SessionRecoveryContext } from './mesh-ledger.js';
|
|
8
8
|
import { claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch } from './mesh-work-queue.js';
|
|
9
9
|
|
|
@@ -90,6 +90,46 @@ function getMeshWithCache(components: DaemonComponents, meshId: string): any | u
|
|
|
90
90
|
return components.router?.getCachedInlineMesh(meshId);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
const INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1000;
|
|
94
|
+
|
|
95
|
+
function isIntentionalCleanupStopMetadata(event: Record<string, unknown>): boolean {
|
|
96
|
+
return event.intentional === true
|
|
97
|
+
|| event.intentionalStop === true
|
|
98
|
+
|| event.operatorCleanup === true
|
|
99
|
+
|| event.reason === 'operator_cleanup'
|
|
100
|
+
|| event.stopReason === 'operator_cleanup'
|
|
101
|
+
|| event.cleanupReason === 'operator_cleanup'
|
|
102
|
+
|| event.source === 'mesh_cleanup_sessions'
|
|
103
|
+
|| event.source === 'mesh_remove_node';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hasRecentIntentionalCleanupStop(meshId: string, sessionId?: string, nodeId?: string): boolean {
|
|
107
|
+
if (!sessionId && !nodeId) return false;
|
|
108
|
+
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
109
|
+
const entries = readLedgerEntries(meshId);
|
|
110
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
111
|
+
const entry = entries[i];
|
|
112
|
+
const timestamp = new Date(entry.timestamp).getTime();
|
|
113
|
+
if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
|
|
114
|
+
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
115
|
+
if (sessionId && entry.sessionId === sessionId) return true;
|
|
116
|
+
if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function shouldSuppressIntentionalCleanupStop(args: {
|
|
122
|
+
event: string;
|
|
123
|
+
meshId: string;
|
|
124
|
+
metadataEvent: Record<string, unknown>;
|
|
125
|
+
sessionId?: string;
|
|
126
|
+
nodeId?: string;
|
|
127
|
+
}): boolean {
|
|
128
|
+
if (args.event !== 'agent:stopped' && args.event !== 'monitor:long_generating') return false;
|
|
129
|
+
if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
|
|
130
|
+
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
131
|
+
}
|
|
132
|
+
|
|
93
133
|
|
|
94
134
|
export function tryAssignQueueTask(
|
|
95
135
|
components: DaemonComponents,
|
|
@@ -526,6 +566,23 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
526
566
|
event: string;
|
|
527
567
|
metadataEvent: Record<string, unknown>;
|
|
528
568
|
}) {
|
|
569
|
+
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
570
|
+
const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
571
|
+
const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
|
|
572
|
+
event: args.event,
|
|
573
|
+
meshId: args.meshId,
|
|
574
|
+
metadataEvent: args.metadataEvent,
|
|
575
|
+
sessionId: eventSessionId || undefined,
|
|
576
|
+
nodeId: eventNodeId || undefined,
|
|
577
|
+
});
|
|
578
|
+
if (intentionalCleanupStop) {
|
|
579
|
+
if (eventSessionId && eventNodeId) {
|
|
580
|
+
remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
|
|
581
|
+
}
|
|
582
|
+
LOG.info('MeshEvents', `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || '(unknown session)'}`);
|
|
583
|
+
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
584
|
+
}
|
|
585
|
+
|
|
529
586
|
// ── Task Queue & Ledger ──
|
|
530
587
|
let completedTaskForLedger: { id?: string } | null = null;
|
|
531
588
|
if (args.event === 'agent:generating_completed') {
|
|
@@ -565,6 +622,14 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
565
622
|
completedViaReady: true,
|
|
566
623
|
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
|
|
567
624
|
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
|
|
625
|
+
evidence: buildTaskCompletionEvidence({
|
|
626
|
+
event: 'agent:ready',
|
|
627
|
+
nodeId,
|
|
628
|
+
sessionId,
|
|
629
|
+
providerType: providerType || undefined,
|
|
630
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
|
|
631
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
|
|
632
|
+
}),
|
|
568
633
|
},
|
|
569
634
|
});
|
|
570
635
|
} catch (e: any) {
|
|
@@ -601,17 +666,31 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
601
666
|
const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
|
|
602
667
|
if (ledgerKind) {
|
|
603
668
|
try {
|
|
669
|
+
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined;
|
|
670
|
+
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined;
|
|
671
|
+
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || undefined;
|
|
672
|
+
const completionEvidence = ledgerKind === 'task_completed' && ledgerNodeId && ledgerSessionId
|
|
673
|
+
? buildTaskCompletionEvidence({
|
|
674
|
+
event: 'agent:generating_completed',
|
|
675
|
+
nodeId: ledgerNodeId,
|
|
676
|
+
sessionId: ledgerSessionId,
|
|
677
|
+
providerType: ledgerProviderType,
|
|
678
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
|
|
679
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
|
|
680
|
+
})
|
|
681
|
+
: undefined;
|
|
604
682
|
appendLedgerEntry(args.meshId, {
|
|
605
683
|
kind: ledgerKind,
|
|
606
|
-
nodeId:
|
|
607
|
-
sessionId:
|
|
608
|
-
providerType:
|
|
684
|
+
nodeId: ledgerNodeId,
|
|
685
|
+
sessionId: ledgerSessionId,
|
|
686
|
+
providerType: ledgerProviderType,
|
|
609
687
|
payload: {
|
|
610
688
|
event: args.event,
|
|
611
689
|
nodeLabel: args.nodeLabel,
|
|
612
690
|
taskId: completedTaskForLedger?.id || undefined,
|
|
613
691
|
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
|
|
614
692
|
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
|
|
693
|
+
evidence: completionEvidence,
|
|
615
694
|
},
|
|
616
695
|
});
|
|
617
696
|
} catch (e: any) {
|
|
@@ -745,6 +824,13 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
|
|
|
745
824
|
providerType: readNonEmptyString(payload.providerType),
|
|
746
825
|
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
747
826
|
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
827
|
+
intentional: payload.intentional === true,
|
|
828
|
+
intentionalStop: payload.intentionalStop === true,
|
|
829
|
+
operatorCleanup: payload.operatorCleanup === true,
|
|
830
|
+
reason: readNonEmptyString(payload.reason),
|
|
831
|
+
stopReason: readNonEmptyString(payload.stopReason),
|
|
832
|
+
cleanupReason: readNonEmptyString(payload.cleanupReason),
|
|
833
|
+
source: readNonEmptyString(payload.source),
|
|
748
834
|
},
|
|
749
835
|
});
|
|
750
836
|
}
|
package/src/mesh/mesh-ledger.ts
CHANGED
|
@@ -49,6 +49,56 @@ export interface MeshLedgerEntry {
|
|
|
49
49
|
payload: Record<string, unknown>;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean {
|
|
53
|
+
if (entry.kind !== 'session_stopped' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') return false;
|
|
54
|
+
const payload = entry.payload && typeof entry.payload === 'object' && !Array.isArray(entry.payload)
|
|
55
|
+
? entry.payload as Record<string, unknown>
|
|
56
|
+
: {};
|
|
57
|
+
return payload.intentional === true
|
|
58
|
+
&& (payload.reason === 'operator_cleanup'
|
|
59
|
+
|| payload.intentionalStopReason === 'operator_cleanup'
|
|
60
|
+
|| payload.source === 'mesh_cleanup_sessions'
|
|
61
|
+
|| payload.source === 'mesh_remove_node');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MeshTaskCompletionEvidence {
|
|
65
|
+
source: 'agent_status_event';
|
|
66
|
+
event: 'agent:generating_completed' | 'agent:ready';
|
|
67
|
+
nodeId: string;
|
|
68
|
+
sessionId: string;
|
|
69
|
+
providerType?: string;
|
|
70
|
+
completedAt: string;
|
|
71
|
+
transcriptHandle: {
|
|
72
|
+
kind: 'provider_session' | 'runtime_session';
|
|
73
|
+
sessionId: string;
|
|
74
|
+
providerSessionId?: string;
|
|
75
|
+
finalSummaryAvailable: boolean;
|
|
76
|
+
};
|
|
77
|
+
git: {
|
|
78
|
+
status: 'deferred';
|
|
79
|
+
reason: string;
|
|
80
|
+
};
|
|
81
|
+
validation: {
|
|
82
|
+
status: 'deferred';
|
|
83
|
+
commandsRun: string[];
|
|
84
|
+
reason: string;
|
|
85
|
+
};
|
|
86
|
+
checkpoint: {
|
|
87
|
+
attempted: false;
|
|
88
|
+
reason: 'not_attempted_for_ordinary_completion';
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface BuildTaskCompletionEvidenceOptions {
|
|
93
|
+
event: MeshTaskCompletionEvidence['event'];
|
|
94
|
+
nodeId: string;
|
|
95
|
+
sessionId: string;
|
|
96
|
+
providerType?: string;
|
|
97
|
+
providerSessionId?: string;
|
|
98
|
+
finalSummary?: string;
|
|
99
|
+
completedAt?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
52
102
|
export interface MeshLedgerSummary {
|
|
53
103
|
meshId: string;
|
|
54
104
|
totalEntries: number;
|
|
@@ -138,6 +188,38 @@ function getRotatedPath(meshId: string, index: number): string {
|
|
|
138
188
|
|
|
139
189
|
// ─── Core API ───────────────────────────────────
|
|
140
190
|
|
|
191
|
+
export function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence {
|
|
192
|
+
const providerSessionId = opts.providerSessionId?.trim() || undefined;
|
|
193
|
+
const providerType = opts.providerType?.trim() || undefined;
|
|
194
|
+
return {
|
|
195
|
+
source: 'agent_status_event',
|
|
196
|
+
event: opts.event,
|
|
197
|
+
nodeId: opts.nodeId,
|
|
198
|
+
sessionId: opts.sessionId,
|
|
199
|
+
providerType,
|
|
200
|
+
completedAt: opts.completedAt || new Date().toISOString(),
|
|
201
|
+
transcriptHandle: {
|
|
202
|
+
kind: providerSessionId ? 'provider_session' : 'runtime_session',
|
|
203
|
+
sessionId: opts.sessionId,
|
|
204
|
+
providerSessionId,
|
|
205
|
+
finalSummaryAvailable: typeof opts.finalSummary === 'string' && opts.finalSummary.trim().length > 0,
|
|
206
|
+
},
|
|
207
|
+
git: {
|
|
208
|
+
status: 'deferred',
|
|
209
|
+
reason: 'ordinary_completion_git_status_not_checked',
|
|
210
|
+
},
|
|
211
|
+
validation: {
|
|
212
|
+
status: 'deferred',
|
|
213
|
+
commandsRun: [],
|
|
214
|
+
reason: 'ordinary_completion_validation_not_run',
|
|
215
|
+
},
|
|
216
|
+
checkpoint: {
|
|
217
|
+
attempted: false,
|
|
218
|
+
reason: 'not_attempted_for_ordinary_completion',
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
141
223
|
/**
|
|
142
224
|
* Append a new entry to the mesh ledger.
|
|
143
225
|
* Handles file creation, rotation on size overflow, and atomic writes.
|
|
@@ -345,13 +427,17 @@ export function getLedgerSummary(meshId: string): MeshLedgerSummary {
|
|
|
345
427
|
case 'task_dispatched': summary.taskDispatched++; break;
|
|
346
428
|
case 'task_completed': summary.taskCompleted++; break;
|
|
347
429
|
case 'task_failed': {
|
|
430
|
+
if (isIntentionalCleanupStopEntry(entry)) break;
|
|
348
431
|
summary.taskFailed++;
|
|
349
432
|
if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
|
|
350
433
|
summary.recentFailures++;
|
|
351
434
|
}
|
|
352
435
|
break;
|
|
353
436
|
}
|
|
354
|
-
case 'task_stalled':
|
|
437
|
+
case 'task_stalled': {
|
|
438
|
+
if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
355
441
|
case 'session_launched': summary.sessionLaunched++; break;
|
|
356
442
|
case 'checkpoint_created': summary.checkpointCreated++; break;
|
|
357
443
|
}
|
|
@@ -422,6 +508,7 @@ export function getSessionRecoveryContext(
|
|
|
422
508
|
if (new Date(e.timestamp).getTime() < recentWindow) break;
|
|
423
509
|
if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
|
|
424
510
|
if (e.kind === 'task_failed') {
|
|
511
|
+
if (isIntentionalCleanupStopEntry(e)) continue;
|
|
425
512
|
consecutiveNodeFailures++;
|
|
426
513
|
} else if (e.kind === 'task_completed' || e.kind === 'task_dispatched') {
|
|
427
514
|
// A completion or new dispatch breaks the consecutive failure chain
|