@adhdev/daemon-core 1.0.28-rc.7 → 1.0.28-rc.9
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 +1 -1
- package/dist/index.js +251 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -45
- 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/dist/providers/cli-provider-instance-types.d.ts +1 -0
- package/dist/providers/spec/fsm-driver.d.ts +13 -0
- package/dist/providers/spec/types.d.ts +25 -0
- package/package.json +3 -3
- package/src/commands/med-family/mesh-crud.ts +27 -0
- 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-reconcile-loop.ts +34 -0
- package/src/mesh/mesh-runtime-store.ts +33 -8
- package/src/mesh/mesh-work-queue.ts +4 -0
- package/src/providers/cli-provider-instance-types.ts +25 -0
- package/src/providers/cli-provider-instance.ts +51 -0
- package/src/providers/spec/evaluator.ts +9 -1
- package/src/providers/spec/fsm-driver.ts +43 -3
- package/src/providers/spec/types.ts +25 -0
|
@@ -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
|
/**
|
|
@@ -39,6 +39,7 @@ export type ExternalTranscriptProbe = {
|
|
|
39
39
|
export declare const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
40
40
|
export declare const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30000;
|
|
41
41
|
export declare const CANON_C_MISSING_ASSISTANT_MIN_ELAPSED_MS = 20000;
|
|
42
|
+
export declare const MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS = 60000;
|
|
42
43
|
export declare const NATIVE_HISTORY_MESH_IDLE_SETTLE_MS = 4000;
|
|
43
44
|
export declare const PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS = 1200;
|
|
44
45
|
export declare const ANTIGRAVITY_HOLD_QUIET_DWELL_MS = 3000;
|
|
@@ -475,4 +475,17 @@ export declare class FsmDriver implements ISpecDriver {
|
|
|
475
475
|
* not register as a region change. No filter → lines returned unchanged.
|
|
476
476
|
* Exported for unit tests of the stable_ms `ignore_lines` change-detection. */
|
|
477
477
|
export declare function filterIgnoredLines(lines: string[], ignoreRe: RegExp | undefined): string[];
|
|
478
|
+
/** Compute the [start, end) line window a cursor_above stable region measures,
|
|
479
|
+
* reconciling the backend's raw cursor row with the blank-trimmed viewport
|
|
480
|
+
* line array (see the CODEX-FSM-DEGENERATE-STABLE note in trackRegionChanges).
|
|
481
|
+
* The window end is clamped to the content length so an overshooting cursor
|
|
482
|
+
* row measures the content tail instead of slicing past the array into a
|
|
483
|
+
* permanently-empty — and therefore permanently "unchanged" — window.
|
|
484
|
+
* Returns null when no measurable window exists (cursor at/above row 0, or no
|
|
485
|
+
* content): the caller must treat the region as CHANGED, never stable.
|
|
486
|
+
* Exported for unit tests. */
|
|
487
|
+
export declare function stableCursorWindow(lineCount: number, cursorRow: number, cursorAbove: number): {
|
|
488
|
+
start: number;
|
|
489
|
+
end: number;
|
|
490
|
+
} | null;
|
|
478
491
|
export {};
|
|
@@ -268,6 +268,31 @@ export interface SectionDef {
|
|
|
268
268
|
* for that anchor. A scalar `anchor` ignores array form beyond index 0.
|
|
269
269
|
*/
|
|
270
270
|
anchor_context?: AnchorContext | (AnchorContext | null)[];
|
|
271
|
+
/**
|
|
272
|
+
* Extends an anchored section N lines ABOVE its anchor line. The default
|
|
273
|
+
* anchored geometry starts the section AT the anchor line, which cannot
|
|
274
|
+
* express a status block that sits directly ABOVE its landmark (e.g.
|
|
275
|
+
* codex's live `Working (…)` spinner above the `› ` composer). With
|
|
276
|
+
* `above: K` the section starts at max(0, anchorLine - K), so the window
|
|
277
|
+
* stays pinned to the landmark regardless of output volume — unlike
|
|
278
|
+
* `from_bottom`, which counts from the LAST NON-BLANK line of a
|
|
279
|
+
* blank-trimmed viewport and lets the live status block escape the window
|
|
280
|
+
* once long output fills the tail (CODEX-FSM-DEGENERATE-STABLE RCA,
|
|
281
|
+
* defect 1: the spinner left the from_bottom:12 window while the worker
|
|
282
|
+
* was still generating).
|
|
283
|
+
*/
|
|
284
|
+
above?: number;
|
|
285
|
+
/**
|
|
286
|
+
* Anchor-miss policy. Default (absent): an anchored section whose anchor
|
|
287
|
+
* matches NOTHING falls back to the whole screen (from=0, to=total) — the
|
|
288
|
+
* historical behavior modal sections rely on ("whole-screen fallback in
|
|
289
|
+
* non-modal frames is harmless"). 'empty' resolves the section to EMPTY
|
|
290
|
+
* instead, for sections whose guard regexes must never see unrelated body
|
|
291
|
+
* text: status_tail's spinner cues, whose whole-screen fallback would
|
|
292
|
+
* re-open the SPINNER-BODY-SELFMATCH defect on any frame where the
|
|
293
|
+
* composer landmark is momentarily absent (mid-redraw).
|
|
294
|
+
*/
|
|
295
|
+
anchor_miss?: 'empty';
|
|
271
296
|
lines?: number;
|
|
272
297
|
until_regex?: string;
|
|
273
298
|
until_regex_flags?: string;
|
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.9",
|
|
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.9",
|
|
53
|
+
"@adhdev/session-host-core": "1.0.28-rc.9",
|
|
54
54
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
55
55
|
"ajv": "^8.20.0",
|
|
56
56
|
"ajv-formats": "^3.0.1",
|
|
@@ -677,6 +677,20 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
677
677
|
...(capabilities && capabilities.length ? { capabilities } : {}),
|
|
678
678
|
});
|
|
679
679
|
if (!node) return { success: false, error: 'Mesh not found' };
|
|
680
|
+
// MESH-MEMBERSHIP-INLINE-CACHE-SYNC: addNode() above only wrote the new
|
|
681
|
+
// node to the file-backed meshes.json. getMeshForCommand's inline-cache-
|
|
682
|
+
// preferred read (the default for mesh_status/mesh_list_nodes/get_mesh)
|
|
683
|
+
// resolves this SAME meshId from `inlineMeshCache` whenever a prior
|
|
684
|
+
// command warmed it (e.g. a cloud coordinator launch with inlineMesh —
|
|
685
|
+
// see mesh-coordinator-launch.ts). Without pushing the new node into
|
|
686
|
+
// that cache too, the live view keeps serving the pre-add snapshot until
|
|
687
|
+
// the daemon restarts and the cache is re-emptied. Only touch the cache
|
|
688
|
+
// when it already holds this mesh (nothing to fix for a pure local-config
|
|
689
|
+
// mesh that no caller has ever warmed).
|
|
690
|
+
const cachedMesh = ctx.getCachedInlineMesh(meshId);
|
|
691
|
+
if (cachedMesh) {
|
|
692
|
+
ctx.updateInlineMeshNode(meshId, cachedMesh, node);
|
|
693
|
+
}
|
|
680
694
|
// mesh_status hands back a coordinator-memory aggregate
|
|
681
695
|
// snapshot keyed on (meshId, queueRevision). Adding a
|
|
682
696
|
// node touches neither, so without an explicit cache
|
|
@@ -1036,6 +1050,19 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
1036
1050
|
// the response is accurate.
|
|
1037
1051
|
if (!removed && !node) removed = true;
|
|
1038
1052
|
if (removed) ctx.invalidateAggregateMeshStatus(meshId);
|
|
1053
|
+
// MESH-MEMBERSHIP-INLINE-CACHE-SYNC: mirror-image of the inline
|
|
1054
|
+
// branch above. This mesh resolved from local_config (nothing had
|
|
1055
|
+
// warmed inlineMeshCache for it YET at getMeshForCommand time), but
|
|
1056
|
+
// another command (e.g. a cloud coordinator launch with inlineMesh)
|
|
1057
|
+
// may have already warmed the cache for this same meshId from an
|
|
1058
|
+
// earlier read. Splice the node out of the cached copy too, and
|
|
1059
|
+
// tombstone it, so a dashboard's stale inlineMesh echo cannot merge
|
|
1060
|
+
// the removed node back in (see removeInlineMeshNode's tombstone
|
|
1061
|
+
// comment).
|
|
1062
|
+
if (removed) {
|
|
1063
|
+
const cachedMesh = ctx.getCachedInlineMesh(meshId);
|
|
1064
|
+
if (cachedMesh) ctx.removeInlineMeshNode(meshId, cachedMesh, nodeId);
|
|
1065
|
+
}
|
|
1039
1066
|
}
|
|
1040
1067
|
|
|
1041
1068
|
// Record in task ledger
|
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[] {
|