@adhdev/daemon-core 0.9.82-rc.24 → 0.9.82-rc.26
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.js +91 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +91 -56
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +9 -0
- package/dist/mesh/mesh-work-queue.d.ts +3 -1
- package/package.json +1 -1
- package/src/commands/router.ts +42 -58
- package/src/mesh/mesh-events.ts +77 -1
- package/src/mesh/mesh-work-queue.ts +7 -4
|
@@ -26,12 +26,21 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
26
26
|
forwarded: number;
|
|
27
27
|
suppressed: boolean;
|
|
28
28
|
intentionalCleanupStop: boolean;
|
|
29
|
+
duplicateCompletion?: undefined;
|
|
30
|
+
error?: undefined;
|
|
31
|
+
} | {
|
|
32
|
+
success: boolean;
|
|
33
|
+
forwarded: number;
|
|
34
|
+
suppressed: boolean;
|
|
35
|
+
duplicateCompletion: boolean;
|
|
36
|
+
intentionalCleanupStop?: undefined;
|
|
29
37
|
error?: undefined;
|
|
30
38
|
} | {
|
|
31
39
|
success: boolean;
|
|
32
40
|
forwarded: number;
|
|
33
41
|
suppressed?: undefined;
|
|
34
42
|
intentionalCleanupStop?: undefined;
|
|
43
|
+
duplicateCompletion?: undefined;
|
|
35
44
|
error?: undefined;
|
|
36
45
|
} | {
|
|
37
46
|
success: boolean;
|
|
@@ -80,7 +80,9 @@ export declare function requeueTask(meshId: string, taskId: string, opts?: {
|
|
|
80
80
|
/**
|
|
81
81
|
* Update the status of the task currently assigned to a specific session.
|
|
82
82
|
*/
|
|
83
|
-
export declare function updateSessionTaskStatus(meshId: string, sessionId: string, status: MeshTaskStatus
|
|
83
|
+
export declare function updateSessionTaskStatus(meshId: string, sessionId: string, status: MeshTaskStatus, opts?: {
|
|
84
|
+
occurredAt?: string;
|
|
85
|
+
}): MeshWorkQueueEntry | null;
|
|
84
86
|
export interface MeshWorkQueueStats {
|
|
85
87
|
total: number;
|
|
86
88
|
active: number;
|
package/package.json
CHANGED
package/src/commands/router.ts
CHANGED
|
@@ -137,60 +137,11 @@ function readGitSubmodules(value: unknown): GitSubmoduleStatus[] | undefined {
|
|
|
137
137
|
return submodules.length > 0 ? submodules : undefined;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
? cachedGit.conflictFiles.filter((value: unknown): value is string => typeof value === 'string')
|
|
146
|
-
: [];
|
|
147
|
-
const conflictCount = readNumberValue(cachedGit.conflicts) ?? conflictFiles.length;
|
|
148
|
-
const hasConflicts = readBooleanValue(cachedGit.hasConflicts) ?? conflictCount > 0;
|
|
149
|
-
const isGitRepo = readBooleanValue(cachedGit.isGitRepo);
|
|
150
|
-
if (isGitRepo !== undefined) {
|
|
151
|
-
const submodules = readGitSubmodules(cachedGit.submodules);
|
|
152
|
-
return {
|
|
153
|
-
workspace: readStringValue(cachedGit.workspace, node?.workspace) || '',
|
|
154
|
-
repoRoot: readStringValue(cachedGit.repoRoot, node?.repoRoot, node?.workspace) || null,
|
|
155
|
-
isGitRepo,
|
|
156
|
-
branch: readStringValue(cachedGit.branch) ?? null,
|
|
157
|
-
headCommit: readStringValue(cachedGit.headCommit) ?? null,
|
|
158
|
-
headMessage: readStringValue(cachedGit.headMessage) ?? null,
|
|
159
|
-
upstream: readStringValue(cachedGit.upstream) ?? null,
|
|
160
|
-
ahead: readNumberValue(cachedGit.ahead) ?? 0,
|
|
161
|
-
behind: readNumberValue(cachedGit.behind) ?? 0,
|
|
162
|
-
staged: readNumberValue(cachedGit.staged) ?? 0,
|
|
163
|
-
modified: readNumberValue(cachedGit.modified) ?? 0,
|
|
164
|
-
untracked: readNumberValue(cachedGit.untracked) ?? 0,
|
|
165
|
-
deleted: readNumberValue(cachedGit.deleted) ?? 0,
|
|
166
|
-
renamed: readNumberValue(cachedGit.renamed) ?? 0,
|
|
167
|
-
hasConflicts,
|
|
168
|
-
conflictFiles,
|
|
169
|
-
stashCount: readNumberValue(cachedGit.stashCount) ?? 0,
|
|
170
|
-
lastCheckedAt: readNumberValue(cachedGit.lastCheckedAt) ?? Date.now(),
|
|
171
|
-
...(submodules ? { submodules } : {}),
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const rawGit = readObjectRecord(node?.lastGit ?? node?.last_git);
|
|
177
|
-
const gitResult = readObjectRecord(rawGit.result);
|
|
178
|
-
const directStatus = readObjectRecord(rawGit.status);
|
|
179
|
-
const nestedStatus = readObjectRecord(gitResult.status);
|
|
180
|
-
const rawProbe = readObjectRecord(node?.lastProbe ?? node?.last_probe);
|
|
181
|
-
const probeGit = readObjectRecord(rawProbe.git);
|
|
182
|
-
const probeGitResult = readObjectRecord(probeGit.result);
|
|
183
|
-
const probeDirectStatus = readObjectRecord(probeGit.status);
|
|
184
|
-
const probeNestedStatus = readObjectRecord(probeGitResult.status);
|
|
185
|
-
const status = Object.keys(directStatus).length
|
|
186
|
-
? directStatus
|
|
187
|
-
: Object.keys(nestedStatus).length
|
|
188
|
-
? nestedStatus
|
|
189
|
-
: Object.keys(probeDirectStatus).length
|
|
190
|
-
? probeDirectStatus
|
|
191
|
-
: Object.keys(probeNestedStatus).length
|
|
192
|
-
? probeNestedStatus
|
|
193
|
-
: {};
|
|
140
|
+
function normalizeInlineMeshGitStatus(
|
|
141
|
+
status: Record<string, unknown>,
|
|
142
|
+
node: any,
|
|
143
|
+
options?: { lastCheckedAt?: number },
|
|
144
|
+
): Record<string, unknown> | undefined {
|
|
194
145
|
const isGitRepo = readBooleanValue(status.isGitRepo);
|
|
195
146
|
if (!Object.keys(status).length || isGitRepo === undefined) return undefined;
|
|
196
147
|
const conflictFiles = Array.isArray(status.conflictFiles)
|
|
@@ -217,11 +168,43 @@ function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | un
|
|
|
217
168
|
hasConflicts,
|
|
218
169
|
conflictFiles,
|
|
219
170
|
stashCount: readNumberValue(status.stashCount) ?? 0,
|
|
220
|
-
lastCheckedAt: Date.now(),
|
|
171
|
+
lastCheckedAt: options?.lastCheckedAt ?? readNumberValue(status.lastCheckedAt) ?? Date.now(),
|
|
221
172
|
...(submodules ? { submodules } : {}),
|
|
222
173
|
};
|
|
223
174
|
}
|
|
224
175
|
|
|
176
|
+
function buildInlineMeshTransitGitStatus(node: any): Record<string, unknown> | undefined {
|
|
177
|
+
const rawGit = readObjectRecord(node?.lastGit ?? node?.last_git);
|
|
178
|
+
const gitResult = readObjectRecord(rawGit.result);
|
|
179
|
+
const directStatus = readObjectRecord(rawGit.status);
|
|
180
|
+
const nestedStatus = readObjectRecord(gitResult.status);
|
|
181
|
+
const rawProbe = readObjectRecord(node?.lastProbe ?? node?.last_probe);
|
|
182
|
+
const probeGit = readObjectRecord(rawProbe.git);
|
|
183
|
+
const probeGitResult = readObjectRecord(probeGit.result);
|
|
184
|
+
const probeDirectStatus = readObjectRecord(probeGit.status);
|
|
185
|
+
const probeNestedStatus = readObjectRecord(probeGitResult.status);
|
|
186
|
+
const status = Object.keys(directStatus).length
|
|
187
|
+
? directStatus
|
|
188
|
+
: Object.keys(nestedStatus).length
|
|
189
|
+
? nestedStatus
|
|
190
|
+
: Object.keys(probeDirectStatus).length
|
|
191
|
+
? probeDirectStatus
|
|
192
|
+
: Object.keys(probeNestedStatus).length
|
|
193
|
+
? probeNestedStatus
|
|
194
|
+
: {};
|
|
195
|
+
return normalizeInlineMeshGitStatus(status, node, { lastCheckedAt: Date.now() });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildCachedInlineMeshGitStatus(node: any): Record<string, unknown> | undefined {
|
|
199
|
+
const liveGit = buildInlineMeshTransitGitStatus(node);
|
|
200
|
+
if (liveGit) return liveGit;
|
|
201
|
+
|
|
202
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
203
|
+
const cachedGit = readObjectRecord(cachedStatus.git);
|
|
204
|
+
if (!Object.keys(cachedGit).length) return undefined;
|
|
205
|
+
return normalizeInlineMeshGitStatus(cachedGit, node);
|
|
206
|
+
}
|
|
207
|
+
|
|
225
208
|
function shouldDiscardCachedInlineMeshStatus(node: any): boolean {
|
|
226
209
|
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
227
210
|
if (!Object.keys(cachedStatus).length) return false;
|
|
@@ -518,9 +501,10 @@ function collectLiveMeshSessionRecords(args: {
|
|
|
518
501
|
|
|
519
502
|
function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node: any): boolean {
|
|
520
503
|
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
const
|
|
504
|
+
const liveGit = buildInlineMeshTransitGitStatus(node);
|
|
505
|
+
const git = liveGit ?? buildCachedInlineMeshGitStatus(node);
|
|
506
|
+
const error = liveGit ? undefined : readStringValue(cachedStatus.error, node?.error);
|
|
507
|
+
const health = liveGit ? undefined : readStringValue(cachedStatus.health, node?.health);
|
|
524
508
|
const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
|
|
525
509
|
const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
|
|
526
510
|
const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
|
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -189,6 +189,62 @@ function shouldSuppressIntentionalCleanupStop(args: {
|
|
|
189
189
|
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
const RECENT_COMPLETION_FINGERPRINT_TTL_MS = 10 * 60 * 1000;
|
|
193
|
+
const recentCompletionFingerprints = new Map<string, number>();
|
|
194
|
+
|
|
195
|
+
function readEventTimestamp(value: unknown): number | null {
|
|
196
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
197
|
+
if (typeof value === 'string' && value.trim()) {
|
|
198
|
+
const numeric = Number(value);
|
|
199
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
200
|
+
const parsed = Date.parse(value);
|
|
201
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildMeshCompletionFingerprint(args: {
|
|
207
|
+
meshId: string;
|
|
208
|
+
event: string;
|
|
209
|
+
sessionId: string;
|
|
210
|
+
providerType?: string;
|
|
211
|
+
providerSessionId?: string;
|
|
212
|
+
timestamp?: number | null;
|
|
213
|
+
finalSummary?: string;
|
|
214
|
+
}): string {
|
|
215
|
+
const timestampPart = Number.isFinite(args.timestamp)
|
|
216
|
+
? String(args.timestamp)
|
|
217
|
+
: readNonEmptyString(args.finalSummary).slice(0, 200);
|
|
218
|
+
return [
|
|
219
|
+
args.meshId,
|
|
220
|
+
args.event,
|
|
221
|
+
args.sessionId,
|
|
222
|
+
args.providerType || '',
|
|
223
|
+
args.providerSessionId || '',
|
|
224
|
+
timestampPart,
|
|
225
|
+
].join('::');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isDuplicateMeshCompletionEvent(args: {
|
|
229
|
+
meshId: string;
|
|
230
|
+
event: string;
|
|
231
|
+
sessionId: string;
|
|
232
|
+
providerType?: string;
|
|
233
|
+
providerSessionId?: string;
|
|
234
|
+
timestamp?: number | null;
|
|
235
|
+
finalSummary?: string;
|
|
236
|
+
}): boolean {
|
|
237
|
+
const fingerprint = buildMeshCompletionFingerprint(args);
|
|
238
|
+
if (!fingerprint) return false;
|
|
239
|
+
const now = Date.now();
|
|
240
|
+
for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
|
|
241
|
+
if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
|
|
242
|
+
}
|
|
243
|
+
if (recentCompletionFingerprints.has(fingerprint)) return true;
|
|
244
|
+
recentCompletionFingerprints.set(fingerprint, now);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
192
248
|
|
|
193
249
|
export function tryAssignQueueTask(
|
|
194
250
|
components: DaemonComponents,
|
|
@@ -651,6 +707,23 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
651
707
|
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
652
708
|
}
|
|
653
709
|
|
|
710
|
+
const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
|
|
711
|
+
if (args.event === 'agent:generating_completed' && eventSessionId) {
|
|
712
|
+
const duplicateCompletion = isDuplicateMeshCompletionEvent({
|
|
713
|
+
meshId: args.meshId,
|
|
714
|
+
event: args.event,
|
|
715
|
+
sessionId: eventSessionId,
|
|
716
|
+
providerType: readNonEmptyString(args.metadataEvent.providerType) || undefined,
|
|
717
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
|
|
718
|
+
timestamp: eventTimestamp,
|
|
719
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
|
|
720
|
+
});
|
|
721
|
+
if (duplicateCompletion) {
|
|
722
|
+
LOG.info('MeshEvents', `Suppressed duplicate completion for mesh ${args.meshId} session ${eventSessionId}`);
|
|
723
|
+
return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true };
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
654
727
|
// ── Task Queue & Ledger ──
|
|
655
728
|
let completedTaskForLedger: { id?: string } | null = null;
|
|
656
729
|
if (args.event === 'agent:generating_completed') {
|
|
@@ -659,7 +732,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
659
732
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
660
733
|
|
|
661
734
|
if (sessionId) {
|
|
662
|
-
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, 'completed'
|
|
735
|
+
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, 'completed', {
|
|
736
|
+
occurredAt: eventTimestamp !== null ? new Date(eventTimestamp).toISOString() : undefined,
|
|
737
|
+
});
|
|
663
738
|
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
664
739
|
if (nodeId && providerType) {
|
|
665
740
|
// Queue state is already updated above; setImmediate avoids the
|
|
@@ -896,6 +971,7 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
|
|
|
896
971
|
providerType: readNonEmptyString(payload.providerType),
|
|
897
972
|
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
898
973
|
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
974
|
+
...(payload.timestamp !== undefined ? { timestamp: payload.timestamp } : {}),
|
|
899
975
|
intentional: payload.intentional === true,
|
|
900
976
|
intentionalStop: payload.intentionalStop === true,
|
|
901
977
|
operatorCleanup: payload.operatorCleanup === true,
|
|
@@ -263,16 +263,19 @@ export function updateSessionTaskStatus(
|
|
|
263
263
|
meshId: string,
|
|
264
264
|
sessionId: string,
|
|
265
265
|
status: MeshTaskStatus,
|
|
266
|
+
opts?: { occurredAt?: string },
|
|
266
267
|
): MeshWorkQueueEntry | null {
|
|
267
268
|
return withQueueLock(meshId, () => {
|
|
268
269
|
const queue = readQueue(meshId);
|
|
270
|
+
const occurredAtTime = opts?.occurredAt ? new Date(opts.occurredAt).getTime() : Number.NaN;
|
|
271
|
+
const hasOccurredAt = Number.isFinite(occurredAtTime);
|
|
269
272
|
let bestIdx = -1;
|
|
270
273
|
let bestTime = 0;
|
|
271
274
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
272
|
-
if (queue[i].assignedSessionId
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
275
|
+
if (queue[i].assignedSessionId !== sessionId || queue[i].status !== 'assigned') continue;
|
|
276
|
+
const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
|
|
277
|
+
if (hasOccurredAt && Number.isFinite(time) && time > occurredAtTime) continue;
|
|
278
|
+
if (time > bestTime) { bestTime = time; bestIdx = i; }
|
|
276
279
|
}
|
|
277
280
|
if (bestIdx === -1) return null;
|
|
278
281
|
queue[bestIdx].status = status;
|