@adhdev/daemon-core 0.9.82-rc.320 → 0.9.82-rc.321

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.
@@ -165,106 +165,6 @@ export function resolveNodeSchedulingPriority(
165
165
  export const MESH_CONVERGE_REFINE_TAG = 'converge=refine';
166
166
  export const MESH_CONVERGE_FAST_FORWARD_TAG = 'converge=fast_forward';
167
167
 
168
- /**
169
- * Standard mesh resource-pool roles. These are the dashboard-provided defaults for
170
- * the role=<x> routing tag (advertised by node policy.providerRoles and matched
171
- * through nodeSatisfiesRequiredTags). A mesh is free to declare additional custom
172
- * role strings in policy.taskAffinity; the dashboard role dropdown exposes the union
173
- * of these four standards plus any config-declared roles. Roles are lowercased
174
- * everywhere (the role=<x> tag is lowercased in roleCapabilityTags).
175
- */
176
- export const STANDARD_MESH_ROLES = ['investigator', 'coder', 'validator', 'converger'] as const;
177
- export type StandardMeshRole = typeof STANDARD_MESH_ROLES[number];
178
-
179
- /**
180
- * Default taskMode → role mapping (all five task modes). A task enqueued with a
181
- * given taskMode auto-routes to a node advertising the mapped role=<x> tag, unless
182
- * the mesh overrides the mapping in policy.taskAffinity.byTaskMode. Keyed by the
183
- * MeshTaskMode string literals (the canonical union lives in mesh-work-queue.ts;
184
- * this map is keyed by string to avoid a types ↔ queue import cycle). Kept in sync
185
- * with MeshTaskMode by the exhaustive lookup in resolveTaskAffinityRole.
186
- */
187
- export const DEFAULT_TASKMODE_ROLE_MAP: Readonly<Record<string, StandardMeshRole>> = {
188
- live_debug_readonly: 'investigator',
189
- code_change: 'coder',
190
- launch_app: 'coder',
191
- validation: 'validator',
192
- convergence: 'converger',
193
- };
194
-
195
- /**
196
- * Declarative task_mode → role affinity policy. When set, enqueueTask auto-injects a
197
- * `role=<role>` required tag (see resolveTaskAffinityRequiredTags) so the task
198
- * hard-filters onto nodes advertising that role — but only when the caller did NOT
199
- * already pin the task with an explicit target_node_id or its own required_tags.
200
- *
201
- * - `enabled`: master switch. Defaults to true so the built-in DEFAULT_TASKMODE_ROLE_MAP
202
- * takes effect even when a mesh declares no taskAffinity block at all. Set false to
203
- * fully disable affinity routing (today's pre-affinity behavior).
204
- * - `byTaskMode`: per-taskMode override of the default mapping. A role string here may
205
- * be any of STANDARD_MESH_ROLES or a custom role declared by the operator. An empty
206
- * string disables affinity for that one task mode.
207
- * - `customRoles`: extra (non-standard) role strings to surface in the dashboard role
208
- * dropdown. Routing itself does not require a role to be pre-declared here — any role
209
- * referenced in byTaskMode is injected regardless; customRoles only widens the UI list.
210
- */
211
- export interface RepoMeshTaskAffinityPolicy {
212
- enabled?: boolean;
213
- byTaskMode?: Record<string, string>;
214
- customRoles?: string[];
215
- }
216
-
217
- /**
218
- * Resolve the affinity role for a task mode against a mesh's taskAffinity policy.
219
- * Returns the lowercased role string to inject as `role=<role>`, or null when no
220
- * affinity applies (affinity disabled, an explicit empty override, or an unknown mode).
221
- *
222
- * Precedence: policy.byTaskMode[mode] (operator override) → DEFAULT_TASKMODE_ROLE_MAP[mode].
223
- * A blank override string explicitly opts that mode out of affinity routing.
224
- */
225
- export function resolveTaskAffinityRole(
226
- taskMode: string | undefined,
227
- policy: RepoMeshTaskAffinityPolicy | null | undefined,
228
- ): string | null {
229
- if (!taskMode) return null;
230
- if (policy?.enabled === false) return null;
231
- const override = policy?.byTaskMode && Object.prototype.hasOwnProperty.call(policy.byTaskMode, taskMode)
232
- ? policy.byTaskMode[taskMode]
233
- : undefined;
234
- if (typeof override === 'string') {
235
- const trimmed = override.trim().toLowerCase();
236
- // An explicit blank override opts this task mode out of affinity routing.
237
- return trimmed ? trimmed : null;
238
- }
239
- const fallback = DEFAULT_TASKMODE_ROLE_MAP[taskMode];
240
- return fallback ?? null;
241
- }
242
-
243
- /**
244
- * Union of the standard roles plus any operator-declared roles (byTaskMode values +
245
- * customRoles), lowercased and deduped, preserving standard-first ordering. Used by
246
- * the dashboard role dropdown so operators can pick a standard or a config-declared
247
- * custom role. Pure/UI-facing — does not gate routing.
248
- */
249
- export function resolveMeshRoleOptions(policy: RepoMeshTaskAffinityPolicy | null | undefined): string[] {
250
- const out: string[] = [...STANDARD_MESH_ROLES];
251
- const seen = new Set<string>(out);
252
- const add = (raw: unknown) => {
253
- if (typeof raw !== 'string') return;
254
- const role = raw.trim().toLowerCase();
255
- if (!role || seen.has(role)) return;
256
- seen.add(role);
257
- out.push(role);
258
- };
259
- if (policy?.byTaskMode) {
260
- for (const value of Object.values(policy.byTaskMode)) add(value);
261
- }
262
- if (Array.isArray(policy?.customRoles)) {
263
- for (const value of policy.customRoles) add(value);
264
- }
265
- return out;
266
- }
267
-
268
168
  /**
269
169
  * Resolve whether the load-balancing scheduler should auto-inject a
270
170
  * `converge=refine` required tag onto code_change tasks so they hard-filter onto
@@ -320,18 +220,6 @@ export interface RepoMeshPolicy {
320
220
  * Defaults to false: code_change routing is unchanged unless opted in.
321
221
  */
322
222
  autoConvergeCodeChange?: boolean;
323
- /**
324
- * Declarative task_mode → role affinity routing. When present (and not disabled),
325
- * enqueueTask auto-injects a `role=<role>` required tag for the task's taskMode so
326
- * the work hard-filters onto nodes advertising that role. Explicit target_node_id
327
- * routing and any caller-supplied required_tags are preserved (auto-injection only
328
- * applies when the caller pinned neither). When the mapped role has no advertising
329
- * node in the mesh, the task is NOT blocked — the role tag is skipped so the work
330
- * falls back to ordinary least_loaded eligibility (soft affinity). Defaults to the
331
- * built-in DEFAULT_TASKMODE_ROLE_MAP even when this block is omitted; set
332
- * `{ enabled: false }` to fully restore pre-affinity routing.
333
- */
334
- taskAffinity?: RepoMeshTaskAffinityPolicy;
335
223
  /**
336
224
  * Whether sessions spawned by mesh/coordinator policy should auto-open as visible
337
225
  * dashboard tabs or start hidden. Defaults to 'visible' to preserve existing
@@ -376,21 +264,7 @@ export interface RepoMeshRelatedRepo {
376
264
  }
377
265
 
378
266
  /**
379
- * Per-(node, provider) role + parallelism declaration.
380
- *
381
- * `role` is a free-form resource-pool label (recommended values:
382
- * 'investigation' | 'coding' | 'orchestration') describing what this
383
- * (node, provider) combination is *for*. As of the load-balancing scheduler it
384
- * is ALSO routable: each declared role is advertised as a synthetic `role=<x>`
385
- * capability tag (see buildMeshNodeCapabilityTags), so a task enqueued with
386
- * requiredTags: ["role=validation"] is hard-filtered to nodes/providers that
387
- * declare that role — through the same nodeSatisfiesRequiredTags path as any
388
- * other tag. There is intentionally no separate "advertisedRoles" field: the
389
- * label and the routing tag are one mechanism. A task that does not require a
390
- * `role=` tag ignores roles entirely (opt-in, fully backward compatible).
391
- *
392
- * role is intentionally orthogonal to taskMode: taskMode classifies the *work*
393
- * (code_change vs live_debug_readonly), role classifies the *resource pool*.
267
+ * Per-(node, provider) parallelism declaration.
394
268
  *
395
269
  * `maxParallel` is the only enforced field: the queue will not assign a task
396
270
  * to this (node, provider) once it already has `maxParallel` active
@@ -398,12 +272,14 @@ export interface RepoMeshRelatedRepo {
398
272
  * provider) cap disagree, the stricter (lower effective) limit wins — a claim
399
273
  * must satisfy both. Omitting `maxParallel` means this provider is bounded only
400
274
  * by the global/taskMode caps (full backward compatibility).
275
+ *
276
+ * Routing is governed exclusively by required_tags (see nodeSatisfiesRequiredTags);
277
+ * this entry carries no routing role. To route work to a specific node, advertise an
278
+ * ordinary capability tag on the node and require it on the task.
401
279
  */
402
280
  export interface RepoMeshProviderRole {
403
281
  /** Provider type this entry governs (e.g. 'claude-cli', 'codex-cli'). */
404
282
  providerType: string;
405
- /** Free-form role label; recommended: 'investigation' | 'coding' | 'orchestration'. */
406
- role?: string;
407
283
  /** Max concurrent active tasks for this (node, provider). Omit = no per-provider cap. */
408
284
  maxParallel?: number;
409
285
  }
@@ -423,13 +299,11 @@ export interface RepoMeshNodePolicy {
423
299
  /** Ordered provider preference used when mesh_launch_session omits an explicit type. */
424
300
  providerPriority?: string[];
425
301
  /**
426
- * Per-(node, provider) role + parallelism declarations. Each entry binds a
427
- * providerType on THIS node to an optional free-form role label and an
428
- * optional maxParallel cap. maxParallel is enforced (as an additional,
429
- * stricter-wins constraint on top of the global maxParallelTasks/taskMode
430
- * caps); role is advertised as a routable `role=<x>` capability tag so tasks
431
- * can hard-filter by required role. Missing/empty: the node behaves exactly
432
- * as before (global caps only, no role tags).
302
+ * Per-(node, provider) parallelism declarations. Each entry binds a
303
+ * providerType on THIS node to an optional maxParallel cap. maxParallel is
304
+ * enforced as an additional, stricter-wins constraint on top of the global
305
+ * maxParallelTasks/taskMode caps. Missing/empty: the node behaves exactly as
306
+ * before (global caps only). Routing is governed solely by required_tags.
433
307
  */
434
308
  providerRoles?: RepoMeshProviderRole[];
435
309
  /**
@@ -496,16 +370,16 @@ export function resolveDelegatedWorkerAutoApprove(
496
370
  }
497
371
 
498
372
  /**
499
- * Resolve the per-(node, provider) role declaration from a node policy.
500
- * Case-insensitive, trimmed match on providerType. Returns undefined when the
501
- * node has no providerRoles entry for this provider (caller then applies only
502
- * the global caps). Defensive against malformed config — non-object entries and
503
- * blank providerTypes are skipped rather than throwing.
373
+ * Resolve the enforced per-(node, provider) maxParallel cap, or undefined when
374
+ * no finite, non-negative cap is declared for this provider. Used by the queue
375
+ * claim path as a stricter-wins constraint layered on top of the global caps.
376
+ * Case-insensitive, trimmed match on providerType. Defensive against malformed
377
+ * config — non-object entries and blank providerTypes are skipped rather than throwing.
504
378
  */
505
- export function resolveProviderRole(
379
+ export function resolveProviderMaxParallel(
506
380
  nodePolicy: Pick<RepoMeshNodePolicy, 'providerRoles'> | null | undefined,
507
381
  providerType: string | null | undefined,
508
- ): RepoMeshProviderRole | undefined {
382
+ ): number | undefined {
509
383
  const wanted = typeof providerType === 'string' ? providerType.trim().toLowerCase() : '';
510
384
  if (!wanted) return undefined;
511
385
  const roles = nodePolicy?.providerRoles;
@@ -513,26 +387,14 @@ export function resolveProviderRole(
513
387
  for (const entry of roles) {
514
388
  if (!entry || typeof entry !== 'object') continue;
515
389
  const type = typeof entry.providerType === 'string' ? entry.providerType.trim().toLowerCase() : '';
516
- if (type && type === wanted) return entry;
390
+ if (!type || type !== wanted) continue;
391
+ const raw = Number(entry.maxParallel);
392
+ if (!Number.isFinite(raw) || raw < 0) return undefined;
393
+ return Math.floor(raw);
517
394
  }
518
395
  return undefined;
519
396
  }
520
397
 
521
- /**
522
- * Resolve the enforced per-(node, provider) maxParallel cap, or undefined when
523
- * no finite, non-negative cap is declared for this provider. Used by the queue
524
- * claim path as a stricter-wins constraint layered on top of the global caps.
525
- */
526
- export function resolveProviderMaxParallel(
527
- nodePolicy: Pick<RepoMeshNodePolicy, 'providerRoles'> | null | undefined,
528
- providerType: string | null | undefined,
529
- ): number | undefined {
530
- const role = resolveProviderRole(nodePolicy, providerType);
531
- const raw = Number(role?.maxParallel);
532
- if (!Number.isFinite(raw) || raw < 0) return undefined;
533
- return Math.floor(raw);
534
- }
535
-
536
398
  // ─── Capabilities ───────────────────────────────
537
399
 
538
400
  export interface RepoMeshNodeCapabilities {