@adhdev/daemon-core 0.9.82-rc.428 → 0.9.82-rc.429
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/commands/med-family/types.d.ts +2 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +65 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -7
- package/dist/index.mjs.map +1 -1
- package/dist/repo-mesh-types.d.ts +61 -0
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +10 -1
- package/src/commands/med-family/mesh-crud.ts +11 -1
- package/src/commands/med-family/types.ts +2 -1
- package/src/commands/router.ts +37 -3
- package/src/config/mesh-config.ts +27 -1
- package/src/index.ts +4 -1
- package/src/repo-mesh-types.ts +89 -0
|
@@ -72,6 +72,26 @@ export interface RepoMeshNode {
|
|
|
72
72
|
export type RepoMeshNodeHealth = 'online' | 'offline' | 'degraded' | 'dirty' | 'wrong_branch' | 'unknown';
|
|
73
73
|
export type RepoMeshSessionCleanupMode = 'preserve' | 'stop' | 'delete_stopped' | 'stop_and_delete';
|
|
74
74
|
export type RepoMeshSpawnedSessionVisibility = 'visible' | 'hidden';
|
|
75
|
+
/**
|
|
76
|
+
* What to do with the worker sessions a MAGI fan-out auto-launched, once the
|
|
77
|
+
* review's responses have been collected (terminal). MAGI dispatches each replica
|
|
78
|
+
* to an independent (node × provider); for a pinned target with no idle session
|
|
79
|
+
* the queue AUTO-LAUNCHES a fresh worker session. Those auto-launched sessions
|
|
80
|
+
* stay idle-LIVE after their turn (the CLI process is still running — `completed`
|
|
81
|
+
* means the task finished, not that the runtime exited), so repeated reviews leave
|
|
82
|
+
* a trail of idle live worker sessions cluttering the session list.
|
|
83
|
+
*
|
|
84
|
+
* 'stop_and_delete' (the default) force-stops AND deletes ONLY the sessions this
|
|
85
|
+
* fan-out auto-launched (verified by the per-session autoLaunchedForQueueTaskId
|
|
86
|
+
* marker — see cleanupMeshSessions). It is the only mode that covers the idle-LIVE
|
|
87
|
+
* case: delete_stopped skips live runtimes by contract, so it would no-op on the
|
|
88
|
+
* exact sessions we want gone. Reused idle sessions (no marker), the coordinator
|
|
89
|
+
* session, and any other node's sessions are NEVER touched.
|
|
90
|
+
*
|
|
91
|
+
* 'preserve' disables auto-cleanup entirely (leave every auto-launched worker
|
|
92
|
+
* session as-is for later inspection).
|
|
93
|
+
*/
|
|
94
|
+
export type RepoMeshMagiSessionCleanupMode = 'preserve' | 'stop_and_delete';
|
|
75
95
|
/**
|
|
76
96
|
* Mesh-wide tie-break strategy for distributing untargeted queue work across
|
|
77
97
|
* eligible nodes. This ONLY governs the final tie-break stage of the scheduler
|
|
@@ -220,6 +240,17 @@ export interface RepoMeshPolicy {
|
|
|
220
240
|
* runtimes are never stopped/deleted unless the mesh owner opts in.
|
|
221
241
|
*/
|
|
222
242
|
sessionCleanupOnNodeRemove?: RepoMeshSessionCleanupMode;
|
|
243
|
+
/**
|
|
244
|
+
* What to do with the worker sessions a MAGI fan-out auto-launched, once the
|
|
245
|
+
* review responses are collected (terminal). Defaults to 'stop_and_delete' so
|
|
246
|
+
* repeated mesh_magi_review calls don't accumulate idle-LIVE worker sessions.
|
|
247
|
+
* Only sessions THIS fan-out auto-launched are affected (marker-verified);
|
|
248
|
+
* reused/coordinator/other-node sessions are never touched. Set 'preserve' to
|
|
249
|
+
* leave auto-launched worker sessions for later inspection. A per-call
|
|
250
|
+
* auto_cleanup override on mesh_magi_review / mesh_magi_collect beats this.
|
|
251
|
+
* Accepts a boolean for convenience (true → stop_and_delete, false → preserve).
|
|
252
|
+
*/
|
|
253
|
+
magiSessionCleanup?: RepoMeshMagiSessionCleanupMode | boolean;
|
|
223
254
|
/**
|
|
224
255
|
* Daemon-initiated fast-forward for idle clean nodes that are only behind
|
|
225
256
|
* their tracked upstream. Defaults to enabled.
|
|
@@ -308,6 +339,36 @@ export interface RepoMeshNodePolicy {
|
|
|
308
339
|
initSubmodulesOnClone?: boolean;
|
|
309
340
|
}
|
|
310
341
|
export declare const DEFAULT_MESH_POLICY: RepoMeshPolicy;
|
|
342
|
+
/**
|
|
343
|
+
* Resolve a magiSessionCleanup policy value (string mode, boolean shorthand,
|
|
344
|
+
* or unset) to a canonical mode. Unset → the default ('stop_and_delete', ON).
|
|
345
|
+
* boolean true → 'stop_and_delete', false → 'preserve'. Any unrecognized string
|
|
346
|
+
* falls back to the default so a typo can't silently disable auto-cleanup.
|
|
347
|
+
*/
|
|
348
|
+
export declare function resolveMagiSessionCleanupMode(value: RepoMeshMagiSessionCleanupMode | boolean | undefined | null): RepoMeshMagiSessionCleanupMode;
|
|
349
|
+
export type MagiCleanupGateReason = 'auto_launch_marker_match' | 'auto_launch_marker_skip_coordinator_session' | 'auto_launch_marker_absent_session_not_auto_launched' | 'auto_launch_marker_mismatch';
|
|
350
|
+
/**
|
|
351
|
+
* Pure decision for the MAGI auto-cleanup marker gate. A session is eligible for
|
|
352
|
+
* MAGI auto-cleanup ONLY when its session-host record was auto-launched FOR the
|
|
353
|
+
* exact replica task the caller expects. This is the safety core: MAGI passes
|
|
354
|
+
* explicit session_ids (which bypass the self-coordinator / shared-daemon guards),
|
|
355
|
+
* so this marker check is the sole thing preventing a reused-idle, coordinator, or
|
|
356
|
+
* re-assigned session from being stopped/deleted.
|
|
357
|
+
*
|
|
358
|
+
* - coordinator session (meta.meshCoordinatorFor === meshId) → skip
|
|
359
|
+
* - no expected task id supplied for this session id → skip
|
|
360
|
+
* - record carries no autoLaunchedForQueueTaskId marker (reused idle) → skip
|
|
361
|
+
* - marker present but points at a DIFFERENT task (re-assignment) → skip
|
|
362
|
+
* - marker present and equals the expected task id → ALLOW
|
|
363
|
+
*/
|
|
364
|
+
export declare function magiAutoLaunchedSessionCleanupDecision(args: {
|
|
365
|
+
recordMarker: string | undefined | null;
|
|
366
|
+
expectedTaskId: string | undefined | null;
|
|
367
|
+
isCoordinatorSession: boolean;
|
|
368
|
+
}): {
|
|
369
|
+
allow: boolean;
|
|
370
|
+
reason: MagiCleanupGateReason;
|
|
371
|
+
};
|
|
311
372
|
/** Min/max bounds for the global write-task parallel cap. */
|
|
312
373
|
export declare const MESH_MAX_PARALLEL_TASKS_MIN = 1;
|
|
313
374
|
export declare const MESH_MAX_PARALLEL_TASKS_MAX = 8;
|
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.429",
|
|
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.429",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -718,12 +718,21 @@ export class DaemonCliManager {
|
|
|
718
718
|
// by now, so updateMeta reaches the session-host store). Best-effort; guarded.
|
|
719
719
|
const launchMeshNodeId = typeof settings?.meshNodeId === 'string' ? settings.meshNodeId.trim() : '';
|
|
720
720
|
const launchMeshNodeFor = typeof settings?.meshNodeFor === 'string' ? settings.meshNodeFor.trim() : '';
|
|
721
|
-
|
|
721
|
+
// Auto-launch attribution: the queue stamps the task id it auto-launched this
|
|
722
|
+
// worker FOR (autoLaunchedForQueueTaskId). Mirror it onto the record meta so a
|
|
723
|
+
// later cleanup (e.g. MAGI post-review auto-cleanup) can verify a session was
|
|
724
|
+
// auto-launched for a SPECIFIC replica task before stopping/deleting it — a reused
|
|
725
|
+
// idle session never carries this marker, so it is preserved.
|
|
726
|
+
const launchAutoLaunchedForQueueTaskId = typeof settings?.autoLaunchedForQueueTaskId === 'string'
|
|
727
|
+
? settings.autoLaunchedForQueueTaskId.trim()
|
|
728
|
+
: '';
|
|
729
|
+
if (launchMeshNodeId || launchMeshNodeFor || launchAutoLaunchedForQueueTaskId) {
|
|
722
730
|
try {
|
|
723
731
|
cliInstance.getAdapter().updateRuntimeMeta?.({
|
|
724
732
|
...(launchMeshNodeId ? { meshNodeId: launchMeshNodeId } : {}),
|
|
725
733
|
...(launchMeshNodeFor ? { meshNodeFor: launchMeshNodeFor } : {}),
|
|
726
734
|
...(settings?.launchedByCoordinator === true ? { launchedByCoordinator: true } : {}),
|
|
735
|
+
...(launchAutoLaunchedForQueueTaskId ? { autoLaunchedForQueueTaskId: launchAutoLaunchedForQueueTaskId } : {}),
|
|
727
736
|
});
|
|
728
737
|
} catch { /* best-effort — record-meta stamp is cleanup hygiene, not on the dispatch path */ }
|
|
729
738
|
}
|
|
@@ -396,6 +396,15 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
396
396
|
const sessionIds = Array.isArray(args?.sessionIds)
|
|
397
397
|
? args.sessionIds.map((id: any) => typeof id === 'string' ? id.trim() : '').filter(Boolean)
|
|
398
398
|
: undefined;
|
|
399
|
+
// MAGI post-review auto-cleanup routes through this same command with
|
|
400
|
+
// source:'magi_session_cleanup' and a per-session autoLaunchedForQueueTaskId
|
|
401
|
+
// map, which gates cleanup to sessions THIS fan-out actually auto-launched.
|
|
402
|
+
const source = args?.source === 'magi_session_cleanup' ? 'magi_session_cleanup' : 'mesh_cleanup_sessions';
|
|
403
|
+
const requireAutoLaunchedForTaskIds = (args?.requireAutoLaunchedForTaskIds
|
|
404
|
+
&& typeof args.requireAutoLaunchedForTaskIds === 'object'
|
|
405
|
+
&& !Array.isArray(args.requireAutoLaunchedForTaskIds))
|
|
406
|
+
? args.requireAutoLaunchedForTaskIds as Record<string, string>
|
|
407
|
+
: undefined;
|
|
399
408
|
const result = await ctx.cleanupMeshSessions({
|
|
400
409
|
meshId,
|
|
401
410
|
nodeId,
|
|
@@ -403,7 +412,8 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
403
412
|
mode,
|
|
404
413
|
sessionIds,
|
|
405
414
|
dryRun: args?.dryRun === true,
|
|
406
|
-
source
|
|
415
|
+
source,
|
|
416
|
+
requireAutoLaunchedForTaskIds,
|
|
407
417
|
});
|
|
408
418
|
return result;
|
|
409
419
|
} catch (e: any) {
|
|
@@ -84,7 +84,8 @@ export interface MedFamilyContext {
|
|
|
84
84
|
mode: RepoMeshSessionCleanupMode;
|
|
85
85
|
sessionIds?: string[];
|
|
86
86
|
dryRun?: boolean;
|
|
87
|
-
source?: 'mesh_cleanup_sessions' | 'mesh_remove_node';
|
|
87
|
+
source?: 'mesh_cleanup_sessions' | 'mesh_remove_node' | 'magi_session_cleanup';
|
|
88
|
+
requireAutoLaunchedForTaskIds?: Record<string, string>;
|
|
88
89
|
}) => Promise<{ success: boolean; [key: string]: unknown }>;
|
|
89
90
|
|
|
90
91
|
/** Bound `DaemonCommandRouter.cleanupLocalWorktreeNode`. */
|
package/src/commands/router.ts
CHANGED
|
@@ -35,7 +35,7 @@ import { analyzeMeshRefineNodeChangeArea, orderMeshRefineBatchNodes } from '../m
|
|
|
35
35
|
import type { WorktreeBootstrapState } from '../mesh/worktree-bootstrap-config.js';
|
|
36
36
|
import { getMeshQueueRevision } from '../mesh/mesh-work-queue.js';
|
|
37
37
|
import type { RepoMeshSessionCleanupMode } from '../repo-mesh-types.js';
|
|
38
|
-
import { DEFAULT_MESH_POLICY } from '../repo-mesh-types.js';
|
|
38
|
+
import { DEFAULT_MESH_POLICY, magiAutoLaunchedSessionCleanupDecision } from '../repo-mesh-types.js';
|
|
39
39
|
import { resolve as pathResolve } from 'path';
|
|
40
40
|
import * as fs from 'fs';
|
|
41
41
|
import { execFileSync } from 'node:child_process';
|
|
@@ -1409,7 +1409,7 @@ export class DaemonCommandRouter {
|
|
|
1409
1409
|
node: any;
|
|
1410
1410
|
sessionId: string;
|
|
1411
1411
|
mode: RepoMeshSessionCleanupMode;
|
|
1412
|
-
source: 'mesh_cleanup_sessions' | 'mesh_remove_node';
|
|
1412
|
+
source: 'mesh_cleanup_sessions' | 'mesh_remove_node' | 'magi_session_cleanup';
|
|
1413
1413
|
action: 'stop_session' | 'delete_session_force';
|
|
1414
1414
|
}): Promise<void> {
|
|
1415
1415
|
try {
|
|
@@ -1440,7 +1440,17 @@ export class DaemonCommandRouter {
|
|
|
1440
1440
|
mode: RepoMeshSessionCleanupMode;
|
|
1441
1441
|
sessionIds?: string[];
|
|
1442
1442
|
dryRun?: boolean;
|
|
1443
|
-
source?: 'mesh_cleanup_sessions' | 'mesh_remove_node';
|
|
1443
|
+
source?: 'mesh_cleanup_sessions' | 'mesh_remove_node' | 'magi_session_cleanup';
|
|
1444
|
+
/**
|
|
1445
|
+
* MAGI auto-cleanup safety gate: a map of sessionId → the queue task id that
|
|
1446
|
+
* session must have been AUTO-LAUNCHED for (record meta autoLaunchedForQueueTaskId).
|
|
1447
|
+
* When set, a matched explicit session is only acted on if its record carries that
|
|
1448
|
+
* exact marker. A reused idle session (no marker), the coordinator session, a
|
|
1449
|
+
* re-assigned session, or any session whose marker points at a DIFFERENT task is
|
|
1450
|
+
* skipped (reason 'auto_launch_marker_mismatch') — so MAGI never kills a session it
|
|
1451
|
+
* didn't itself spawn for this fan-out. Only consulted alongside explicit sessionIds.
|
|
1452
|
+
*/
|
|
1453
|
+
requireAutoLaunchedForTaskIds?: Record<string, string>;
|
|
1444
1454
|
}): Promise<{ success: boolean; [key: string]: unknown }> {
|
|
1445
1455
|
if (args.mode === 'preserve') {
|
|
1446
1456
|
return { success: true, mode: 'preserve', matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -1459,6 +1469,7 @@ export class DaemonCommandRouter {
|
|
|
1459
1469
|
const skippedLiveSessionIds: string[] = [];
|
|
1460
1470
|
const skippedCoordinatorSessionIds: string[] = [];
|
|
1461
1471
|
const skippedLiveSessionReasons: Array<{ sessionId: string; reason: string }> = [];
|
|
1472
|
+
const skippedMarkerMismatchSessionIds: string[] = [];
|
|
1462
1473
|
const actedLiveDelegateSessionIds: string[] = [];
|
|
1463
1474
|
const deleteUnsupportedSessionIds: string[] = [];
|
|
1464
1475
|
const recordsRemainSessionIds: string[] = [];
|
|
@@ -1511,6 +1522,28 @@ export class DaemonCommandRouter {
|
|
|
1511
1522
|
skippedCoordinatorSessionIds.push(sessionId);
|
|
1512
1523
|
continue;
|
|
1513
1524
|
}
|
|
1525
|
+
// MAGI auto-cleanup marker gate. When the caller supplied a per-session
|
|
1526
|
+
// expected autoLaunchedForQueueTaskId, the session must (a) carry the marker
|
|
1527
|
+
// on its record meta AND (b) have it equal the expected replica task id.
|
|
1528
|
+
// This is the safety core for MAGI: explicit session_ids bypass the
|
|
1529
|
+
// self-coordinator / shared-daemon guards (hasExplicitSessionIds=true), so the
|
|
1530
|
+
// ONLY thing protecting a reused-idle / coordinator / re-assigned session here
|
|
1531
|
+
// is this marker check. Always-skip the coordinator session even when its id is
|
|
1532
|
+
// passed explicitly (it never carries an autoLaunchedForQueueTaskId marker, so
|
|
1533
|
+
// the mismatch branch already covers it, but be explicit for clarity).
|
|
1534
|
+
if (args.requireAutoLaunchedForTaskIds) {
|
|
1535
|
+
const decision = magiAutoLaunchedSessionCleanupDecision({
|
|
1536
|
+
recordMarker: readStringValue(record?.meta?.autoLaunchedForQueueTaskId),
|
|
1537
|
+
expectedTaskId: args.requireAutoLaunchedForTaskIds[sessionId],
|
|
1538
|
+
isCoordinatorSession: coordinatorSession,
|
|
1539
|
+
});
|
|
1540
|
+
if (!decision.allow) {
|
|
1541
|
+
skippedSessionIds.push(sessionId);
|
|
1542
|
+
skippedMarkerMismatchSessionIds.push(sessionId);
|
|
1543
|
+
skippedLiveSessionReasons.push({ sessionId, reason: decision.reason });
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1514
1547
|
// Only the conservative shared-daemon guard for live sessions that are NOT a delegate
|
|
1515
1548
|
// explicitly bound to this node. Delegate-bound live sessions fall through and are
|
|
1516
1549
|
// stopped/deleted by the mode handlers below (which already record an intentional stop).
|
|
@@ -1622,6 +1655,7 @@ export class DaemonCommandRouter {
|
|
|
1622
1655
|
skippedSessionIds,
|
|
1623
1656
|
skippedLiveSessionIds,
|
|
1624
1657
|
skippedCoordinatorSessionIds,
|
|
1658
|
+
...(skippedMarkerMismatchSessionIds.length ? { skippedMarkerMismatchSessionIds } : {}),
|
|
1625
1659
|
...(actedLiveDelegateSessionIds.length ? { actedLiveDelegateSessionIds } : {}),
|
|
1626
1660
|
...(skippedLiveSessionReasons.length ? { skippedLiveSessionReasons } : {}),
|
|
1627
1661
|
...(deleteUnsupported ? {
|
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
RepoMeshHostMetadata,
|
|
23
23
|
RepoMeshDaemonRole,
|
|
24
24
|
} from '../repo-mesh-types.js';
|
|
25
|
-
import type { MagiPanel, MagiPanelMember } from '@adhdev/mesh-shared';
|
|
25
|
+
import type { MagiPanel, MagiPanelMember, MagiPanelDefaultKind } from '@adhdev/mesh-shared';
|
|
26
26
|
import { mergeAndNormalizePolicy } from '../repo-mesh-types.js';
|
|
27
27
|
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
28
28
|
|
|
@@ -597,6 +597,30 @@ function normalizeReplicaCount(value: unknown): number | undefined {
|
|
|
597
597
|
return n >= 1 ? n : undefined;
|
|
598
598
|
}
|
|
599
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Normalize a panel `defaultKind` (the non-binding default output kind). Returns
|
|
602
|
+
* undefined (drop, don't throw) for any absent / unknown value so a stray field
|
|
603
|
+
* never blocks a panel write. 'freeform' is explicitly DROPPED with a warning: a
|
|
604
|
+
* panel is a cross-verification tool and freeform contributes no structured claims
|
|
605
|
+
* (claims:[]), so defaulting to it would silently zero out the very thing the panel
|
|
606
|
+
* exists for. Only the evidence-bearing kinds (claim_audit / rca / design) survive.
|
|
607
|
+
*/
|
|
608
|
+
function normalizeMagiPanelDefaultKind(raw: unknown): MagiPanelDefaultKind | undefined {
|
|
609
|
+
if (raw == null) return undefined;
|
|
610
|
+
const s = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
611
|
+
if (s === 'claim_audit' || s === 'rca' || s === 'design') return s;
|
|
612
|
+
if (s === 'freeform') {
|
|
613
|
+
// eslint-disable-next-line no-console
|
|
614
|
+
console.warn(
|
|
615
|
+
"[magi] panel defaultKind='freeform' rejected — freeform contributes no structured claims to cross-verification; dropping (use claim_audit / rca / design, or omit).",
|
|
616
|
+
);
|
|
617
|
+
return undefined;
|
|
618
|
+
}
|
|
619
|
+
// Any other value (typo / unsupported kind): drop silently — the panel still
|
|
620
|
+
// resolves to the claim_audit fallback at review time.
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
600
624
|
/**
|
|
601
625
|
* Validate + normalize a panel config before persisting. Mirrors the node-config
|
|
602
626
|
* normalization style (mesh-config addNode/updateNode): trims strings, drops
|
|
@@ -639,10 +663,12 @@ export function normalizeMagiPanel(config: unknown): MagiPanel {
|
|
|
639
663
|
? raw.description.trim().slice(0, 200)
|
|
640
664
|
: undefined;
|
|
641
665
|
const defaultN = normalizeReplicaCount(raw.defaultN);
|
|
666
|
+
const defaultKind = normalizeMagiPanelDefaultKind(raw.defaultKind);
|
|
642
667
|
return {
|
|
643
668
|
...(description ? { description } : {}),
|
|
644
669
|
members,
|
|
645
670
|
...(defaultN !== undefined ? { defaultN } : {}),
|
|
671
|
+
...(defaultKind !== undefined ? { defaultKind } : {}),
|
|
646
672
|
// dedupExempt is always meaningful for a MAGI panel (intentional same-prompt
|
|
647
673
|
// fan-out). Persist it true unless the caller explicitly disables it.
|
|
648
674
|
dedupExempt: raw.dedupExempt === false ? false : true,
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,7 @@ export type {
|
|
|
110
110
|
RepoMeshNode,
|
|
111
111
|
RepoMeshNodeHealth,
|
|
112
112
|
RepoMeshPolicy,
|
|
113
|
+
RepoMeshMagiSessionCleanupMode,
|
|
113
114
|
RepoMeshNodePolicy,
|
|
114
115
|
RepoMeshRelatedRepo,
|
|
115
116
|
RepoMeshNodeCapabilities,
|
|
@@ -142,6 +143,8 @@ export type {
|
|
|
142
143
|
export {
|
|
143
144
|
DEFAULT_MESH_POLICY,
|
|
144
145
|
resolveDelegatedWorkerAutoApprove,
|
|
146
|
+
resolveMagiSessionCleanupMode,
|
|
147
|
+
magiAutoLaunchedSessionCleanupDecision,
|
|
145
148
|
MESH_SCHEDULING_STRATEGIES,
|
|
146
149
|
DEFAULT_MESH_SCHEDULING_STRATEGY,
|
|
147
150
|
normalizeMeshSchedulingStrategy,
|
|
@@ -199,7 +202,7 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
199
202
|
// leaf so the mcp-server — which depends only on @adhdev/daemon-core — can consume
|
|
200
203
|
// them without taking a direct @adhdev/mesh-shared dependency).
|
|
201
204
|
export type {
|
|
202
|
-
MagiPanel, MagiPanelMember, MagiPanelMap, MagiMode,
|
|
205
|
+
MagiPanel, MagiPanelMember, MagiPanelMap, MagiMode, MagiTaskKind, MagiPanelDefaultKind,
|
|
203
206
|
MagiClaim, MagiClaimStance, MagiAgentResponse,
|
|
204
207
|
MagiResponseSource, MagiReplicaGitRef, MagiGitSkew, MagiSynthesizedResponse,
|
|
205
208
|
MagiClusterCategory, MagiClusterMember, MagiClaimCluster, MagiSynthesis,
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -92,6 +92,27 @@ export type RepoMeshNodeHealth =
|
|
|
92
92
|
export type RepoMeshSessionCleanupMode = 'preserve' | 'stop' | 'delete_stopped' | 'stop_and_delete';
|
|
93
93
|
export type RepoMeshSpawnedSessionVisibility = 'visible' | 'hidden';
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* What to do with the worker sessions a MAGI fan-out auto-launched, once the
|
|
97
|
+
* review's responses have been collected (terminal). MAGI dispatches each replica
|
|
98
|
+
* to an independent (node × provider); for a pinned target with no idle session
|
|
99
|
+
* the queue AUTO-LAUNCHES a fresh worker session. Those auto-launched sessions
|
|
100
|
+
* stay idle-LIVE after their turn (the CLI process is still running — `completed`
|
|
101
|
+
* means the task finished, not that the runtime exited), so repeated reviews leave
|
|
102
|
+
* a trail of idle live worker sessions cluttering the session list.
|
|
103
|
+
*
|
|
104
|
+
* 'stop_and_delete' (the default) force-stops AND deletes ONLY the sessions this
|
|
105
|
+
* fan-out auto-launched (verified by the per-session autoLaunchedForQueueTaskId
|
|
106
|
+
* marker — see cleanupMeshSessions). It is the only mode that covers the idle-LIVE
|
|
107
|
+
* case: delete_stopped skips live runtimes by contract, so it would no-op on the
|
|
108
|
+
* exact sessions we want gone. Reused idle sessions (no marker), the coordinator
|
|
109
|
+
* session, and any other node's sessions are NEVER touched.
|
|
110
|
+
*
|
|
111
|
+
* 'preserve' disables auto-cleanup entirely (leave every auto-launched worker
|
|
112
|
+
* session as-is for later inspection).
|
|
113
|
+
*/
|
|
114
|
+
export type RepoMeshMagiSessionCleanupMode = 'preserve' | 'stop_and_delete';
|
|
115
|
+
|
|
95
116
|
/**
|
|
96
117
|
* Mesh-wide tie-break strategy for distributing untargeted queue work across
|
|
97
118
|
* eligible nodes. This ONLY governs the final tie-break stage of the scheduler
|
|
@@ -309,6 +330,17 @@ export interface RepoMeshPolicy {
|
|
|
309
330
|
* runtimes are never stopped/deleted unless the mesh owner opts in.
|
|
310
331
|
*/
|
|
311
332
|
sessionCleanupOnNodeRemove?: RepoMeshSessionCleanupMode;
|
|
333
|
+
/**
|
|
334
|
+
* What to do with the worker sessions a MAGI fan-out auto-launched, once the
|
|
335
|
+
* review responses are collected (terminal). Defaults to 'stop_and_delete' so
|
|
336
|
+
* repeated mesh_magi_review calls don't accumulate idle-LIVE worker sessions.
|
|
337
|
+
* Only sessions THIS fan-out auto-launched are affected (marker-verified);
|
|
338
|
+
* reused/coordinator/other-node sessions are never touched. Set 'preserve' to
|
|
339
|
+
* leave auto-launched worker sessions for later inspection. A per-call
|
|
340
|
+
* auto_cleanup override on mesh_magi_review / mesh_magi_collect beats this.
|
|
341
|
+
* Accepts a boolean for convenience (true → stop_and_delete, false → preserve).
|
|
342
|
+
*/
|
|
343
|
+
magiSessionCleanup?: RepoMeshMagiSessionCleanupMode | boolean;
|
|
312
344
|
/**
|
|
313
345
|
* Daemon-initiated fast-forward for idle clean nodes that are only behind
|
|
314
346
|
* their tracked upstream. Defaults to enabled.
|
|
@@ -414,6 +446,11 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
|
|
|
414
446
|
spawnedSessionVisibility: 'hidden',
|
|
415
447
|
delegatedWorkerAutoApprove: true,
|
|
416
448
|
sessionCleanupOnNodeRemove: 'preserve',
|
|
449
|
+
// MAGI auto-launches a worker session per pinned replica target with no idle
|
|
450
|
+
// session; those stay idle-LIVE after their turn. Default ON (stop_and_delete)
|
|
451
|
+
// so repeated reviews don't pile up idle worker sessions. Only marker-verified
|
|
452
|
+
// auto-launched sessions are cleaned — see RepoMeshMagiSessionCleanupMode.
|
|
453
|
+
magiSessionCleanup: 'stop_and_delete',
|
|
417
454
|
autoFastForward: { enabled: true },
|
|
418
455
|
maxTaskRetries: 1,
|
|
419
456
|
};
|
|
@@ -430,6 +467,56 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
|
|
|
430
467
|
const SESSION_CLEANUP_MODES = new Set<RepoMeshSessionCleanupMode>([
|
|
431
468
|
'preserve', 'stop', 'delete_stopped', 'stop_and_delete',
|
|
432
469
|
]);
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Resolve a magiSessionCleanup policy value (string mode, boolean shorthand,
|
|
473
|
+
* or unset) to a canonical mode. Unset → the default ('stop_and_delete', ON).
|
|
474
|
+
* boolean true → 'stop_and_delete', false → 'preserve'. Any unrecognized string
|
|
475
|
+
* falls back to the default so a typo can't silently disable auto-cleanup.
|
|
476
|
+
*/
|
|
477
|
+
export function resolveMagiSessionCleanupMode(
|
|
478
|
+
value: RepoMeshMagiSessionCleanupMode | boolean | undefined | null,
|
|
479
|
+
): RepoMeshMagiSessionCleanupMode {
|
|
480
|
+
if (value === undefined || value === null) return DEFAULT_MESH_POLICY.magiSessionCleanup as RepoMeshMagiSessionCleanupMode;
|
|
481
|
+
if (typeof value === 'boolean') return value ? 'stop_and_delete' : 'preserve';
|
|
482
|
+
return value === 'preserve' || value === 'stop_and_delete'
|
|
483
|
+
? value
|
|
484
|
+
: (DEFAULT_MESH_POLICY.magiSessionCleanup as RepoMeshMagiSessionCleanupMode);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export type MagiCleanupGateReason =
|
|
488
|
+
| 'auto_launch_marker_match'
|
|
489
|
+
| 'auto_launch_marker_skip_coordinator_session'
|
|
490
|
+
| 'auto_launch_marker_absent_session_not_auto_launched'
|
|
491
|
+
| 'auto_launch_marker_mismatch';
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Pure decision for the MAGI auto-cleanup marker gate. A session is eligible for
|
|
495
|
+
* MAGI auto-cleanup ONLY when its session-host record was auto-launched FOR the
|
|
496
|
+
* exact replica task the caller expects. This is the safety core: MAGI passes
|
|
497
|
+
* explicit session_ids (which bypass the self-coordinator / shared-daemon guards),
|
|
498
|
+
* so this marker check is the sole thing preventing a reused-idle, coordinator, or
|
|
499
|
+
* re-assigned session from being stopped/deleted.
|
|
500
|
+
*
|
|
501
|
+
* - coordinator session (meta.meshCoordinatorFor === meshId) → skip
|
|
502
|
+
* - no expected task id supplied for this session id → skip
|
|
503
|
+
* - record carries no autoLaunchedForQueueTaskId marker (reused idle) → skip
|
|
504
|
+
* - marker present but points at a DIFFERENT task (re-assignment) → skip
|
|
505
|
+
* - marker present and equals the expected task id → ALLOW
|
|
506
|
+
*/
|
|
507
|
+
export function magiAutoLaunchedSessionCleanupDecision(args: {
|
|
508
|
+
recordMarker: string | undefined | null;
|
|
509
|
+
expectedTaskId: string | undefined | null;
|
|
510
|
+
isCoordinatorSession: boolean;
|
|
511
|
+
}): { allow: boolean; reason: MagiCleanupGateReason } {
|
|
512
|
+
const marker = typeof args.recordMarker === 'string' ? args.recordMarker.trim() : '';
|
|
513
|
+
const expected = typeof args.expectedTaskId === 'string' ? args.expectedTaskId.trim() : '';
|
|
514
|
+
if (args.isCoordinatorSession) return { allow: false, reason: 'auto_launch_marker_skip_coordinator_session' };
|
|
515
|
+
if (!expected) return { allow: false, reason: 'auto_launch_marker_mismatch' };
|
|
516
|
+
if (!marker) return { allow: false, reason: 'auto_launch_marker_absent_session_not_auto_launched' };
|
|
517
|
+
if (marker !== expected) return { allow: false, reason: 'auto_launch_marker_mismatch' };
|
|
518
|
+
return { allow: true, reason: 'auto_launch_marker_match' };
|
|
519
|
+
}
|
|
433
520
|
const SPAWNED_SESSION_VISIBILITY_MODES = new Set<RepoMeshSpawnedSessionVisibility>([
|
|
434
521
|
'visible', 'hidden',
|
|
435
522
|
]);
|
|
@@ -526,6 +613,8 @@ export function mergeAndNormalizePolicy(
|
|
|
526
613
|
if (!SESSION_CLEANUP_MODES.has(policy.sessionCleanupOnNodeRemove as RepoMeshSessionCleanupMode)) {
|
|
527
614
|
policy.sessionCleanupOnNodeRemove = 'preserve';
|
|
528
615
|
}
|
|
616
|
+
// Canonicalize magiSessionCleanup (accepts boolean shorthand / unset → default ON).
|
|
617
|
+
policy.magiSessionCleanup = resolveMagiSessionCleanupMode(policy.magiSessionCleanup);
|
|
529
618
|
if (!SPAWNED_SESSION_VISIBILITY_MODES.has(policy.spawnedSessionVisibility as RepoMeshSpawnedSessionVisibility)) {
|
|
530
619
|
policy.spawnedSessionVisibility = DEFAULT_MESH_POLICY.spawnedSessionVisibility;
|
|
531
620
|
}
|