@adhdev/daemon-core 0.9.82-rc.472 → 0.9.82-rc.474
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 +4 -2
- package/dist/index.js +204 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +200 -58
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-missions.d.ts +55 -0
- package/package.json +3 -3
- package/src/index.ts +7 -2
- package/src/mesh/mesh-completion-synthesis.ts +19 -1
- package/src/mesh/mesh-events-stale.ts +13 -1
- package/src/mesh/mesh-missions.ts +127 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +138 -56
|
@@ -91,6 +91,18 @@ export declare const GOAL_PREVIEW_MAX = 120;
|
|
|
91
91
|
* tighter than the dashboard / mesh_mission_list preview (GOAL_PREVIEW_MAX).
|
|
92
92
|
*/
|
|
93
93
|
export declare const COMPACT_STATUS_GOAL_PREVIEW_MAX = 80;
|
|
94
|
+
/**
|
|
95
|
+
* mesh_mission_list default fold sizes. Without an explicit `statuses` filter the
|
|
96
|
+
* tool returns non-terminal (active/paused) missions in full detail but folds the
|
|
97
|
+
* completed/abandoned history into counts + a capped newest-first id list — a mesh
|
|
98
|
+
* can accumulate hundreds of terminal missions and returning them all (each with a
|
|
99
|
+
* ledger-scanned stats rollup) blew past the tool payload / token budget and spilled
|
|
100
|
+
* to a file. When an explicit `statuses` filter IS given, the matching missions are
|
|
101
|
+
* returned in detail but still bounded by MESH_MISSION_LIST_STATUS_LIMIT so a
|
|
102
|
+
* status:["completed"] call on a huge history stays bounded (overflow → truncated).
|
|
103
|
+
*/
|
|
104
|
+
export declare const MESH_MISSION_LIST_HISTORY_ID_LIMIT = 30;
|
|
105
|
+
export declare const MESH_MISSION_LIST_STATUS_LIMIT = 50;
|
|
94
106
|
export declare function upsertMeshMission(meshId: string, input: {
|
|
95
107
|
id?: string;
|
|
96
108
|
title: string;
|
|
@@ -218,6 +230,49 @@ export declare function listMeshMissionSummaries(meshId: string, options?: {
|
|
|
218
230
|
verbose?: boolean;
|
|
219
231
|
includeMagi?: boolean;
|
|
220
232
|
}): MeshMissionSummary[] | MeshMissionSlimSummary[];
|
|
233
|
+
/** Shaped mesh_mission_list result: bounded detail list + optional folded history. */
|
|
234
|
+
export interface MeshMissionListResult {
|
|
235
|
+
/** Detailed (slim or verbose) mission summaries — bounded, newest-first. */
|
|
236
|
+
missions: MeshMissionSummary[] | MeshMissionSlimSummary[];
|
|
237
|
+
/**
|
|
238
|
+
* Completed/abandoned missions folded to counts + ids. Present (default path)
|
|
239
|
+
* when no explicit status filter is given and terminal missions exist. Null when
|
|
240
|
+
* an explicit status filter is active (those missions go into `missions`).
|
|
241
|
+
*/
|
|
242
|
+
historyFold: MeshStatusMissionsHistoryFold | null;
|
|
243
|
+
/** True when an explicit status filter matched more than `limit` missions. */
|
|
244
|
+
truncated: boolean;
|
|
245
|
+
/** Total missions matching the query BEFORE the detail-list cap was applied. */
|
|
246
|
+
matched: number;
|
|
247
|
+
/** Newest-first ids of missions dropped by the detail-list cap (truncated path). */
|
|
248
|
+
overflowIds?: string[];
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* mesh_mission_list projection: payload-bounded regardless of mesh mission count.
|
|
252
|
+
*
|
|
253
|
+
* Default (no explicit `statuses`): non-terminal missions (active/paused) return in
|
|
254
|
+
* detail; completed/abandoned missions are folded to counts + a capped id list
|
|
255
|
+
* (historyFold), NOT emitted one-by-one. This is what keeps the tool from returning
|
|
256
|
+
* hundreds of terminal missions (each with a ledger stats rollup) and overflowing the
|
|
257
|
+
* token budget.
|
|
258
|
+
*
|
|
259
|
+
* Explicit `statuses` (e.g. ["completed"]): the coordinator asked to see those
|
|
260
|
+
* missions, so they ARE returned in detail — but still bounded by `limit` (default
|
|
261
|
+
* MESH_MISSION_LIST_STATUS_LIMIT). Overflow beyond `limit` is reported via
|
|
262
|
+
* truncated:true + overflowIds rather than silently dropped.
|
|
263
|
+
*
|
|
264
|
+
* MAGI + verbose semantics match listMeshMissionSummaries. `withStats` opts each
|
|
265
|
+
* detailed mission into the ledger-scanned stats rollup (off by default — the tasks
|
|
266
|
+
* aggregate is enough for a list view).
|
|
267
|
+
*/
|
|
268
|
+
export declare function listMeshMissionsForTool(meshId: string, options?: {
|
|
269
|
+
statuses?: MeshMissionStatus[];
|
|
270
|
+
verbose?: boolean;
|
|
271
|
+
includeMagi?: boolean;
|
|
272
|
+
withStats?: boolean;
|
|
273
|
+
limit?: number;
|
|
274
|
+
historyIdLimit?: number;
|
|
275
|
+
}): MeshMissionListResult;
|
|
221
276
|
/**
|
|
222
277
|
* M3-3: render active missions as a prompt section for {{mission}}.
|
|
223
278
|
* Empty string when no active mission — the prompt stays byte-identical to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.474",
|
|
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",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"author": "vilmire",
|
|
48
48
|
"license": "AGPL-3.0-or-later",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
51
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "0.9.82-rc.474",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.474",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
package/src/index.ts
CHANGED
|
@@ -228,8 +228,8 @@ export type { CanonicalMeshToolName } from '@adhdev/mesh-shared';
|
|
|
228
228
|
|
|
229
229
|
// ── Mesh Coordinator ──
|
|
230
230
|
export { buildCoordinatorSystemPrompt } from './mesh/coordinator-prompt.js';
|
|
231
|
-
export { upsertMeshMission, getMeshMissions, getMeshMission, summarizeMissionTasks, summarizeMeshMission, getActiveMeshMissionSummaries, getMeshStatusMissionSummaries, getMeshStatusMissionsCompact, listMeshMissionSummaries, buildMissionPromptSection, GOAL_PREVIEW_MAX, COMPACT_STATUS_GOAL_PREVIEW_MAX, MESH_MISSION_STATUSES } from './mesh/mesh-missions.js';
|
|
232
|
-
export type { MeshMissionRecord, MeshMissionStatus, MeshMissionSummary, MeshMissionSlimSummary, MeshMissionTaskAggregate, MeshStatusMissionsCompact, MeshStatusMissionsHistoryFold } from './mesh/mesh-missions.js';
|
|
231
|
+
export { upsertMeshMission, getMeshMissions, getMeshMission, summarizeMissionTasks, summarizeMeshMission, getActiveMeshMissionSummaries, getMeshStatusMissionSummaries, getMeshStatusMissionsCompact, listMeshMissionSummaries, listMeshMissionsForTool, buildMissionPromptSection, GOAL_PREVIEW_MAX, COMPACT_STATUS_GOAL_PREVIEW_MAX, MESH_MISSION_LIST_HISTORY_ID_LIMIT, MESH_MISSION_LIST_STATUS_LIMIT, MESH_MISSION_STATUSES } from './mesh/mesh-missions.js';
|
|
232
|
+
export type { MeshMissionRecord, MeshMissionStatus, MeshMissionSummary, MeshMissionSlimSummary, MeshMissionTaskAggregate, MeshStatusMissionsCompact, MeshStatusMissionsHistoryFold, MeshMissionListResult } from './mesh/mesh-missions.js';
|
|
233
233
|
export { computeMeshTaskStats, computeMeshMissionStats } from './mesh/mesh-task-stats.js';
|
|
234
234
|
export type { MeshTaskStats, MeshMissionStats } from './mesh/mesh-task-stats.js';
|
|
235
235
|
export { deriveMeshReviewInboxItems } from './mesh/mesh-review-inbox.js';
|
|
@@ -303,6 +303,11 @@ export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHost
|
|
|
303
303
|
// ── Mesh Events ──
|
|
304
304
|
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent, requeueHeldMeshCoordinatorEvents, serializeV2EnvelopeToWire, readV2EnvelopeFromWire, reconcileDirectDispatchCompletionFromTranscript } from './mesh/mesh-events.js';
|
|
305
305
|
export type { PendingMeshCoordinatorEvent, MeshHeldEventRequeueFilter, MeshHeldEventRequeueResult } from './mesh/mesh-events.js';
|
|
306
|
+
// COORD-EVENT-MISROUTE: coordinator-identity helper so the mcp-server drain path can build a
|
|
307
|
+
// session-scoped drainer identity (identityDeliversTo sibling-session filter) with the same
|
|
308
|
+
// canonical builder daemon-core uses internally, instead of hand-rolling the identity shape.
|
|
309
|
+
export { coordinatorIdentityFromEmitFields } from './mesh/contracts.js';
|
|
310
|
+
export type { CoordinatorIdentity } from './mesh/contracts.js';
|
|
306
311
|
// The coordinator-side preview surfaced from a worker's completion/status event
|
|
307
312
|
// (finalSummary / workerResult.summary / lastMessagePreview). Same data the mobile
|
|
308
313
|
// inbox is fed; reused by mesh_read_chat's cache fallback when the live P2P read path
|
|
@@ -327,7 +327,23 @@ export async function reconcileUnterminatedDirectDispatches(
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
const providerSessionId = readNonEmptyString(payload.providerSessionId);
|
|
330
|
-
|
|
330
|
+
// COORD-EVENT-MISROUTE (anchor preservation): the coordinator DAEMON anchor for the
|
|
331
|
+
// synthesized completion is the daemon that DISPATCHED the task, NOT this reconcile
|
|
332
|
+
// runner's own daemon. On a REMOTE worker the reconcile loop runs on the WORKER's daemon,
|
|
333
|
+
// so `selfIds` is the worker daemon — stamping it as targetCoordinatorDaemonId corrupts the
|
|
334
|
+
// anchor and (when the real coordinator is on another machine) downgrades the completion to
|
|
335
|
+
// a cross-machine broadcast deliverable to any coordinator. Recover the true anchor:
|
|
336
|
+
// 1. the live worker session's meshCoordinatorDaemonId relay stamp (set at dispatch,
|
|
337
|
+
// the same anchor the worker's own real-emit path reads in mesh-event-forwarding); then
|
|
338
|
+
// 2. leave it to reconcileDirectDispatchCompletionFromTranscript, which recovers the
|
|
339
|
+
// DISPATCHING coordinator daemon from the task_dispatched ledger (authoritative).
|
|
340
|
+
// Only when NEITHER is available do we fall back to selfIds — a genuinely local,
|
|
341
|
+
// single-daemon dispatch where self IS the coordinator daemon (unchanged behaviour).
|
|
342
|
+
const workerSession = components.instanceManager.getInstance(sessionId);
|
|
343
|
+
const workerCoordinatorDaemonId = readNonEmptyString(
|
|
344
|
+
(workerSession?.getState()?.settings as Record<string, unknown> | undefined)?.meshCoordinatorDaemonId,
|
|
345
|
+
);
|
|
346
|
+
const coordinatorDaemonId = workerCoordinatorDaemonId || selfIds.find(id => !!id);
|
|
331
347
|
try {
|
|
332
348
|
const result = reconcileDirectDispatchCompletionFromTranscript({
|
|
333
349
|
meshId: mesh.id,
|
|
@@ -338,6 +354,8 @@ export async function reconcileUnterminatedDirectDispatches(
|
|
|
338
354
|
taskId,
|
|
339
355
|
finalSummary: evidence.finalSummary,
|
|
340
356
|
...(evidence.transcriptMessageAt ? { transcriptMessageAt: evidence.transcriptMessageAt } : {}),
|
|
357
|
+
// The ledger-recovered dispatching-coordinator daemon (inside the reconcile fn)
|
|
358
|
+
// takes PRIORITY over this arg; this remains the best-available fallback.
|
|
341
359
|
...(coordinatorDaemonId ? { targetCoordinatorDaemonId: coordinatorDaemonId } : {}),
|
|
342
360
|
source: 'daemon_reconcile_transcript_completion',
|
|
343
361
|
});
|
|
@@ -345,6 +345,18 @@ export function reconcileDirectDispatchCompletionFromTranscript(args: {
|
|
|
345
345
|
// Absent on legacy rows → undefined → daemon-level routing (unchanged, no regression).
|
|
346
346
|
const targetCoordinatorSessionId = readNonEmptyString(args.targetCoordinatorSessionId)
|
|
347
347
|
|| readNonEmptyString(dispatch?.payload?.coordinatorSessionId);
|
|
348
|
+
// COORD-EVENT-MISROUTE (anchor preservation): the DISPATCHING coordinator's daemon anchor.
|
|
349
|
+
// Prefer the DISPATCH LEDGER's `coordinatorDaemonId` (the daemon that ISSUED the task) over the
|
|
350
|
+
// caller-supplied arg — the synth caller (mesh-completion-synthesis) resolves the arg from its
|
|
351
|
+
// OWN selfIds, which on a remote worker's reconcile is the WORKER daemon, not the coordinator.
|
|
352
|
+
// Using the worker's self-daemon as the anchor makes coordinatorIdentityFromEmitFields treat
|
|
353
|
+
// the completion as addressed to the worker daemon; when the coordinator lives on a DIFFERENT
|
|
354
|
+
// machine that anchor never matches, selfFallback fires and stampPendingEventV2 broadcasts the
|
|
355
|
+
// completion to ANY coordinator (contracts.ts shouldDeliverPendingEventToCoordinator → true for
|
|
356
|
+
// broadcast), the cross-machine misroute. The ledger value is the authoritative issuing-daemon
|
|
357
|
+
// anchor. Absent on legacy rows → fall back to the arg → daemon-level routing (no regression).
|
|
358
|
+
const targetCoordinatorDaemonId = readNonEmptyString(dispatch?.payload?.coordinatorDaemonId)
|
|
359
|
+
|| readNonEmptyString(args.targetCoordinatorDaemonId);
|
|
348
360
|
queuePendingMeshCoordinatorEvent({
|
|
349
361
|
event: eventName,
|
|
350
362
|
meshId: args.meshId,
|
|
@@ -353,7 +365,7 @@ export function reconcileDirectDispatchCompletionFromTranscript(args: {
|
|
|
353
365
|
metadataEvent,
|
|
354
366
|
coordinatorMessage: buildMeshSystemMessage({ event: eventName, nodeLabel, metadataEvent }),
|
|
355
367
|
queuedAt: Date.now(),
|
|
356
|
-
...(
|
|
368
|
+
...(targetCoordinatorDaemonId ? { targetCoordinatorDaemonId } : {}),
|
|
357
369
|
...(targetCoordinatorSessionId ? { targetCoordinatorSessionId } : {}),
|
|
358
370
|
});
|
|
359
371
|
|
|
@@ -120,6 +120,19 @@ export const GOAL_PREVIEW_MAX = 120;
|
|
|
120
120
|
*/
|
|
121
121
|
export const COMPACT_STATUS_GOAL_PREVIEW_MAX = 80;
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* mesh_mission_list default fold sizes. Without an explicit `statuses` filter the
|
|
125
|
+
* tool returns non-terminal (active/paused) missions in full detail but folds the
|
|
126
|
+
* completed/abandoned history into counts + a capped newest-first id list — a mesh
|
|
127
|
+
* can accumulate hundreds of terminal missions and returning them all (each with a
|
|
128
|
+
* ledger-scanned stats rollup) blew past the tool payload / token budget and spilled
|
|
129
|
+
* to a file. When an explicit `statuses` filter IS given, the matching missions are
|
|
130
|
+
* returned in detail but still bounded by MESH_MISSION_LIST_STATUS_LIMIT so a
|
|
131
|
+
* status:["completed"] call on a huge history stays bounded (overflow → truncated).
|
|
132
|
+
*/
|
|
133
|
+
export const MESH_MISSION_LIST_HISTORY_ID_LIMIT = 30;
|
|
134
|
+
export const MESH_MISSION_LIST_STATUS_LIMIT = 50;
|
|
135
|
+
|
|
123
136
|
function normalizeMissionStatus(value: unknown): MeshMissionStatus {
|
|
124
137
|
return MESH_MISSION_STATUSES.includes(value as MeshMissionStatus)
|
|
125
138
|
? value as MeshMissionStatus
|
|
@@ -542,6 +555,120 @@ export function listMeshMissionSummaries(
|
|
|
542
555
|
return options?.verbose ? full : full.map(summary => slimMissionSummary(summary));
|
|
543
556
|
}
|
|
544
557
|
|
|
558
|
+
/** Shaped mesh_mission_list result: bounded detail list + optional folded history. */
|
|
559
|
+
export interface MeshMissionListResult {
|
|
560
|
+
/** Detailed (slim or verbose) mission summaries — bounded, newest-first. */
|
|
561
|
+
missions: MeshMissionSummary[] | MeshMissionSlimSummary[];
|
|
562
|
+
/**
|
|
563
|
+
* Completed/abandoned missions folded to counts + ids. Present (default path)
|
|
564
|
+
* when no explicit status filter is given and terminal missions exist. Null when
|
|
565
|
+
* an explicit status filter is active (those missions go into `missions`).
|
|
566
|
+
*/
|
|
567
|
+
historyFold: MeshStatusMissionsHistoryFold | null;
|
|
568
|
+
/** True when an explicit status filter matched more than `limit` missions. */
|
|
569
|
+
truncated: boolean;
|
|
570
|
+
/** Total missions matching the query BEFORE the detail-list cap was applied. */
|
|
571
|
+
matched: number;
|
|
572
|
+
/** Newest-first ids of missions dropped by the detail-list cap (truncated path). */
|
|
573
|
+
overflowIds?: string[];
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* mesh_mission_list projection: payload-bounded regardless of mesh mission count.
|
|
578
|
+
*
|
|
579
|
+
* Default (no explicit `statuses`): non-terminal missions (active/paused) return in
|
|
580
|
+
* detail; completed/abandoned missions are folded to counts + a capped id list
|
|
581
|
+
* (historyFold), NOT emitted one-by-one. This is what keeps the tool from returning
|
|
582
|
+
* hundreds of terminal missions (each with a ledger stats rollup) and overflowing the
|
|
583
|
+
* token budget.
|
|
584
|
+
*
|
|
585
|
+
* Explicit `statuses` (e.g. ["completed"]): the coordinator asked to see those
|
|
586
|
+
* missions, so they ARE returned in detail — but still bounded by `limit` (default
|
|
587
|
+
* MESH_MISSION_LIST_STATUS_LIMIT). Overflow beyond `limit` is reported via
|
|
588
|
+
* truncated:true + overflowIds rather than silently dropped.
|
|
589
|
+
*
|
|
590
|
+
* MAGI + verbose semantics match listMeshMissionSummaries. `withStats` opts each
|
|
591
|
+
* detailed mission into the ledger-scanned stats rollup (off by default — the tasks
|
|
592
|
+
* aggregate is enough for a list view).
|
|
593
|
+
*/
|
|
594
|
+
export function listMeshMissionsForTool(
|
|
595
|
+
meshId: string,
|
|
596
|
+
options?: {
|
|
597
|
+
statuses?: MeshMissionStatus[];
|
|
598
|
+
verbose?: boolean;
|
|
599
|
+
includeMagi?: boolean;
|
|
600
|
+
withStats?: boolean;
|
|
601
|
+
limit?: number;
|
|
602
|
+
historyIdLimit?: number;
|
|
603
|
+
},
|
|
604
|
+
): MeshMissionListResult {
|
|
605
|
+
const explicitStatuses = options?.statuses && options.statuses.length > 0 ? options.statuses : undefined;
|
|
606
|
+
const includeMagi = options?.includeMagi === true;
|
|
607
|
+
const verbose = options?.verbose === true;
|
|
608
|
+
const withStats = options?.withStats === true;
|
|
609
|
+
const limit = Math.max(1, options?.limit ?? MESH_MISSION_LIST_STATUS_LIMIT);
|
|
610
|
+
const historyIdLimit = Math.max(0, options?.historyIdLimit ?? MESH_MISSION_LIST_HISTORY_ID_LIMIT);
|
|
611
|
+
|
|
612
|
+
const passesMagi = (m: MeshMissionRecord) => includeMagi || !(m.source === 'magi' && m.status === 'completed');
|
|
613
|
+
const byUpdatedDesc = (a: MeshMissionRecord, b: MeshMissionRecord) => (b.updatedAt || '').localeCompare(a.updatedAt || '');
|
|
614
|
+
|
|
615
|
+
const project = (mission: MeshMissionRecord): MeshMissionSummary | MeshMissionSlimSummary => {
|
|
616
|
+
let summary = summarizeMeshMission(meshId, mission);
|
|
617
|
+
if (withStats) {
|
|
618
|
+
try {
|
|
619
|
+
summary = { ...summary, stats: computeMeshMissionStats(meshId, mission.id) };
|
|
620
|
+
} catch { /* stats optional — omit on failure */ }
|
|
621
|
+
}
|
|
622
|
+
return verbose ? summary : slimMissionSummary(summary);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const foldHistory = (history: MeshMissionRecord[]): MeshStatusMissionsHistoryFold | null => {
|
|
626
|
+
if (history.length === 0) return null;
|
|
627
|
+
const byStatus: Record<string, number> = {};
|
|
628
|
+
for (const m of history) byStatus[m.status] = (byStatus[m.status] ?? 0) + 1;
|
|
629
|
+
return {
|
|
630
|
+
count: history.length,
|
|
631
|
+
byStatus,
|
|
632
|
+
missionIds: history.slice(0, historyIdLimit).map(m => m.id),
|
|
633
|
+
note: 'Completed/abandoned missions are folded to counts + ids. Pass status (e.g. status:["completed"]) to list them in detail.',
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
if (explicitStatuses) {
|
|
638
|
+
// Explicit filter: return matching missions in detail, capped at `limit`.
|
|
639
|
+
const matched = getMeshMissions(meshId, explicitStatuses)
|
|
640
|
+
.filter(passesMagi)
|
|
641
|
+
.sort(byUpdatedDesc);
|
|
642
|
+
const shown = matched.slice(0, limit);
|
|
643
|
+
const overflow = matched.slice(limit);
|
|
644
|
+
return {
|
|
645
|
+
missions: shown.map(project) as MeshMissionSummary[] | MeshMissionSlimSummary[],
|
|
646
|
+
historyFold: null,
|
|
647
|
+
truncated: overflow.length > 0,
|
|
648
|
+
matched: matched.length,
|
|
649
|
+
...(overflow.length > 0 ? { overflowIds: overflow.map(m => m.id) } : {}),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Default: detail for non-terminal missions, fold terminal history.
|
|
654
|
+
const all = getMeshMissions(meshId).filter(passesMagi);
|
|
655
|
+
const live = all
|
|
656
|
+
.filter(m => m.status === 'active' || m.status === 'paused')
|
|
657
|
+
.sort(byUpdatedDesc);
|
|
658
|
+
const history = all
|
|
659
|
+
.filter(m => m.status === 'completed' || m.status === 'abandoned')
|
|
660
|
+
.sort(byUpdatedDesc);
|
|
661
|
+
const shown = live.slice(0, limit);
|
|
662
|
+
const overflow = live.slice(limit);
|
|
663
|
+
return {
|
|
664
|
+
missions: shown.map(project) as MeshMissionSummary[] | MeshMissionSlimSummary[],
|
|
665
|
+
historyFold: foldHistory(history),
|
|
666
|
+
truncated: overflow.length > 0,
|
|
667
|
+
matched: live.length,
|
|
668
|
+
...(overflow.length > 0 ? { overflowIds: overflow.map(m => m.id) } : {}),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
545
672
|
/**
|
|
546
673
|
* M3-3: render active missions as a prompt section for {{mission}}.
|
|
547
674
|
* Empty string when no active mission — the prompt stays byte-identical to
|
|
@@ -861,6 +861,119 @@ function parseConversationDb(
|
|
|
861
861
|
return messages.length > 0 ? messages : null;
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
+
/**
|
|
865
|
+
* Assemble the history.jsonl user-prompt index for a single session id into a
|
|
866
|
+
* partial NativeHistorySession (user prompts only — history.jsonl never carries
|
|
867
|
+
* assistant answers). Returns null when the index has no rows for this session.
|
|
868
|
+
* Shared by the history.jsonl read case and the .db sibling fallback.
|
|
869
|
+
*/
|
|
870
|
+
function readHistoryJsonlSession(
|
|
871
|
+
resolvedSessionId: string,
|
|
872
|
+
workspace?: string,
|
|
873
|
+
): NativeHistorySession | null {
|
|
874
|
+
if (!isUuidLike(resolvedSessionId)) return null;
|
|
875
|
+
const rows = readHistoryRows().filter((r) => r.conversationId === resolvedSessionId);
|
|
876
|
+
if (rows.length === 0) return null;
|
|
877
|
+
|
|
878
|
+
rows.sort((a, b) => a.receivedAt - b.receivedAt);
|
|
879
|
+
const firstWorkspace = workspace || rows.find((r) => r.workspace)?.workspace || '';
|
|
880
|
+
|
|
881
|
+
const messages: NativeHistoryMessage[] = [];
|
|
882
|
+
if (firstWorkspace) {
|
|
883
|
+
messages.push({
|
|
884
|
+
ts: new Date(rows[0].receivedAt).toISOString(),
|
|
885
|
+
receivedAt: rows[0].receivedAt,
|
|
886
|
+
role: 'system',
|
|
887
|
+
content: firstWorkspace,
|
|
888
|
+
kind: 'session_start',
|
|
889
|
+
agent: 'antigravity-cli',
|
|
890
|
+
historySessionId: resolvedSessionId,
|
|
891
|
+
workspace: firstWorkspace,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
for (const row of rows) {
|
|
896
|
+
const msg: NativeHistoryMessage = {
|
|
897
|
+
ts: new Date(row.receivedAt).toISOString(),
|
|
898
|
+
receivedAt: row.receivedAt,
|
|
899
|
+
role: 'user',
|
|
900
|
+
content: row.display,
|
|
901
|
+
kind: 'standard',
|
|
902
|
+
agent: 'antigravity-cli',
|
|
903
|
+
historySessionId: resolvedSessionId,
|
|
904
|
+
};
|
|
905
|
+
if (row.workspace) msg.workspace = row.workspace;
|
|
906
|
+
messages.push(msg);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return {
|
|
910
|
+
messages,
|
|
911
|
+
providerSessionId: resolvedSessionId,
|
|
912
|
+
source: 'provider-native',
|
|
913
|
+
sourcePath: historyJsonlPath(),
|
|
914
|
+
sourceMtimeMs: statMtimeMs(historyJsonlPath()),
|
|
915
|
+
nativeHistoryCoverage: 'partial',
|
|
916
|
+
partialReason: 'antigravity_cli_history_jsonl_contains_user_prompts_only',
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* ANTIGRAVITY-COMPLETION-DASHBOARD-GAP: recover a bound session's transcript
|
|
922
|
+
* from the legacy sibling sources when its per-session .db read came back empty
|
|
923
|
+
* (transient WAL lock, assistant step not yet flushed, or a decode miss on a
|
|
924
|
+
* future step_payload schema). Tried in descending fidelity for the SAME
|
|
925
|
+
* session id — never cross-binds to another conversation:
|
|
926
|
+
* 1. brain transcript (full coverage, authoritative when present),
|
|
927
|
+
* 2. sibling conversations/<uuid>.pb (best-effort raw text),
|
|
928
|
+
* 3. history.jsonl (partial — user prompts only).
|
|
929
|
+
* Returns null when no sibling yields any message.
|
|
930
|
+
*/
|
|
931
|
+
function readAntigravitySiblingFallback(
|
|
932
|
+
sessionId: string,
|
|
933
|
+
workspace?: string,
|
|
934
|
+
): NativeHistorySession | null {
|
|
935
|
+
if (!isUuidLike(sessionId)) return null;
|
|
936
|
+
|
|
937
|
+
// (1) brain transcript — full coverage when antigravity actually wrote it.
|
|
938
|
+
const brainPath = findBrainTranscriptPath(sessionId);
|
|
939
|
+
if (brainPath && statMtimeMs(brainPath) > 0) {
|
|
940
|
+
const brainMessages = parseBrainTranscript(brainPath, sessionId, workspace);
|
|
941
|
+
if (brainMessages && brainMessages.length > 0) {
|
|
942
|
+
return {
|
|
943
|
+
messages: brainMessages,
|
|
944
|
+
providerSessionId: sessionId,
|
|
945
|
+
source: 'provider-native',
|
|
946
|
+
sourcePath: brainPath,
|
|
947
|
+
sourceMtimeMs: statMtimeMs(brainPath),
|
|
948
|
+
nativeHistoryCoverage: 'full',
|
|
949
|
+
workspace,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// (2) sibling .pb — best-effort raw text extraction.
|
|
955
|
+
const pbPath = resolvePathInside(conversationsRoot(), `${sessionId}.pb`);
|
|
956
|
+
if (pbPath && fs.existsSync(pbPath)) {
|
|
957
|
+
const pbMessages = parsePbFile(pbPath, sessionId);
|
|
958
|
+
if (pbMessages && pbMessages.length > 0) {
|
|
959
|
+
return {
|
|
960
|
+
messages: pbMessages,
|
|
961
|
+
providerSessionId: sessionId,
|
|
962
|
+
source: 'provider-native',
|
|
963
|
+
sourcePath: pbPath,
|
|
964
|
+
sourceMtimeMs: statMtimeMs(pbPath),
|
|
965
|
+
nativeHistoryCoverage: 'best-effort',
|
|
966
|
+
partialReason: 'antigravity_cli_pb_raw_text_extraction',
|
|
967
|
+
workspace,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// (3) history.jsonl — partial (user prompts only). Last resort so the
|
|
973
|
+
// dashboard at least shows the prompt when no answer source is readable.
|
|
974
|
+
return readHistoryJsonlSession(sessionId, workspace);
|
|
975
|
+
}
|
|
976
|
+
|
|
864
977
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
865
978
|
|
|
866
979
|
/**
|
|
@@ -916,17 +1029,31 @@ export function readSession(
|
|
|
916
1029
|
if (!isUuidLike(dbSessionId)) return null;
|
|
917
1030
|
|
|
918
1031
|
const messages = parseConversationDb(sessionPath, dbSessionId, workspace);
|
|
919
|
-
if (
|
|
1032
|
+
if (messages && messages.length > 0) {
|
|
1033
|
+
return {
|
|
1034
|
+
messages,
|
|
1035
|
+
providerSessionId: dbSessionId,
|
|
1036
|
+
source: 'provider-native',
|
|
1037
|
+
sourcePath: sessionPath,
|
|
1038
|
+
sourceMtimeMs,
|
|
1039
|
+
nativeHistoryCoverage: 'full',
|
|
1040
|
+
workspace,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
920
1043
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1044
|
+
// ANTIGRAVITY-COMPLETION-DASHBOARD-GAP: the bound .db yielded nothing this
|
|
1045
|
+
// read — transient (WAL lock / assistant step not yet flushed) or a decode
|
|
1046
|
+
// miss on a future schema. The dispatcher binds STRAIGHT to <uuid>.db once
|
|
1047
|
+
// the session id is known and never re-resolves to a sibling source, so a
|
|
1048
|
+
// null here previously collapsed the whole read to native-unavailable even
|
|
1049
|
+
// when the SAME session's assistant answer was recoverable from the legacy
|
|
1050
|
+
// brain transcript / .pb / history.jsonl. Fall back across those sibling
|
|
1051
|
+
// sources for the same session id before giving up. This is read-only,
|
|
1052
|
+
// scoped to the bound session, and cannot cross-bind to another conversation.
|
|
1053
|
+
const siblingFallback = readAntigravitySiblingFallback(dbSessionId, workspace);
|
|
1054
|
+
if (siblingFallback) return siblingFallback;
|
|
1055
|
+
|
|
1056
|
+
return null;
|
|
930
1057
|
}
|
|
931
1058
|
|
|
932
1059
|
// ── Case 2b: .pb conversation file (legacy protobuf) ─────────────────────
|
|
@@ -950,52 +1077,7 @@ export function readSession(
|
|
|
950
1077
|
|
|
951
1078
|
// ── Case 3: history.jsonl (user prompts index) ──────────────────────────
|
|
952
1079
|
if (path.basename(sessionPath) === 'history.jsonl') {
|
|
953
|
-
|
|
954
|
-
if (!resolvedSessionId || !isUuidLike(resolvedSessionId)) return null;
|
|
955
|
-
|
|
956
|
-
const rows = readHistoryRows().filter((r) => r.conversationId === resolvedSessionId);
|
|
957
|
-
if (rows.length === 0) return null;
|
|
958
|
-
|
|
959
|
-
rows.sort((a, b) => a.receivedAt - b.receivedAt);
|
|
960
|
-
const firstWorkspace = workspace || rows.find((r) => r.workspace)?.workspace || '';
|
|
961
|
-
|
|
962
|
-
const messages: NativeHistoryMessage[] = [];
|
|
963
|
-
if (firstWorkspace) {
|
|
964
|
-
messages.push({
|
|
965
|
-
ts: new Date(rows[0].receivedAt).toISOString(),
|
|
966
|
-
receivedAt: rows[0].receivedAt,
|
|
967
|
-
role: 'system',
|
|
968
|
-
content: firstWorkspace,
|
|
969
|
-
kind: 'session_start',
|
|
970
|
-
agent: 'antigravity-cli',
|
|
971
|
-
historySessionId: resolvedSessionId,
|
|
972
|
-
workspace: firstWorkspace,
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
for (const row of rows) {
|
|
977
|
-
const msg: NativeHistoryMessage = {
|
|
978
|
-
ts: new Date(row.receivedAt).toISOString(),
|
|
979
|
-
receivedAt: row.receivedAt,
|
|
980
|
-
role: 'user',
|
|
981
|
-
content: row.display,
|
|
982
|
-
kind: 'standard',
|
|
983
|
-
agent: 'antigravity-cli',
|
|
984
|
-
historySessionId: resolvedSessionId,
|
|
985
|
-
};
|
|
986
|
-
if (row.workspace) msg.workspace = row.workspace;
|
|
987
|
-
messages.push(msg);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
return {
|
|
991
|
-
messages,
|
|
992
|
-
providerSessionId: resolvedSessionId,
|
|
993
|
-
source: 'provider-native',
|
|
994
|
-
sourcePath: sessionPath,
|
|
995
|
-
sourceMtimeMs,
|
|
996
|
-
nativeHistoryCoverage: 'partial',
|
|
997
|
-
partialReason: 'antigravity_cli_history_jsonl_contains_user_prompts_only',
|
|
998
|
-
};
|
|
1080
|
+
return readHistoryJsonlSession(sessionId || '', workspace);
|
|
999
1081
|
}
|
|
1000
1082
|
|
|
1001
1083
|
return null;
|