@adhdev/daemon-core 0.9.82-rc.380 → 0.9.82-rc.381
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 +222 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -59
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-runtime-store.d.ts +12 -0
- package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
- package/dist/repo-mesh-types.d.ts +81 -0
- package/package.json +2 -2
- package/src/config/mesh-config.ts +6 -58
- package/src/index.ts +13 -0
- package/src/mesh/coordinator-prompt.ts +3 -3
- package/src/mesh/mesh-event-forwarding.ts +17 -1
- package/src/mesh/mesh-runtime-store.ts +23 -0
- package/src/mesh/mesh-scheduling-runtime.ts +199 -0
- package/src/repo-mesh-types.ts +164 -0
package/src/repo-mesh-types.ts
CHANGED
|
@@ -352,6 +352,114 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
|
|
|
352
352
|
maxTaskRetries: 1,
|
|
353
353
|
};
|
|
354
354
|
|
|
355
|
+
// ─── Policy normalization (single source of truth) ──────────────────────────
|
|
356
|
+
//
|
|
357
|
+
// Every mesh policy passes through mergeAndNormalizePolicy exactly once on write
|
|
358
|
+
// (createMesh/updateMesh) and again whenever a policy is materialized for display
|
|
359
|
+
// or scheduling. Co-locating the default constant, the per-field normalizers, and
|
|
360
|
+
// the merge here keeps the three former layers (DEFAULT_MESH_POLICY, the merge in
|
|
361
|
+
// mesh-config, and the scattered field clamps) from drifting apart. The function
|
|
362
|
+
// is idempotent: feeding it an already-normalized policy yields the same object.
|
|
363
|
+
|
|
364
|
+
const SESSION_CLEANUP_MODES = new Set<RepoMeshSessionCleanupMode>([
|
|
365
|
+
'preserve', 'stop', 'delete_stopped', 'stop_and_delete',
|
|
366
|
+
]);
|
|
367
|
+
const SPAWNED_SESSION_VISIBILITY_MODES = new Set<RepoMeshSpawnedSessionVisibility>([
|
|
368
|
+
'visible', 'hidden',
|
|
369
|
+
]);
|
|
370
|
+
const DIRTY_WORKSPACE_BEHAVIORS = new Set<RepoMeshPolicy['dirtyWorkspaceBehavior']>([
|
|
371
|
+
'block', 'warn', 'checkpoint_then_continue',
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
/** Min/max bounds for the global write-task parallel cap. */
|
|
375
|
+
export const MESH_MAX_PARALLEL_TASKS_MIN = 1;
|
|
376
|
+
export const MESH_MAX_PARALLEL_TASKS_MAX = 8;
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Resolve the effective global write-task parallel cap from a raw policy value,
|
|
380
|
+
* clamped to [MESH_MAX_PARALLEL_TASKS_MIN, MESH_MAX_PARALLEL_TASKS_MAX] and
|
|
381
|
+
* defaulting to DEFAULT_MESH_POLICY.maxParallelTasks for a missing/NaN value.
|
|
382
|
+
* Both the config write path and the runtime scheduler read the cap through this
|
|
383
|
+
* helper so they can never disagree on what "max parallel" means.
|
|
384
|
+
*/
|
|
385
|
+
export function resolveMaxParallelTasks(value: unknown): number {
|
|
386
|
+
const n = Number(value);
|
|
387
|
+
if (!Number.isFinite(n)) return DEFAULT_MESH_POLICY.maxParallelTasks;
|
|
388
|
+
return Math.max(MESH_MAX_PARALLEL_TASKS_MIN, Math.min(MESH_MAX_PARALLEL_TASKS_MAX, Math.floor(n)));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Normalize an autoFastForward sub-policy, filling defaults and dropping an
|
|
393
|
+
* invalid maxBehind. Mirrors the (previously mesh-config-local) shape so the merge
|
|
394
|
+
* always emits a fully-populated, valid autoFastForward object.
|
|
395
|
+
*/
|
|
396
|
+
export function normalizeAutoFastForwardPolicy(value: unknown): NonNullable<RepoMeshPolicy['autoFastForward']> {
|
|
397
|
+
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
398
|
+
? value as Record<string, unknown>
|
|
399
|
+
: {};
|
|
400
|
+
const maxBehind = Number(record.maxBehind);
|
|
401
|
+
return {
|
|
402
|
+
enabled: record.enabled !== false,
|
|
403
|
+
...(Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {}),
|
|
404
|
+
requireCleanSubmodules: record.requireCleanSubmodules !== false,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Canonical merge+normalize for a RepoMeshPolicy. Layers (lowest→highest):
|
|
410
|
+
* DEFAULT_MESH_POLICY → base (existing persisted policy) → patch (incoming change),
|
|
411
|
+
* then applies every per-field normalizer so the result is always valid regardless
|
|
412
|
+
* of what a hand-edited meshes.json or a partial patch contained.
|
|
413
|
+
*
|
|
414
|
+
* Persistence economy is preserved: schedulingStrategy is dropped when it
|
|
415
|
+
* normalizes to the 'first_eligible' default, and autoConvergeCodeChange is dropped
|
|
416
|
+
* unless explicitly true — so an untouched meshes.json stays byte-for-byte the same.
|
|
417
|
+
*/
|
|
418
|
+
export function mergeAndNormalizePolicy(
|
|
419
|
+
base: RepoMeshPolicy | undefined,
|
|
420
|
+
patch: Partial<RepoMeshPolicy> | undefined,
|
|
421
|
+
): RepoMeshPolicy {
|
|
422
|
+
const autoFastForward = normalizeAutoFastForwardPolicy({
|
|
423
|
+
...DEFAULT_MESH_POLICY.autoFastForward,
|
|
424
|
+
...((base?.autoFastForward && typeof base.autoFastForward === 'object') ? base.autoFastForward : {}),
|
|
425
|
+
...((patch?.autoFastForward && typeof patch.autoFastForward === 'object') ? patch.autoFastForward : {}),
|
|
426
|
+
});
|
|
427
|
+
const policy: RepoMeshPolicy = {
|
|
428
|
+
...DEFAULT_MESH_POLICY,
|
|
429
|
+
...(base || {}),
|
|
430
|
+
...(patch || {}),
|
|
431
|
+
autoFastForward,
|
|
432
|
+
};
|
|
433
|
+
if (!DIRTY_WORKSPACE_BEHAVIORS.has(policy.dirtyWorkspaceBehavior)) {
|
|
434
|
+
policy.dirtyWorkspaceBehavior = 'warn';
|
|
435
|
+
}
|
|
436
|
+
policy.maxParallelTasks = resolveMaxParallelTasks(policy.maxParallelTasks);
|
|
437
|
+
policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
|
|
438
|
+
if (!SESSION_CLEANUP_MODES.has(policy.sessionCleanupOnNodeRemove as RepoMeshSessionCleanupMode)) {
|
|
439
|
+
policy.sessionCleanupOnNodeRemove = 'preserve';
|
|
440
|
+
}
|
|
441
|
+
if (!SPAWNED_SESSION_VISIBILITY_MODES.has(policy.spawnedSessionVisibility as RepoMeshSpawnedSessionVisibility)) {
|
|
442
|
+
policy.spawnedSessionVisibility = DEFAULT_MESH_POLICY.spawnedSessionVisibility;
|
|
443
|
+
}
|
|
444
|
+
// Load-balancing: normalize the scheduling strategy so an invalid/blank value
|
|
445
|
+
// falls back to 'first_eligible' (strict no-change). Only persist the field when
|
|
446
|
+
// it is explicitly a non-default value to keep existing meshes.json untouched.
|
|
447
|
+
const normalizedStrategy = normalizeMeshSchedulingStrategy(policy.schedulingStrategy);
|
|
448
|
+
if (normalizedStrategy === 'first_eligible') {
|
|
449
|
+
delete policy.schedulingStrategy;
|
|
450
|
+
} else {
|
|
451
|
+
policy.schedulingStrategy = normalizedStrategy;
|
|
452
|
+
}
|
|
453
|
+
// Convergence routing: strict opt-in (default false). Only persist when explicitly
|
|
454
|
+
// enabled so existing meshes.json stays byte-for-byte untouched.
|
|
455
|
+
if (policy.autoConvergeCodeChange === true) {
|
|
456
|
+
policy.autoConvergeCodeChange = true;
|
|
457
|
+
} else {
|
|
458
|
+
delete policy.autoConvergeCodeChange;
|
|
459
|
+
}
|
|
460
|
+
return policy;
|
|
461
|
+
}
|
|
462
|
+
|
|
355
463
|
/**
|
|
356
464
|
* Resolve whether a delegated worker session launched onto `nodePolicy` (within a mesh
|
|
357
465
|
* governed by `meshPolicy`) should auto-approve. Precedence: node override → mesh policy
|
|
@@ -585,6 +693,44 @@ export interface LocalMeshNodeEntry {
|
|
|
585
693
|
|
|
586
694
|
// ─── Mesh Status (runtime, not persisted) ───────
|
|
587
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Per-(node, provider) cap + consumption, as surfaced on a node's scheduling
|
|
698
|
+
* status. Wire-shape mirror of MeshNodeProviderSchedulingRuntime.
|
|
699
|
+
*/
|
|
700
|
+
export interface RepoMeshNodeProviderSchedulingStatus {
|
|
701
|
+
providerType: string;
|
|
702
|
+
maxParallel?: number;
|
|
703
|
+
activeAssigned: number;
|
|
704
|
+
capReached: boolean;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Per-node scheduling runtime exposed on RepoMeshNodeStatus.scheduling. Carried in
|
|
709
|
+
* full by verbose mesh_status; compact mesh_status sends only {load, capReached}.
|
|
710
|
+
*/
|
|
711
|
+
export interface RepoMeshNodeSchedulingStatus {
|
|
712
|
+
load: number;
|
|
713
|
+
schedulingPriority?: number;
|
|
714
|
+
maxConcurrentSessions?: number;
|
|
715
|
+
providerRoles?: RepoMeshNodeProviderSchedulingStatus[];
|
|
716
|
+
capReached: boolean;
|
|
717
|
+
capReasons?: string[];
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Mesh-level scheduling rollup exposed on RepoMeshStatus.scheduling: which tie-break
|
|
722
|
+
* strategy is live and how much of the global parallel caps is consumed.
|
|
723
|
+
*/
|
|
724
|
+
export interface RepoMeshSchedulingStatus {
|
|
725
|
+
strategy: RepoMeshSchedulingStrategy;
|
|
726
|
+
maxParallelTasks: number;
|
|
727
|
+
maxReadonlyParallelTasks: number;
|
|
728
|
+
activeWriteAssigned: number;
|
|
729
|
+
activeReadonlyAssigned: number;
|
|
730
|
+
globalWriteCapReached: boolean;
|
|
731
|
+
globalReadonlyCapReached: boolean;
|
|
732
|
+
}
|
|
733
|
+
|
|
588
734
|
export interface RepoMeshStatus {
|
|
589
735
|
meshId: string;
|
|
590
736
|
meshName: string;
|
|
@@ -595,6 +741,11 @@ export interface RepoMeshStatus {
|
|
|
595
741
|
nodes: RepoMeshNodeStatus[];
|
|
596
742
|
queue?: RepoMeshQueueStatus;
|
|
597
743
|
ledger?: RepoMeshLedgerStatus;
|
|
744
|
+
/**
|
|
745
|
+
* Mesh-level scheduling rollup (strategy + global cap consumption). Omitted by
|
|
746
|
+
* daemons predating the scheduling-runtime exposure — treat as optional.
|
|
747
|
+
*/
|
|
748
|
+
scheduling?: RepoMeshSchedulingStatus;
|
|
598
749
|
/**
|
|
599
750
|
* Mission summaries for the dashboard overview. Active/paused missions plus a
|
|
600
751
|
* capped, newest-first slice of completed/abandoned history. Omitted by older
|
|
@@ -674,6 +825,19 @@ export interface RepoMeshNodeStatus {
|
|
|
674
825
|
lastSeenAt?: string;
|
|
675
826
|
updatedAt?: string;
|
|
676
827
|
connection?: RepoMeshPeerConnectionStatus;
|
|
828
|
+
/**
|
|
829
|
+
* Per-node scheduling runtime (load / priority / provider caps / claim-block
|
|
830
|
+
* reasons). Verbose mesh_status carries the full shape; compact carries only
|
|
831
|
+
* {load, capReached}. Omitted by daemons predating the exposure.
|
|
832
|
+
*/
|
|
833
|
+
scheduling?: RepoMeshNodeSchedulingStatus;
|
|
834
|
+
/**
|
|
835
|
+
* Stale-daemon-build marker: the live daemon's build commit is a strict ancestor
|
|
836
|
+
* of this node's workspace HEAD (merged code not yet live). Best-effort, set by
|
|
837
|
+
* mesh_status when the git probe reports daemonBuildBehind; shape is daemon-defined
|
|
838
|
+
* (scope/isDaemonAffecting flags). Omitted when the build is current.
|
|
839
|
+
*/
|
|
840
|
+
staleDaemonBuild?: Record<string, unknown>;
|
|
677
841
|
error?: string;
|
|
678
842
|
}
|
|
679
843
|
|