@adhdev/daemon-core 0.9.82-rc.347 → 0.9.82-rc.349
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 +2 -2
- package/dist/index.js +4665 -4467
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4652 -4456
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-utils.d.ts +0 -14
- package/dist/mesh/mesh-missions.d.ts +48 -0
- package/package.json +2 -2
- package/src/index.ts +2 -2
- package/src/mesh/mesh-events-coordinator.ts +36 -6
- package/src/mesh/mesh-events-utils.ts +36 -7
- package/src/mesh/mesh-missions.ts +79 -5
|
@@ -49,20 +49,6 @@ export declare function readWorkerResultMetadata(event: Record<string, unknown>)
|
|
|
49
49
|
* and the held-event ledger audit record so both read the summary the same way.
|
|
50
50
|
*/
|
|
51
51
|
export declare function readMeshCompletionSummary(metadataEvent: Record<string, unknown>): string;
|
|
52
|
-
/**
|
|
53
|
-
* A coordinator that surfaces a REMOTE worker's mesh session has no local instance for
|
|
54
|
-
* it, so the status snapshot's getLastDisplayMessage has nothing to read and the only
|
|
55
|
-
* preview the coordinator-mirrored copy could ever carry comes from the worker's
|
|
56
|
-
* completion event. The worker's latest assistant reply rides on that event as
|
|
57
|
-
* `finalSummary` (and as `workerResult.summary` / `result.summary` on some paths).
|
|
58
|
-
*
|
|
59
|
-
* This resolves that assistant text into a preview the coordinator can stamp onto its
|
|
60
|
-
* mirror entry so the mobile inbox shows the worker's latest assistant response instead
|
|
61
|
-
* of being stuck on the first dispatched user task (which is the only message the
|
|
62
|
-
* coordinator-side transcript ever holds). Returns undefined when the event carries no
|
|
63
|
-
* assistant text — non-completion lifecycle events (generating_started / ready without a
|
|
64
|
-
* summary) must NOT clobber a previously surfaced preview.
|
|
65
|
-
*/
|
|
66
52
|
export declare function resolveMeshSurfacedSessionPreview(metadataEvent: Record<string, unknown>): {
|
|
67
53
|
preview: string;
|
|
68
54
|
role: 'assistant';
|
|
@@ -62,6 +62,13 @@ export interface MeshMissionSlimSummary extends Omit<MeshMissionSummary, 'goal'>
|
|
|
62
62
|
}
|
|
63
63
|
/** Max chars of goal text retained in the slim (compact) mission summary. */
|
|
64
64
|
export declare const GOAL_PREVIEW_MAX = 120;
|
|
65
|
+
/**
|
|
66
|
+
* Shorter goal preview used by the mesh_status compact (LLM coordinator) surface.
|
|
67
|
+
* The coordinator only needs to recognize a mission, not read its full goal, and
|
|
68
|
+
* mesh_status repeats every live mission on every poll — so the status preview is
|
|
69
|
+
* tighter than the dashboard / mesh_mission_list preview (GOAL_PREVIEW_MAX).
|
|
70
|
+
*/
|
|
71
|
+
export declare const COMPACT_STATUS_GOAL_PREVIEW_MAX = 80;
|
|
65
72
|
export declare function upsertMeshMission(meshId: string, input: {
|
|
66
73
|
id?: string;
|
|
67
74
|
title: string;
|
|
@@ -93,6 +100,47 @@ export declare function getMeshStatusMissionSummaries(meshId: string, options?:
|
|
|
93
100
|
verbose?: boolean;
|
|
94
101
|
withStats?: boolean;
|
|
95
102
|
}): MeshMissionSummary[] | MeshMissionSlimSummary[];
|
|
103
|
+
/** Folded completed/abandoned history for the mesh_status compact surface. */
|
|
104
|
+
export interface MeshStatusMissionsHistoryFold {
|
|
105
|
+
/** Total completed + abandoned missions. */
|
|
106
|
+
count: number;
|
|
107
|
+
/** Count by lifecycle status (e.g. { completed, abandoned }). */
|
|
108
|
+
byStatus: Record<string, number>;
|
|
109
|
+
/** Newest-first id list (capped) so each folded mission stays addressable. */
|
|
110
|
+
missionIds: string[];
|
|
111
|
+
note: string;
|
|
112
|
+
}
|
|
113
|
+
/** Compact mesh_status mission projection: live detail + folded history. */
|
|
114
|
+
export interface MeshStatusMissionsCompact {
|
|
115
|
+
/**
|
|
116
|
+
* Active + paused missions, goal-elided to COMPACT_STATUS_GOAL_PREVIEW_MAX and
|
|
117
|
+
* WITHOUT the operational `stats` rollup — the `tasks` aggregate already carries
|
|
118
|
+
* progress, and stats (durations/retries) is a verbose/dashboard concern.
|
|
119
|
+
*/
|
|
120
|
+
live: MeshMissionSlimSummary[];
|
|
121
|
+
/** Completed + abandoned missions folded to counts + ids; null when none. */
|
|
122
|
+
historyFold: MeshStatusMissionsHistoryFold | null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Mission projection for the mesh_status COMPACT (LLM coordinator) surface.
|
|
126
|
+
*
|
|
127
|
+
* Unlike getMeshStatusMissionSummaries (which emits every live mission plus a
|
|
128
|
+
* capped slice of full-detail history, each carrying a stats rollup), this keeps
|
|
129
|
+
* per-mission detail ONLY for live (active/paused) missions and folds the whole
|
|
130
|
+
* completed/abandoned history into a counts + id-list summary. Combined with the
|
|
131
|
+
* tighter goal preview and dropped stats, this is what keeps the compact
|
|
132
|
+
* mesh_status payload bounded as a mesh accumulates missions — the missions
|
|
133
|
+
* section previously dominated the payload (full goalPreview + tasks + stats per
|
|
134
|
+
* mission, for every live mission and up to 10 history missions, on every poll).
|
|
135
|
+
*
|
|
136
|
+
* The stored missions are untouched; this is an output-only projection. Full
|
|
137
|
+
* mission detail (goal text + stats + history) stays available via
|
|
138
|
+
* mesh_status verbose=true or mesh_mission_list.
|
|
139
|
+
*/
|
|
140
|
+
export declare function getMeshStatusMissionsCompact(meshId: string, options?: {
|
|
141
|
+
previewMax?: number;
|
|
142
|
+
historyIdLimit?: number;
|
|
143
|
+
}): MeshStatusMissionsCompact;
|
|
96
144
|
/**
|
|
97
145
|
* Read-only mission listing for the mesh_mission_list tool. Returns summaries
|
|
98
146
|
* (record + live task aggregate) for missions matching `statuses` — or every
|
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.349",
|
|
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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.349",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
package/src/index.ts
CHANGED
|
@@ -188,8 +188,8 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
188
188
|
|
|
189
189
|
// ── Mesh Coordinator ──
|
|
190
190
|
export { buildCoordinatorSystemPrompt } from './mesh/coordinator-prompt.js';
|
|
191
|
-
export { upsertMeshMission, getMeshMissions, getMeshMission, summarizeMissionTasks, summarizeMeshMission, getActiveMeshMissionSummaries, getMeshStatusMissionSummaries, listMeshMissionSummaries, buildMissionPromptSection, GOAL_PREVIEW_MAX, MESH_MISSION_STATUSES } from './mesh/mesh-missions.js';
|
|
192
|
-
export type { MeshMissionRecord, MeshMissionStatus, MeshMissionSummary, MeshMissionSlimSummary, MeshMissionTaskAggregate } from './mesh/mesh-missions.js';
|
|
191
|
+
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';
|
|
192
|
+
export type { MeshMissionRecord, MeshMissionStatus, MeshMissionSummary, MeshMissionSlimSummary, MeshMissionTaskAggregate, MeshStatusMissionsCompact, MeshStatusMissionsHistoryFold } from './mesh/mesh-missions.js';
|
|
193
193
|
export { computeMeshTaskStats, computeMeshMissionStats } from './mesh/mesh-task-stats.js';
|
|
194
194
|
export type { MeshTaskStats, MeshMissionStats } from './mesh/mesh-task-stats.js';
|
|
195
195
|
export { deriveMeshReviewInboxItems } from './mesh/mesh-review-inbox.js';
|
|
@@ -14,6 +14,7 @@ import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents } f
|
|
|
14
14
|
import type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
|
|
15
15
|
import { resolveWorkerDelegateRouting, recordUnroutableDelegateEvent, isUnroutableDelegateRejection } from './mesh-routing.js';
|
|
16
16
|
import { enqueueUnresolvedDelegateForward, peekUnresolvedDelegateForwards, ackUnresolvedDelegateForward } from './mesh-unresolved-forward-outbox.js';
|
|
17
|
+
import { getLastDisplayMessage } from '../status/snapshot.js';
|
|
17
18
|
import { resolveDelegatedWorkerAutoApprove, resolveProviderMaxParallel, resolveNodeSchedulingPriority, normalizeMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
18
19
|
import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
19
20
|
import { normalizeMeshNodeId, meshNodeIdMatches, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
@@ -1424,6 +1425,26 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1424
1425
|
(sourceSession?.getState()?.settings as Record<string, unknown>)?.meshCoordinatorSessionId,
|
|
1425
1426
|
) || readNonEmptyString(args.metadataEvent.meshCoordinatorSessionId);
|
|
1426
1427
|
|
|
1428
|
+
// T2: a summary-less completion (and any non-completion status-sync event) carries no
|
|
1429
|
+
// assistant text on the event, so resolveMeshSurfacedSessionPreview had nothing to surface
|
|
1430
|
+
// and the coordinator's inbox mirror stayed stuck on the first dispatched user task. When
|
|
1431
|
+
// THIS daemon hosts the live worker instance (sourceSession present), derive the worker's
|
|
1432
|
+
// latest display message straight from its transcript and attach it to the event as
|
|
1433
|
+
// lastMessagePreview/lastMessageRole/lastMessageAt. resolveMeshSurfacedSessionPreview reads
|
|
1434
|
+
// these as an assistant-only fallback; they also ride the pending-queue + P2P relay
|
|
1435
|
+
// (handleMeshForwardEvent whitelist) so a remote coordinator can surface them. A remote
|
|
1436
|
+
// coordinator has no local instance and keeps relying on the relayed fields — unchanged.
|
|
1437
|
+
const enrichedMetadataEvent = ((): Record<string, unknown> => {
|
|
1438
|
+
const last = sourceSession ? getLastDisplayMessage(sourceSession.getState()) : null;
|
|
1439
|
+
if (!last || !last.preview) return args.metadataEvent;
|
|
1440
|
+
return {
|
|
1441
|
+
...args.metadataEvent,
|
|
1442
|
+
lastMessagePreview: last.preview,
|
|
1443
|
+
lastMessageRole: last.role,
|
|
1444
|
+
...(last.receivedAt > 0 ? { lastMessageAt: last.receivedAt } : {}),
|
|
1445
|
+
};
|
|
1446
|
+
})();
|
|
1447
|
+
|
|
1427
1448
|
// R2: cloud P2P dashboard metadata sync. The cloud daemon used to do this from its own
|
|
1428
1449
|
// relay listener; now the single core forwarder invokes the injected hook (no-op on
|
|
1429
1450
|
// standalone) so the event path stays single-listener and the local code path is identical
|
|
@@ -1435,15 +1456,17 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1435
1456
|
// mirror would stay stuck on the first dispatched user task. Resolve the
|
|
1436
1457
|
// worker's latest assistant reply (carried on the completion event's
|
|
1437
1458
|
// finalSummary / workerResult) into a preview the mirror can stamp, so the
|
|
1438
|
-
// mobile inbox reflects the assistant response.
|
|
1439
|
-
//
|
|
1440
|
-
//
|
|
1441
|
-
|
|
1459
|
+
// mobile inbox reflects the assistant response. Completion events carry assistant
|
|
1460
|
+
// text as finalSummary; a summary-less completion / status sync falls back to the
|
|
1461
|
+
// worker's latest assistant display message (enrichedMetadataEvent.lastMessage*).
|
|
1462
|
+
// For a mid-turn user-only event this is undefined and the prior surfaced preview
|
|
1463
|
+
// is preserved downstream (no clobber).
|
|
1464
|
+
const surfacedPreview = resolveMeshSurfacedSessionPreview(enrichedMetadataEvent);
|
|
1442
1465
|
components.onMeshCoordinatorEventForwarded({
|
|
1443
1466
|
event: args.event,
|
|
1444
1467
|
meshId: args.meshId,
|
|
1445
1468
|
nodeId: eventNodeId || undefined,
|
|
1446
|
-
...
|
|
1469
|
+
...enrichedMetadataEvent,
|
|
1447
1470
|
// Ensure a `workspace` field reaches updateMeshOwnedSession even when the
|
|
1448
1471
|
// worker provider event only carried `workspaceName`. The merge spread of
|
|
1449
1472
|
// metadataEvent above wins when it already has a non-empty `workspace`.
|
|
@@ -1877,7 +1900,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1877
1900
|
workspace: readNonEmptyString(args.metadataEvent.workspace)
|
|
1878
1901
|
|| readNonEmptyString(args.metadataEvent.workspaceName),
|
|
1879
1902
|
metadataEvent: {
|
|
1880
|
-
...
|
|
1903
|
+
...enrichedMetadataEvent,
|
|
1881
1904
|
...(recoveryContext ? { recoveryContext } : {}),
|
|
1882
1905
|
// Stash the coordinator session id INSIDE metadataEvent too, so it survives the
|
|
1883
1906
|
// P2P relay serialization (buildForwardPayloadFromPending spreads metadata; the
|
|
@@ -1955,6 +1978,13 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
|
|
|
1955
1978
|
providerName: readNonEmptyString(payload.providerName),
|
|
1956
1979
|
...(payload.sessionSettings && typeof payload.sessionSettings === 'object' && !Array.isArray(payload.sessionSettings) ? { sessionSettings: payload.sessionSettings } : {}),
|
|
1957
1980
|
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
1981
|
+
// T2: carry the worker's status-snapshot last-message preview across the machine
|
|
1982
|
+
// boundary so a summary-less completion still surfaces the assistant reply in the
|
|
1983
|
+
// coordinator's inbox mirror. resolveMeshSurfacedSessionPreview reads these
|
|
1984
|
+
// (assistant-role only) when finalSummary is absent.
|
|
1985
|
+
lastMessagePreview: readNonEmptyString(payload.lastMessagePreview),
|
|
1986
|
+
lastMessageRole: readNonEmptyString(payload.lastMessageRole),
|
|
1987
|
+
...(payload.lastMessageAt !== undefined ? { lastMessageAt: payload.lastMessageAt } : {}),
|
|
1958
1988
|
jobId: readNonEmptyString(payload.jobId),
|
|
1959
1989
|
interactionId: readNonEmptyString(payload.interactionId),
|
|
1960
1990
|
status: readNonEmptyString(payload.status),
|
|
@@ -162,17 +162,46 @@ export function readMeshCompletionSummary(metadataEvent: Record<string, unknown>
|
|
|
162
162
|
* assistant text — non-completion lifecycle events (generating_started / ready without a
|
|
163
163
|
* summary) must NOT clobber a previously surfaced preview.
|
|
164
164
|
*/
|
|
165
|
+
function truncateSurfacedPreview(text: string): string {
|
|
166
|
+
const truncationSuffix = '...[truncated]';
|
|
167
|
+
return text.length > MESH_SURFACED_PREVIEW_MAX_CHARS
|
|
168
|
+
? `${text.slice(0, MESH_SURFACED_PREVIEW_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}`
|
|
169
|
+
: text;
|
|
170
|
+
}
|
|
171
|
+
|
|
165
172
|
export function resolveMeshSurfacedSessionPreview(
|
|
166
173
|
metadataEvent: Record<string, unknown>,
|
|
167
174
|
): { preview: string; role: 'assistant'; receivedAt: number } | undefined {
|
|
175
|
+
// 1) Completion summary — the worker's final assistant text rides a completion event
|
|
176
|
+
// as finalSummary / workerResult.summary / result.summary.
|
|
168
177
|
const summaryText = readMeshCompletionSummary(metadataEvent);
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
if (summaryText) {
|
|
179
|
+
return {
|
|
180
|
+
preview: truncateSurfacedPreview(summaryText),
|
|
181
|
+
role: 'assistant',
|
|
182
|
+
receivedAt: readEventTimestampValue(metadataEvent.timestamp),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// 2) Status-snapshot fallback — a completion that carried NO summary (and any later
|
|
186
|
+
// status-sync event) still ships the worker's latest display message on the event as
|
|
187
|
+
// lastMessagePreview/lastMessageRole/lastMessageAt (computed by getLastDisplayMessage
|
|
188
|
+
// on the worker, attached in injectMeshSystemMessage and carried over the relay).
|
|
189
|
+
// Surface it ONLY when it is an assistant reply: a user-role last message means the
|
|
190
|
+
// turn is still mid-flight (the dispatched task is the only message), and surfacing it
|
|
191
|
+
// would both re-introduce the "inbox stuck on the user task" bug and clobber a
|
|
192
|
+
// previously surfaced assistant preview. The web inbox guard renders only an
|
|
193
|
+
// assistant-role preview anyway, so a user-role one would be inert there.
|
|
194
|
+
const lastPreview = readNonEmptyString(metadataEvent.lastMessagePreview);
|
|
195
|
+
const lastRole = readNonEmptyString(metadataEvent.lastMessageRole);
|
|
196
|
+
if (lastPreview && lastRole === 'assistant') {
|
|
197
|
+
return {
|
|
198
|
+
preview: truncateSurfacedPreview(lastPreview),
|
|
199
|
+
role: 'assistant',
|
|
200
|
+
receivedAt: readEventTimestampValue(metadataEvent.lastMessageAt)
|
|
201
|
+
|| readEventTimestampValue(metadataEvent.timestamp),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
176
205
|
}
|
|
177
206
|
|
|
178
207
|
function readEventTimestampValue(value: unknown): number {
|
|
@@ -74,6 +74,14 @@ export interface MeshMissionSlimSummary extends Omit<MeshMissionSummary, 'goal'>
|
|
|
74
74
|
/** Max chars of goal text retained in the slim (compact) mission summary. */
|
|
75
75
|
export const GOAL_PREVIEW_MAX = 120;
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Shorter goal preview used by the mesh_status compact (LLM coordinator) surface.
|
|
79
|
+
* The coordinator only needs to recognize a mission, not read its full goal, and
|
|
80
|
+
* mesh_status repeats every live mission on every poll — so the status preview is
|
|
81
|
+
* tighter than the dashboard / mesh_mission_list preview (GOAL_PREVIEW_MAX).
|
|
82
|
+
*/
|
|
83
|
+
export const COMPACT_STATUS_GOAL_PREVIEW_MAX = 80;
|
|
84
|
+
|
|
77
85
|
function normalizeMissionStatus(value: unknown): MeshMissionStatus {
|
|
78
86
|
return MESH_MISSION_STATUSES.includes(value as MeshMissionStatus)
|
|
79
87
|
? value as MeshMissionStatus
|
|
@@ -153,13 +161,13 @@ export function getActiveMeshMissionSummaries(meshId: string): MeshMissionSummar
|
|
|
153
161
|
}
|
|
154
162
|
|
|
155
163
|
/** Project a full mission summary down to the slim (goal-elided) shape. */
|
|
156
|
-
function slimMissionSummary(summary: MeshMissionSummary): MeshMissionSlimSummary {
|
|
164
|
+
function slimMissionSummary(summary: MeshMissionSummary, previewMax: number = GOAL_PREVIEW_MAX): MeshMissionSlimSummary {
|
|
157
165
|
const goal = typeof summary.goal === 'string' ? summary.goal : '';
|
|
158
|
-
const goalTruncated = goal.length >
|
|
166
|
+
const goalTruncated = goal.length > previewMax;
|
|
159
167
|
const { goal: _omitGoal, ...rest } = summary;
|
|
160
168
|
return {
|
|
161
169
|
...rest,
|
|
162
|
-
goalPreview: goalTruncated ? goal.slice(0,
|
|
170
|
+
goalPreview: goalTruncated ? goal.slice(0, previewMax) : goal,
|
|
163
171
|
goalTruncated,
|
|
164
172
|
};
|
|
165
173
|
}
|
|
@@ -197,7 +205,73 @@ export function getMeshStatusMissionSummaries(
|
|
|
197
205
|
if (options?.withStats) {
|
|
198
206
|
full = full.map(summary => ({ ...summary, stats: computeMeshMissionStats(meshId, summary.id) }));
|
|
199
207
|
}
|
|
200
|
-
return options?.verbose ? full : full.map(slimMissionSummary);
|
|
208
|
+
return options?.verbose ? full : full.map(summary => slimMissionSummary(summary));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Folded completed/abandoned history for the mesh_status compact surface. */
|
|
212
|
+
export interface MeshStatusMissionsHistoryFold {
|
|
213
|
+
/** Total completed + abandoned missions. */
|
|
214
|
+
count: number;
|
|
215
|
+
/** Count by lifecycle status (e.g. { completed, abandoned }). */
|
|
216
|
+
byStatus: Record<string, number>;
|
|
217
|
+
/** Newest-first id list (capped) so each folded mission stays addressable. */
|
|
218
|
+
missionIds: string[];
|
|
219
|
+
note: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Compact mesh_status mission projection: live detail + folded history. */
|
|
223
|
+
export interface MeshStatusMissionsCompact {
|
|
224
|
+
/**
|
|
225
|
+
* Active + paused missions, goal-elided to COMPACT_STATUS_GOAL_PREVIEW_MAX and
|
|
226
|
+
* WITHOUT the operational `stats` rollup — the `tasks` aggregate already carries
|
|
227
|
+
* progress, and stats (durations/retries) is a verbose/dashboard concern.
|
|
228
|
+
*/
|
|
229
|
+
live: MeshMissionSlimSummary[];
|
|
230
|
+
/** Completed + abandoned missions folded to counts + ids; null when none. */
|
|
231
|
+
historyFold: MeshStatusMissionsHistoryFold | null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Mission projection for the mesh_status COMPACT (LLM coordinator) surface.
|
|
236
|
+
*
|
|
237
|
+
* Unlike getMeshStatusMissionSummaries (which emits every live mission plus a
|
|
238
|
+
* capped slice of full-detail history, each carrying a stats rollup), this keeps
|
|
239
|
+
* per-mission detail ONLY for live (active/paused) missions and folds the whole
|
|
240
|
+
* completed/abandoned history into a counts + id-list summary. Combined with the
|
|
241
|
+
* tighter goal preview and dropped stats, this is what keeps the compact
|
|
242
|
+
* mesh_status payload bounded as a mesh accumulates missions — the missions
|
|
243
|
+
* section previously dominated the payload (full goalPreview + tasks + stats per
|
|
244
|
+
* mission, for every live mission and up to 10 history missions, on every poll).
|
|
245
|
+
*
|
|
246
|
+
* The stored missions are untouched; this is an output-only projection. Full
|
|
247
|
+
* mission detail (goal text + stats + history) stays available via
|
|
248
|
+
* mesh_status verbose=true or mesh_mission_list.
|
|
249
|
+
*/
|
|
250
|
+
export function getMeshStatusMissionsCompact(
|
|
251
|
+
meshId: string,
|
|
252
|
+
options?: { previewMax?: number; historyIdLimit?: number },
|
|
253
|
+
): MeshStatusMissionsCompact {
|
|
254
|
+
const previewMax = Math.max(0, options?.previewMax ?? COMPACT_STATUS_GOAL_PREVIEW_MAX);
|
|
255
|
+
const historyIdLimit = Math.max(0, options?.historyIdLimit ?? 20);
|
|
256
|
+
const all = getMeshMissions(meshId);
|
|
257
|
+
const live = all
|
|
258
|
+
.filter(m => m.status === 'active' || m.status === 'paused')
|
|
259
|
+
.map(mission => slimMissionSummary(summarizeMeshMission(meshId, mission), previewMax));
|
|
260
|
+
const history = all
|
|
261
|
+
.filter(m => m.status === 'completed' || m.status === 'abandoned')
|
|
262
|
+
.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
|
|
263
|
+
let historyFold: MeshStatusMissionsHistoryFold | null = null;
|
|
264
|
+
if (history.length > 0) {
|
|
265
|
+
const byStatus: Record<string, number> = {};
|
|
266
|
+
for (const m of history) byStatus[m.status] = (byStatus[m.status] ?? 0) + 1;
|
|
267
|
+
historyFold = {
|
|
268
|
+
count: history.length,
|
|
269
|
+
byStatus,
|
|
270
|
+
missionIds: history.slice(0, historyIdLimit).map(m => m.id),
|
|
271
|
+
note: 'Completed/abandoned missions are folded to counts + ids in compact mesh_status. Use mesh_mission_list or mesh_status verbose=true for their goal/task detail.',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return { live, historyFold };
|
|
201
275
|
}
|
|
202
276
|
|
|
203
277
|
/**
|
|
@@ -219,7 +293,7 @@ export function listMeshMissionSummaries(
|
|
|
219
293
|
const missions = getMeshMissions(meshId, statuses)
|
|
220
294
|
.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
|
|
221
295
|
const full = missions.map(mission => summarizeMeshMission(meshId, mission));
|
|
222
|
-
return options?.verbose ? full : full.map(slimMissionSummary);
|
|
296
|
+
return options?.verbose ? full : full.map(summary => slimMissionSummary(summary));
|
|
223
297
|
}
|
|
224
298
|
|
|
225
299
|
/**
|