@adhdev/daemon-core 1.0.28-rc.6 → 1.0.28-rc.8
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/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +232 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +231 -44
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-ledger.d.ts +5 -1
- package/dist/mesh/mesh-queue-assignment.d.ts +19 -1
- package/dist/mesh/mesh-runtime-store.d.ts +4 -0
- package/package.json +3 -3
- package/src/cli-adapters/cli-state-engine.ts +9 -1
- package/src/cli-adapters/provider-cli-adapter.ts +42 -1
- package/src/index.ts +1 -1
- package/src/mesh/mesh-ledger.ts +72 -11
- package/src/mesh/mesh-queue-assignment.ts +176 -17
- package/src/mesh/mesh-runtime-store.ts +33 -8
- package/src/mesh/mesh-work-queue.ts +4 -0
- package/src/providers/cli-provider-instance.ts +55 -11
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { EventEmitter } from 'events';
|
|
16
16
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
17
17
|
import { type MeshLedgerOriginatingCoordinatorV2 } from './contracts.js';
|
|
18
|
-
export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'task_question_pending' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'event_held_requeued' | 'task_reclaimed' | 'coordinator_operating_note' | 'coordinator_operating_note_tombstone' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis' | 'key_injection' | 'worktree_cleanup_candidate';
|
|
18
|
+
export type MeshLedgerKind = 'task_dispatched' | 'task_claimed' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'task_question_pending' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'event_held_requeued' | 'task_reclaimed' | 'coordinator_operating_note' | 'coordinator_operating_note_tombstone' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis' | 'key_injection' | 'worktree_cleanup_candidate';
|
|
19
19
|
export interface MeshLedgerEntry {
|
|
20
20
|
id: string;
|
|
21
21
|
meshId: string;
|
|
@@ -24,8 +24,12 @@ export interface MeshLedgerEntry {
|
|
|
24
24
|
nodeId?: string;
|
|
25
25
|
sessionId?: string;
|
|
26
26
|
providerType?: string;
|
|
27
|
+
taskId?: string;
|
|
27
28
|
payload: Record<string, unknown>;
|
|
28
29
|
}
|
|
30
|
+
/** Resolve the taskId for a ledger entry, preferring the base field and falling
|
|
31
|
+
* back to payload.taskId (legacy rows / entries that only carry it in payload). */
|
|
32
|
+
export declare function ledgerEntryTaskId(entry: Pick<MeshLedgerEntry, 'taskId' | 'payload'>): string | undefined;
|
|
29
33
|
export declare function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean;
|
|
30
34
|
export type MeshWorkerResultStatus = 'completed' | 'failed' | 'blocked' | 'partial' | 'unknown';
|
|
31
35
|
export type MeshProcessArtifactKind = 'process' | 'log' | 'port' | 'window' | 'session' | 'file' | 'url' | 'other';
|
|
@@ -5,7 +5,25 @@ import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
|
5
5
|
export declare function isWorkspaceAutoFastForwardInFlight(workspace: string | undefined): boolean;
|
|
6
6
|
export declare function __resetIdleAutoFastForwardForTests(): void;
|
|
7
7
|
export declare function getMeshWithCache(components: DaemonComponents, meshId: string): any | undefined;
|
|
8
|
-
export
|
|
8
|
+
export interface MeshTaskRoutingDecision {
|
|
9
|
+
source: 'queue' | 'autoLaunch' | 'direct';
|
|
10
|
+
fitnessScore?: number;
|
|
11
|
+
skippedCandidates?: Array<{
|
|
12
|
+
nodeId: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
}>;
|
|
15
|
+
requiredTagsResult?: {
|
|
16
|
+
required: string[];
|
|
17
|
+
satisfied: boolean;
|
|
18
|
+
missing: string[];
|
|
19
|
+
};
|
|
20
|
+
resolvedProviderType?: string;
|
|
21
|
+
resolvedModel?: string;
|
|
22
|
+
resolvedThinkingLevel?: string;
|
|
23
|
+
resolvedDifficulty?: string;
|
|
24
|
+
reason?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string, routingDecision?: MeshTaskRoutingDecision): boolean;
|
|
9
27
|
export declare const AUTO_LAUNCH_AWAIT_CLAIM_MS = 90000;
|
|
10
28
|
interface AwaitClaimBackoffState {
|
|
11
29
|
cycles: number;
|
|
@@ -355,6 +355,7 @@ export declare class MeshRuntimeStore {
|
|
|
355
355
|
nodeId?: string | null;
|
|
356
356
|
sessionId?: string | null;
|
|
357
357
|
providerType?: string | null;
|
|
358
|
+
taskId?: string | null;
|
|
358
359
|
payload?: unknown;
|
|
359
360
|
}): void;
|
|
360
361
|
readLedgerEntries(meshId: string, opts?: {
|
|
@@ -370,6 +371,7 @@ export declare class MeshRuntimeStore {
|
|
|
370
371
|
nodeId: string | null;
|
|
371
372
|
sessionId: string | null;
|
|
372
373
|
providerType: string | null;
|
|
374
|
+
taskId: string | null;
|
|
373
375
|
payload: unknown;
|
|
374
376
|
}>;
|
|
375
377
|
/**
|
|
@@ -390,6 +392,7 @@ export declare class MeshRuntimeStore {
|
|
|
390
392
|
nodeId: string | null;
|
|
391
393
|
sessionId: string | null;
|
|
392
394
|
providerType: string | null;
|
|
395
|
+
taskId: string | null;
|
|
393
396
|
payload: unknown;
|
|
394
397
|
}>;
|
|
395
398
|
/** Remove all ledger entries for a mesh (mesh deletion / test cleanup). */
|
|
@@ -406,6 +409,7 @@ export declare class MeshRuntimeStore {
|
|
|
406
409
|
nodeId?: string | null;
|
|
407
410
|
sessionId?: string | null;
|
|
408
411
|
providerType?: string | null;
|
|
412
|
+
taskId?: string | null;
|
|
409
413
|
payload?: unknown;
|
|
410
414
|
}>): number;
|
|
411
415
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "1.0.28-rc.
|
|
3
|
+
"version": "1.0.28-rc.8",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"author": "vilmire",
|
|
50
50
|
"license": "AGPL-3.0-or-later",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@adhdev/mesh-shared": "1.0.28-rc.
|
|
53
|
-
"@adhdev/session-host-core": "1.0.28-rc.
|
|
52
|
+
"@adhdev/mesh-shared": "1.0.28-rc.8",
|
|
53
|
+
"@adhdev/session-host-core": "1.0.28-rc.8",
|
|
54
54
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
55
55
|
"ajv": "^8.20.0",
|
|
56
56
|
"ajv-formats": "^3.0.1",
|
|
@@ -1604,7 +1604,15 @@ export class CliStateEngine {
|
|
|
1604
1604
|
// background-tool race this was meant to guard against. If a provider
|
|
1605
1605
|
// really needs the gate, the manifest can set
|
|
1606
1606
|
// `requiresFinalAssistantBeforeIdle: true`.
|
|
1607
|
-
|
|
1607
|
+
//
|
|
1608
|
+
// This is a completion-timing JUDGMENT, so it goes through the shared
|
|
1609
|
+
// transcript-authority profile rather than reading the raw flag: the
|
|
1610
|
+
// 'floor' class is exactly `requiresFinalAssistantBeforeIdle && !hold`
|
|
1611
|
+
// (see resolveTranscriptAuthorityProfile), which is equivalent to the
|
|
1612
|
+
// old `!!requiresFinalAssistantBeforeIdle` for every real provider
|
|
1613
|
+
// because none declares both flags — a hold provider (antigravity)
|
|
1614
|
+
// sets holdCompletionForTranscript only, so it was already false here.
|
|
1615
|
+
const requiresFinalAssistant = resolveTranscriptAuthorityProfile(this.provider).timing === 'floor';
|
|
1608
1616
|
if (!requiresFinalAssistant) return false;
|
|
1609
1617
|
if (!this.isWaitingForResponse || !this.currentTurnScope || this.hasActionableApproval()) return false;
|
|
1610
1618
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status.trim() : '';
|
|
@@ -232,6 +232,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
232
232
|
private pendingOutboundQueue: PendingOutboundMessage[] = [];
|
|
233
233
|
private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
|
|
234
234
|
private pendingOutboundFlushInFlight = false;
|
|
235
|
+
// Stale-queue watchdog: fires STALE_QUEUE_WARN_MS after the oldest queued
|
|
236
|
+
// message was enqueued if nothing has been flushed yet.
|
|
237
|
+
private pendingOutboundStaleTimer: NodeJS.Timeout | null = null;
|
|
235
238
|
// Submit retry timer — PTY-level, not state machine
|
|
236
239
|
private submitRetryTimer: NodeJS.Timeout | null = null;
|
|
237
240
|
|
|
@@ -1715,6 +1718,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1715
1718
|
await new Promise<void>(resolve => setTimeout(resolve, FORCE_SUBMIT_SETTLE_MS));
|
|
1716
1719
|
}
|
|
1717
1720
|
|
|
1721
|
+
// Stale-queue threshold: warn if a queued message has not been flushed
|
|
1722
|
+
// within this many milliseconds (instrumentation only, no behavior change).
|
|
1723
|
+
private static readonly STALE_QUEUE_WARN_MS = 15000;
|
|
1724
|
+
|
|
1718
1725
|
private enqueuePendingOutboundMessage(text: string, reason: string, meshTaskId?: string): void {
|
|
1719
1726
|
const content = String(text || '');
|
|
1720
1727
|
const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
|
|
@@ -1722,6 +1729,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1722
1729
|
return;
|
|
1723
1730
|
}
|
|
1724
1731
|
const queuedAt = Date.now();
|
|
1732
|
+
const stableMs = this.lastScreenChangeAt ? (queuedAt - this.lastScreenChangeAt) : -1;
|
|
1733
|
+
// Diagnostic context: which gate caused the silent-queue and the full
|
|
1734
|
+
// session readiness snapshot at the moment of enqueue.
|
|
1735
|
+
const gateContext = {
|
|
1736
|
+
reason,
|
|
1737
|
+
startupParseGate: this.startupParseGate,
|
|
1738
|
+
ready: this.ready,
|
|
1739
|
+
engineStatus: this.engine.currentStatus,
|
|
1740
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1741
|
+
stableMs,
|
|
1742
|
+
sessionId: this.engine.getTraceSessionId(),
|
|
1743
|
+
};
|
|
1725
1744
|
const message: PendingOutboundMessage = {
|
|
1726
1745
|
id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
|
|
1727
1746
|
role: 'user',
|
|
@@ -1731,8 +1750,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1731
1750
|
...(typeof meshTaskId === 'string' && meshTaskId.trim() ? { meshTaskId } : {}),
|
|
1732
1751
|
};
|
|
1733
1752
|
this.pendingOutboundQueue.push(message);
|
|
1734
|
-
LOG.info('CLI', `[${this.cliType}] queued outbound message
|
|
1753
|
+
LOG.info('CLI', `[${this.cliType}] queued outbound message; gate=${JSON.stringify(gateContext)}; queue=${this.pendingOutboundQueue.length}`);
|
|
1735
1754
|
this.onStatusChange?.();
|
|
1755
|
+
// Stale-queue watchdog: emit a warn-level log if the enqueued message
|
|
1756
|
+
// has not been flushed after STALE_QUEUE_WARN_MS. This fires once per
|
|
1757
|
+
// enqueue event (not once per message still in queue) so it does not
|
|
1758
|
+
// spam on a persistently stuck queue; it simply surfaces that the queue
|
|
1759
|
+
// is stuck and records the current gate state at warn time.
|
|
1760
|
+
if (!this.pendingOutboundStaleTimer) {
|
|
1761
|
+
this.pendingOutboundStaleTimer = setTimeout(() => {
|
|
1762
|
+
this.pendingOutboundStaleTimer = null;
|
|
1763
|
+
if (this.pendingOutboundQueue.length === 0) return;
|
|
1764
|
+
const oldest = this.pendingOutboundQueue[0];
|
|
1765
|
+
const staleSec = ((Date.now() - oldest.queuedAt) / 1000).toFixed(1);
|
|
1766
|
+
const nowStableMs = this.lastScreenChangeAt ? (Date.now() - this.lastScreenChangeAt) : -1;
|
|
1767
|
+
LOG.warn('CLI', `[${this.cliType}] STALE QUEUE: ${this.pendingOutboundQueue.length} message(s) undelivered for ${staleSec}s; gate=startupParseGate:${this.startupParseGate} ready:${this.ready} engineStatus:${this.engine.currentStatus} isWaitingForResponse:${this.engine.isWaitingForResponse} stableMs:${nowStableMs} sessionId:${this.engine.getTraceSessionId()} enqueueReason:${oldest.id}`);
|
|
1768
|
+
}, ProviderCliAdapter.STALE_QUEUE_WARN_MS);
|
|
1769
|
+
}
|
|
1736
1770
|
}
|
|
1737
1771
|
|
|
1738
1772
|
private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
|
|
@@ -1785,6 +1819,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1785
1819
|
try {
|
|
1786
1820
|
await this.sendMessageNow(next.content, false, next.meshTaskId);
|
|
1787
1821
|
this.pendingOutboundQueue.shift();
|
|
1822
|
+
// Clear stale watchdog once the queue drains.
|
|
1823
|
+
if (this.pendingOutboundQueue.length === 0 && this.pendingOutboundStaleTimer) {
|
|
1824
|
+
clearTimeout(this.pendingOutboundStaleTimer);
|
|
1825
|
+
this.pendingOutboundStaleTimer = null;
|
|
1826
|
+
}
|
|
1788
1827
|
this.onStatusChange?.();
|
|
1789
1828
|
} catch (error: any) {
|
|
1790
1829
|
LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
|
|
@@ -2174,6 +2213,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2174
2213
|
this.pendingTerminalQueryTail = '';
|
|
2175
2214
|
this.ptyOutputChunks = [];
|
|
2176
2215
|
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2216
|
+
if (this.pendingOutboundStaleTimer) { clearTimeout(this.pendingOutboundStaleTimer); this.pendingOutboundStaleTimer = null; }
|
|
2177
2217
|
this.pendingOutboundQueue = [];
|
|
2178
2218
|
this.pendingOutboundFlushInFlight = false;
|
|
2179
2219
|
if (this.ptyProcess) {
|
|
@@ -2197,6 +2237,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2197
2237
|
this.pendingTerminalQueryTail = '';
|
|
2198
2238
|
this.ptyOutputChunks = [];
|
|
2199
2239
|
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2240
|
+
if (this.pendingOutboundStaleTimer) { clearTimeout(this.pendingOutboundStaleTimer); this.pendingOutboundStaleTimer = null; }
|
|
2200
2241
|
this.pendingOutboundQueue = [];
|
|
2201
2242
|
this.pendingOutboundFlushInFlight = false;
|
|
2202
2243
|
if (this.ptyProcess) {
|
package/src/index.ts
CHANGED
|
@@ -288,7 +288,7 @@ export { loadRepoSettings } from './config/repo-settings.js';
|
|
|
288
288
|
export type { RepoSettings, LoadRepoSettingsOptions } from './config/repo-settings.js';
|
|
289
289
|
|
|
290
290
|
// ── Mesh Task Ledger ──
|
|
291
|
-
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, readLedgerSliceFromStore, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT, tombstoneOperatingNote, readOperatingNotes, pruneOperatingNotes, isOperatingNoteTombstoned, OPERATING_NOTE_KIND, OPERATING_NOTE_TOMBSTONE_KIND, OPERATING_NOTE_DEDUPE_WINDOW, OPERATING_NOTE_KEEP_LATEST } from './mesh/mesh-ledger.js';
|
|
291
|
+
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, readLedgerSliceFromStore, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, ledgerEntryTaskId, MAX_LEDGER_SLICE_LIMIT, tombstoneOperatingNote, readOperatingNotes, pruneOperatingNotes, isOperatingNoteTombstoned, OPERATING_NOTE_KIND, OPERATING_NOTE_TOMBSTONE_KIND, OPERATING_NOTE_DEDUPE_WINDOW, OPERATING_NOTE_KEEP_LATEST } from './mesh/mesh-ledger.js';
|
|
292
292
|
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext, MeshTaskCompletionEvidence, MeshWorkerResultArtifact, MeshProcessArtifact, MeshValidationResultArtifact } from './mesh/mesh-ledger.js';
|
|
293
293
|
export { fastForwardMeshNode } from './mesh/mesh-fast-forward.js';
|
|
294
294
|
export type { MeshFastForwardNodeArgs, MeshFastForwardPlannedStep, MeshFastForwardResult } from './mesh/mesh-fast-forward.js';
|
package/src/mesh/mesh-ledger.ts
CHANGED
|
@@ -29,6 +29,11 @@ import {
|
|
|
29
29
|
|
|
30
30
|
export type MeshLedgerKind =
|
|
31
31
|
| 'task_dispatched'
|
|
32
|
+
// LEDGER-TASK-TRACEABILITY (C): a queue task transitioned pending→assigned (a
|
|
33
|
+
// node/session claimed it). Distinct from task_dispatched (the message was handed
|
|
34
|
+
// to the transport): claim precedes dispatch and marks the lifecycle handoff.
|
|
35
|
+
// payload: { taskId, nodeId, sessionId, providerType?, claimedAt }
|
|
36
|
+
| 'task_claimed'
|
|
32
37
|
| 'task_completed'
|
|
33
38
|
| 'task_failed'
|
|
34
39
|
| 'task_stalled'
|
|
@@ -111,9 +116,40 @@ export interface MeshLedgerEntry {
|
|
|
111
116
|
nodeId?: string;
|
|
112
117
|
sessionId?: string;
|
|
113
118
|
providerType?: string;
|
|
119
|
+
// LEDGER-TASK-TRACEABILITY (B): the task this entry pertains to, promoted from
|
|
120
|
+
// payload.taskId to a top-level base field so a task's lifecycle
|
|
121
|
+
// (task_dispatched → task_claimed → task_completed/failed/stalled/reclaimed) can
|
|
122
|
+
// be joined by kind+taskId without an O(n) per-entry payload scan. Optional and
|
|
123
|
+
// back-compat: legacy rows never carried it — readers fall back to payload.taskId,
|
|
124
|
+
// and appendLedgerEntry auto-derives it from payload.taskId for task-lifecycle
|
|
125
|
+
// kinds so every such entry is uniformly queryable.
|
|
126
|
+
taskId?: string;
|
|
114
127
|
payload: Record<string, unknown>;
|
|
115
128
|
}
|
|
116
129
|
|
|
130
|
+
// LEDGER-TASK-TRACEABILITY (B): the kinds whose taskId base field is auto-derived
|
|
131
|
+
// from payload.taskId at append time (so a join by kind+taskId is uniform). Other
|
|
132
|
+
// kinds may still set taskId explicitly; this only backfills the common lifecycle.
|
|
133
|
+
const TASK_LIFECYCLE_LEDGER_KINDS: ReadonlySet<MeshLedgerKind> = new Set<MeshLedgerKind>([
|
|
134
|
+
'task_dispatched',
|
|
135
|
+
'task_claimed',
|
|
136
|
+
'task_completed',
|
|
137
|
+
'task_failed',
|
|
138
|
+
'task_stalled',
|
|
139
|
+
'task_reclaimed',
|
|
140
|
+
'task_approval_needed',
|
|
141
|
+
'task_question_pending',
|
|
142
|
+
'p2p_dispatch_failed',
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
/** Resolve the taskId for a ledger entry, preferring the base field and falling
|
|
146
|
+
* back to payload.taskId (legacy rows / entries that only carry it in payload). */
|
|
147
|
+
export function ledgerEntryTaskId(entry: Pick<MeshLedgerEntry, 'taskId' | 'payload'>): string | undefined {
|
|
148
|
+
if (typeof entry.taskId === 'string' && entry.taskId.trim()) return entry.taskId.trim();
|
|
149
|
+
const fromPayload = entry.payload && typeof entry.payload === 'object' ? (entry.payload as Record<string, unknown>).taskId : undefined;
|
|
150
|
+
return typeof fromPayload === 'string' && fromPayload.trim() ? fromPayload.trim() : undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
117
153
|
export function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind' | 'payload'>): boolean {
|
|
118
154
|
if (entry.kind !== 'session_stopped' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') return false;
|
|
119
155
|
const payload = entry.payload && typeof entry.payload === 'object' && !Array.isArray(entry.payload)
|
|
@@ -796,6 +832,14 @@ export function appendLedgerEntry(
|
|
|
796
832
|
...partial,
|
|
797
833
|
};
|
|
798
834
|
|
|
835
|
+
// LEDGER-TASK-TRACEABILITY (B): backfill the taskId base field from payload.taskId
|
|
836
|
+
// for task-lifecycle kinds so every dispatch/claim/complete/fail/stall/reclaim
|
|
837
|
+
// entry is uniformly join-able by kind+taskId. An explicit partial.taskId wins.
|
|
838
|
+
if (!entry.taskId && TASK_LIFECYCLE_LEDGER_KINDS.has(entry.kind)) {
|
|
839
|
+
const derived = ledgerEntryTaskId(entry);
|
|
840
|
+
if (derived) entry.taskId = derived;
|
|
841
|
+
}
|
|
842
|
+
|
|
799
843
|
const filePath = getLedgerPath(meshId);
|
|
800
844
|
|
|
801
845
|
// Compact or rotate based on file size
|
|
@@ -822,6 +866,7 @@ export function appendLedgerEntry(
|
|
|
822
866
|
nodeId: entry.nodeId ?? null,
|
|
823
867
|
sessionId: entry.sessionId ?? null,
|
|
824
868
|
providerType: entry.providerType ?? null,
|
|
869
|
+
taskId: entry.taskId ?? null,
|
|
825
870
|
payload: entry.payload,
|
|
826
871
|
});
|
|
827
872
|
} catch {
|
|
@@ -1074,7 +1119,15 @@ function readLedgerFile(meshId: string): MeshLedgerEntry[] {
|
|
|
1074
1119
|
if (!line.trim()) continue;
|
|
1075
1120
|
try {
|
|
1076
1121
|
const entry = JSON.parse(line) as MeshLedgerEntry;
|
|
1077
|
-
if (entry.id && entry.kind)
|
|
1122
|
+
if (entry.id && entry.kind) {
|
|
1123
|
+
// LEDGER-TASK-TRACEABILITY (B): backfill the base taskId from
|
|
1124
|
+
// payload.taskId for legacy JSONL lines that predate the field.
|
|
1125
|
+
if (!entry.taskId) {
|
|
1126
|
+
const derived = ledgerEntryTaskId(entry);
|
|
1127
|
+
if (derived) entry.taskId = derived;
|
|
1128
|
+
}
|
|
1129
|
+
entries.push(entry);
|
|
1130
|
+
}
|
|
1078
1131
|
} catch { /* skip malformed lines */ }
|
|
1079
1132
|
}
|
|
1080
1133
|
return entries;
|
|
@@ -1108,6 +1161,7 @@ function ensureLedgerImported(store: MeshRuntimeStore, meshId: string): void {
|
|
|
1108
1161
|
nodeId: e.nodeId ?? null,
|
|
1109
1162
|
sessionId: e.sessionId ?? null,
|
|
1110
1163
|
providerType: e.providerType ?? null,
|
|
1164
|
+
taskId: ledgerEntryTaskId(e) ?? null,
|
|
1111
1165
|
payload: e.payload ?? {},
|
|
1112
1166
|
})));
|
|
1113
1167
|
} catch { /* import is best-effort; reads fall back to JSONL on store failure */ }
|
|
@@ -1116,16 +1170,23 @@ function ensureLedgerImported(store: MeshRuntimeStore, meshId: string): void {
|
|
|
1116
1170
|
function readLedgerFromStore(meshId: string): MeshLedgerEntry[] {
|
|
1117
1171
|
const store = MeshRuntimeStore.getInstance();
|
|
1118
1172
|
ensureLedgerImported(store, meshId);
|
|
1119
|
-
return store.readLedgerEntriesOrdered(meshId).map(r =>
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1173
|
+
return store.readLedgerEntriesOrdered(meshId).map(r => {
|
|
1174
|
+
const payload = (r.payload && typeof r.payload === 'object' ? r.payload : {}) as Record<string, unknown>;
|
|
1175
|
+
// LEDGER-TASK-TRACEABILITY (B): prefer the column, fall back to payload.taskId
|
|
1176
|
+
// for legacy rows written before the column existed (back-compat join).
|
|
1177
|
+
const taskId = ledgerEntryTaskId({ taskId: r.taskId ?? undefined, payload });
|
|
1178
|
+
return {
|
|
1179
|
+
id: r.id,
|
|
1180
|
+
meshId: r.meshId,
|
|
1181
|
+
timestamp: r.timestamp,
|
|
1182
|
+
kind: r.kind as MeshLedgerKind,
|
|
1183
|
+
...(r.nodeId ? { nodeId: r.nodeId } : {}),
|
|
1184
|
+
...(r.sessionId ? { sessionId: r.sessionId } : {}),
|
|
1185
|
+
...(r.providerType ? { providerType: r.providerType } : {}),
|
|
1186
|
+
...(taskId ? { taskId } : {}),
|
|
1187
|
+
payload,
|
|
1188
|
+
};
|
|
1189
|
+
});
|
|
1129
1190
|
}
|
|
1130
1191
|
|
|
1131
1192
|
function getCachedRawEntries(meshId: string): MeshLedgerEntry[] {
|