@adhdev/daemon-core 0.9.82-rc.485 → 0.9.82-rc.486

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.
@@ -591,6 +591,15 @@ export interface MeshWorkQueueEntry {
591
591
  * setConfigOption('thought_level')). Rides in payload JSON. Best-effort like model.
592
592
  */
593
593
  thinkingLevel?: string;
594
+ /**
595
+ * SLOT-ROUTING (ORCHESTRATION_NODE_SLOTS.md): the coordinator's difficulty
596
+ * classification for this task ('easy'|'medium'|'difficult'|'freeform'),
597
+ * PERSISTED on the entry so the scheduler can match it against node capability
598
+ * slots at assignment time. Previously an enqueue-only option consumed to
599
+ * resolve model/thinkingLevel and then discarded; keeping it lets task→node
600
+ * fitness matching run. Absent on tasks enqueued without a difficulty.
601
+ */
602
+ difficulty?: string;
594
603
  /**
595
604
  * M1: why this task is held back (e.g. "dependency_failed:<taskId>").
596
605
  * Only set by the system on dependency failure under the 'block' policy;
@@ -904,6 +913,10 @@ export function enqueueTask(
904
913
  // an unconfigured preset just leaves the explicit values (or none) in place.
905
914
  let effectiveModel = typeof opts?.model === 'string' && opts.model.trim() ? opts.model.trim() : undefined;
906
915
  let effectiveThinkingLevel = typeof opts?.thinkingLevel === 'string' && opts.thinkingLevel.trim() ? opts.thinkingLevel.trim() : undefined;
916
+ // SLOT-ROUTING: persist the difficulty class on the entry so the scheduler can
917
+ // match it against node capability slots at assignment time (not just resolve
918
+ // model/thinking here). Absent/invalid → undefined (task carries no difficulty).
919
+ const taskDifficulty = isMeshTaskDifficulty(opts?.difficulty) ? (opts!.difficulty as MeshTaskDifficulty) : undefined;
907
920
  if (isMeshTaskDifficulty(opts?.difficulty)) {
908
921
  try {
909
922
  const preset = getDifficultyBrains()[opts!.difficulty as MeshTaskDifficulty];
@@ -951,6 +964,7 @@ export function enqueueTask(
951
964
  ...(typeof opts?.consensusGroupId === 'string' && opts.consensusGroupId.trim() ? { consensusGroupId: opts.consensusGroupId.trim() } : {}),
952
965
  ...(effectiveModel ? { model: effectiveModel } : {}),
953
966
  ...(effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {}),
967
+ ...(taskDifficulty ? { difficulty: taskDifficulty } : {}),
954
968
  ...(typeof opts?.sourceCoordinatorSessionId === 'string' && opts.sourceCoordinatorSessionId.trim()
955
969
  ? { sourceCoordinatorSessionId: opts.sourceCoordinatorSessionId.trim() }
956
970
  : {}),
@@ -14,7 +14,7 @@
14
14
  import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
15
15
  import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
16
16
  import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
17
- import type { MagiKindPanelMap, DifficultyBrainMap } from '@adhdev/mesh-shared';
17
+ import type { MagiKindPanelMap, DifficultyBrainMap, NodeCapabilitySlot } from '@adhdev/mesh-shared';
18
18
 
19
19
  // ─── Core Mesh Types ────────────────────────────
20
20
 
@@ -139,13 +139,18 @@ export type RepoMeshSchedulingStrategy =
139
139
  | 'first_eligible'
140
140
  | 'least_loaded'
141
141
  | 'round_robin'
142
- | 'priority_only';
142
+ | 'priority_only'
143
+ // ORCHESTRATION_NODE_SLOTS.md: rank nodes by task→capability-slot fitness
144
+ // (task difficulty/requiredTags vs the node's slots), then priority/load/order.
145
+ // Falls back to load ordering when no task is in scope (idle-session drain).
146
+ | 'fitness';
143
147
 
144
148
  export const MESH_SCHEDULING_STRATEGIES: RepoMeshSchedulingStrategy[] = [
145
149
  'first_eligible',
146
150
  'least_loaded',
147
151
  'round_robin',
148
152
  'priority_only',
153
+ 'fitness',
149
154
  ];
150
155
 
151
156
  export const DEFAULT_MESH_SCHEDULING_STRATEGY: RepoMeshSchedulingStrategy = 'first_eligible';
@@ -403,8 +408,22 @@ export interface RepoMeshNodePolicy {
403
408
  * enforced as an additional, stricter-wins constraint on top of the global
404
409
  * maxParallelTasks/taskMode caps. Missing/empty: the node behaves exactly as
405
410
  * before (global caps only). Routing is governed solely by required_tags.
411
+ *
412
+ * SUPERSEDED by `slots` (ORCHESTRATION_NODE_SLOTS.md). Kept for back-compat:
413
+ * when `slots` is absent, providerRoles + providerPriority + the machine-global
414
+ * difficultyBrains are auto-derived into slots via deriveSlotsFromLegacy.
406
415
  */
407
416
  providerRoles?: RepoMeshProviderRole[];
417
+ /**
418
+ * Node capability slots (ORCHESTRATION_NODE_SLOTS.md) — the ordered "Preferred
419
+ * AI tools" profile that is the single source of truth for task routing, MAGI
420
+ * fan-out, and orchestrator-proposed edits. Each slot bundles provider + model
421
+ * + thinkingLevel + difficulty range + capability tags + per-slot maxParallel.
422
+ * Order = preference. When absent, the scheduler derives slots from the legacy
423
+ * providerPriority/providerRoles/difficultyBrains (deriveSlotsFromLegacy) so
424
+ * existing nodes keep working without reconfiguration.
425
+ */
426
+ slots?: NodeCapabilitySlot[];
408
427
  /**
409
428
  * Per-node override for RepoMeshPolicy.delegatedWorkerAutoApprove. When set, takes
410
429
  * precedence over the mesh-level policy for worker sessions launched onto this node.
@@ -440,7 +459,11 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
440
459
  allowAutoPublishSubmoduleMainCommits: false,
441
460
  requireApprovalForDestructiveGit: true,
442
461
  dirtyWorkspaceBehavior: 'warn',
443
- maxParallelTasks: 2,
462
+ // Mesh-wide task cap is effectively unlimited by default: the real concurrency
463
+ // limits live per node / per capability slot (ORCHESTRATION_NODE_SLOTS.md), so a
464
+ // global ceiling is rarely meaningful. The UI hides this control; set it via the
465
+ // API only to impose a deliberate mesh-wide cap.
466
+ maxParallelTasks: 200,
444
467
  // Coordinator-spawned worker sessions default to hidden so the dashboard is not
445
468
  // flooded with mesh noise tabs/notifications. Users can still surface or unmute
446
469
  // any specific session manually; that override is preserved per-device.
@@ -1143,6 +1166,8 @@ export interface RepoMeshNodeStatus {
1143
1166
  activeSessions: string[];
1144
1167
  activeSessionDetails?: RepoMeshSessionStatus[];
1145
1168
  providerPriority?: string[];
1169
+ /** Explicitly-configured node capability slots (ORCHESTRATION_NODE_SLOTS.md). */
1170
+ slots?: NodeCapabilitySlot[];
1146
1171
  launchReady?: boolean;
1147
1172
  /** True when the node is clean, ahead=0, behind>0, and safe for fast-forward consideration. */
1148
1173
  autoFastForwardEligible?: boolean;
@@ -416,6 +416,13 @@ export interface SessionEntry {
416
416
  completionMarker?: string;
417
417
  seenCompletionMarker?: string;
418
418
  surfaceHidden?: boolean;
419
+ /**
420
+ * User (or coordinator-policy) muted: suppress attention side-effects
421
+ * (notifications, toasts, completion audio) for this session WITHOUT removing
422
+ * it from the inbox list. Distinct from surfaceHidden (which collapses it from
423
+ * the list). Daemon-owned, in-memory; rides the status snapshot.
424
+ */
425
+ muted?: boolean;
419
426
  settings?: Record<string, any>;
420
427
  /**
421
428
  * True owning-daemon id for a session a coordinator synthesises into its own
@@ -488,6 +495,7 @@ export interface CompactSessionEntry {
488
495
  completionMarker?: string;
489
496
  seenCompletionMarker?: string;
490
497
  surfaceHidden?: boolean;
498
+ muted?: boolean;
491
499
  controlValues?: Record<string, string | number | boolean>;
492
500
  providerControls?: ProviderControlSchema[];
493
501
  summaryMetadata?: ProviderSummaryMetadata;
@@ -34,6 +34,47 @@ import {
34
34
  } from '../providers/open-panel-support.js';
35
35
  import { TEXT_ONLY_MESSAGE_INPUT_SUPPORT } from '../providers/provider-input-support.js';
36
36
 
37
+ /**
38
+ * A coordinator-spawned worker session that mesh policy launched hidden. This is
39
+ * the daemon-side equivalent of the web `shouldAutoHideMeshConversation` predicate:
40
+ * these sessions should default to muted+hidden in the user dashboard (the user
41
+ * interacts through the ONE coordinator session, not each worker), while the
42
+ * coordinator↔worker mesh data/completion path (mesh-event-forwarding) is
43
+ * unaffected.
44
+ */
45
+ function isCoordinatorSpawnedHiddenWorker(settings: Record<string, any> | undefined): boolean {
46
+ if (!settings) return false;
47
+ return settings.launchedByCoordinator === true
48
+ && typeof settings.meshNodeFor === 'string'
49
+ && settings.meshNodeFor.trim().length > 0
50
+ && settings.spawnedSessionVisibility === 'hidden';
51
+ }
52
+
53
+ /**
54
+ * A session is surface-hidden (collapsed from the user's inbox/notifications) when
55
+ * mesh policy spawned it hidden, OR when a coordinator-spawned worker defaults
56
+ * hidden, OR when the user manually hid it (userHidden). userHidden === false is an
57
+ * explicit un-hide that overrides the policy/worker default until daemon restart.
58
+ */
59
+ function resolveSurfaceHidden(settings: Record<string, any> | undefined): boolean {
60
+ if (!settings) return false;
61
+ if (settings.userHidden === true) return true;
62
+ if (settings.userHidden === false) return false;
63
+ return settings.spawnedSessionVisibility === 'hidden' || isCoordinatorSpawnedHiddenWorker(settings);
64
+ }
65
+
66
+ /**
67
+ * A session is muted (attention side-effects suppressed, but still shown in the
68
+ * list) when the user muted it, OR a coordinator-spawned worker defaults muted.
69
+ * userMuted === false is an explicit un-mute overriding the worker default.
70
+ */
71
+ function resolveMuted(settings: Record<string, any> | undefined): boolean {
72
+ if (!settings) return false;
73
+ if (settings.userMuted === true) return true;
74
+ if (settings.userMuted === false) return false;
75
+ return isCoordinatorSpawnedHiddenWorker(settings);
76
+ }
77
+
37
78
  export type SessionEntryProfile = 'full' | 'live' | 'metadata';
38
79
 
39
80
  export interface SessionEntryBuildOptions {
@@ -342,7 +383,8 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
342
383
  settings: state.settings,
343
384
  ...(coordinator && { coordinator }),
344
385
  ...(meshQueueStats && { meshQueueStats }),
345
- ...(state.settings?.spawnedSessionVisibility === 'hidden' && { surfaceHidden: true }),
386
+ ...(resolveSurfaceHidden(state.settings) && { surfaceHidden: true }),
387
+ ...(resolveMuted(state.settings) && { muted: true }),
346
388
  };
347
389
  }
348
390
 
@@ -384,7 +426,8 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
384
426
  settings: state.settings,
385
427
  ...(coordinator && { coordinator }),
386
428
  ...(meshQueueStats && { meshQueueStats }),
387
- ...(state.settings?.spawnedSessionVisibility === 'hidden' && { surfaceHidden: true }),
429
+ ...(resolveSurfaceHidden(state.settings) && { surfaceHidden: true }),
430
+ ...(resolveMuted(state.settings) && { muted: true }),
388
431
  };
389
432
  }
390
433
 
@@ -130,7 +130,7 @@ function buildDetectedIdeInfos(
130
130
  }));
131
131
  }
132
132
 
133
- function buildAvailableProviders(
133
+ export function buildAvailableProviders(
134
134
  providerLoader: StatusSnapshotOptions['providerLoader'],
135
135
  ): AvailableProviderInfo[] {
136
136
  const providers: Array<{