@adhdev/daemon-core 0.9.82-rc.483 → 0.9.82-rc.484
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/cli-manager.d.ts +20 -0
- package/dist/config/mesh-config.d.ts +14 -1
- package/dist/index.js +305 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +305 -17
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-pending.d.ts +12 -0
- package/dist/mesh/mesh-runtime-store.d.ts +14 -0
- package/dist/mesh/mesh-work-queue.d.ts +17 -0
- package/dist/providers/cli-provider-instance.d.ts +14 -0
- package/dist/providers/contracts.d.ts +47 -0
- package/dist/repo-mesh-types.d.ts +10 -1
- package/dist/shared-types.d.ts +4 -0
- package/package.json +3 -3
- package/src/commands/cli-manager.ts +64 -3
- package/src/commands/med-family/mesh-crud.ts +24 -0
- package/src/config/mesh-config.ts +30 -1
- package/src/mesh/coordinator-prompt.ts +36 -0
- package/src/mesh/mesh-events-pending.ts +35 -1
- package/src/mesh/mesh-queue-assignment.ts +46 -4
- package/src/mesh/mesh-reconcile-loop.ts +69 -1
- package/src/mesh/mesh-runtime-store.ts +22 -0
- package/src/mesh/mesh-work-queue.ts +36 -3
- package/src/providers/cli-provider-instance.ts +46 -0
- package/src/providers/contracts.ts +47 -0
- package/src/providers/provider-schema.ts +6 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +29 -0
- package/src/repo-mesh-types.ts +10 -1
- package/src/shared-types.ts +4 -0
- package/src/status/snapshot.ts +4 -0
|
@@ -34,6 +34,18 @@ export interface PendingMeshCoordinatorEvent {
|
|
|
34
34
|
dispatchedBy?: CoordinatorIdentity;
|
|
35
35
|
/** Present only for unicast scope: the coordinator this event is addressed to. */
|
|
36
36
|
intendedFor?: CoordinatorIdentity;
|
|
37
|
+
/**
|
|
38
|
+
* True when this event was stamped as a broadcast SOLELY because no owning
|
|
39
|
+
* coordinator identity was resolvable at emit time (self-fallback: dispatchedBy
|
|
40
|
+
* is THIS daemon's own machineId, not a real coordinator). Such a broadcast has
|
|
41
|
+
* no owner, so the MAGI-REPLICA-COMPLETION-EVENT-LEAK guard — which only exists
|
|
42
|
+
* to stop a NON-owner coordinator from consuming an OWNED terminal event — must
|
|
43
|
+
* not apply: an ownerless terminal broadcast is a genuine "deliver to any
|
|
44
|
+
* coordinator that drains on this machine" event and identity-matching its
|
|
45
|
+
* self-id dispatchedBy against the drainer would wrongly route it away.
|
|
46
|
+
* Absent (undefined/false) on a normally-owned event → the leak guard applies.
|
|
47
|
+
*/
|
|
48
|
+
dispatchedBySelfFallback?: boolean;
|
|
37
49
|
}
|
|
38
50
|
/**
|
|
39
51
|
* Optional emit-time hint passed to queuePendingMeshCoordinatorEvent so a call
|
|
@@ -239,6 +239,20 @@ export declare class MeshRuntimeStore {
|
|
|
239
239
|
* watchdog's). Indexed by (mesh_id, task_id).
|
|
240
240
|
*/
|
|
241
241
|
taskHasConfirmedDelivery(meshId: string, taskId: string): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* DELIVERED-NOT-CONSUMED re-drive support: true when at least one delivery record for
|
|
244
|
+
* the task has reached a CONSUMED status ('acked' / 'completed'). Distinct from
|
|
245
|
+
* {@link taskHasConfirmedDelivery} ('delivered' | 'acked' | 'completed'): a delivery is
|
|
246
|
+
* flipped to 'delivered' the instant the transport hands the dispatch off, but only
|
|
247
|
+
* flipped to 'acked' when the worker's agent:generating_started event arrives (see the
|
|
248
|
+
* generating_started handler in mesh-event-forwarding) — i.e. when the session has
|
|
249
|
+
* actually begun the turn. That distinction is the cross-daemon consumption signal the
|
|
250
|
+
* short-grace re-drive uses: a row whose delivery is 'delivered' but never 'acked' was
|
|
251
|
+
* handed to a REMOTE worker that never started generating — the remote autoLaunch
|
|
252
|
+
* delivered≠consumed gap — even when the session's busy verdict is UNKNOWN (not locally
|
|
253
|
+
* observable). Indexed by (mesh_id, task_id).
|
|
254
|
+
*/
|
|
255
|
+
taskDeliveryConsumed(meshId: string, taskId: string): boolean;
|
|
242
256
|
expireStaleSessionDeliveries(meshId: string): void;
|
|
243
257
|
deleteSessionDeliveries(meshId: string): void;
|
|
244
258
|
/**
|
|
@@ -119,6 +119,13 @@ export interface MeshWorkQueueEntry {
|
|
|
119
119
|
* that cannot honor the model still runs the task (never a fatal launch error).
|
|
120
120
|
*/
|
|
121
121
|
model?: string;
|
|
122
|
+
/**
|
|
123
|
+
* BRAIN-ROUTING (thinking axis): standard reasoning level ('low'|'medium'|'high')
|
|
124
|
+
* for the session that executes this task. When the task auto-launches, this is
|
|
125
|
+
* passed to launch_cli as `initialThinkingLevel` (CLI → thinkingLaunchArgs; ACP →
|
|
126
|
+
* setConfigOption('thought_level')). Rides in payload JSON. Best-effort like model.
|
|
127
|
+
*/
|
|
128
|
+
thinkingLevel?: string;
|
|
122
129
|
/**
|
|
123
130
|
* M1: why this task is held back (e.g. "dependency_failed:<taskId>").
|
|
124
131
|
* Only set by the system on dependency failure under the 'block' policy;
|
|
@@ -241,6 +248,16 @@ export declare function enqueueTask(meshId: string, message: string, opts?: {
|
|
|
241
248
|
consensusGroupId?: string;
|
|
242
249
|
/** MAGI-KIND-PANEL: model override forwarded to the executing session's launch (initialModel). */
|
|
243
250
|
model?: string;
|
|
251
|
+
/** BRAIN-ROUTING: standard thinking level forwarded to launch (initialThinkingLevel). */
|
|
252
|
+
thinkingLevel?: string;
|
|
253
|
+
/**
|
|
254
|
+
* BRAIN-ROUTING: task execution difficulty ('easy'|'medium'|'difficult'|
|
|
255
|
+
* 'freeform'). When set, the mesh's difficulty→brain preset fills in model /
|
|
256
|
+
* thinkingLevel that were not passed explicitly (an explicit model/thinkingLevel
|
|
257
|
+
* wins). Purely a convenience resolver — the stored task still carries the
|
|
258
|
+
* resolved model/thinkingLevel, so downstream launch is unchanged.
|
|
259
|
+
*/
|
|
260
|
+
difficulty?: string;
|
|
244
261
|
/** Explicit task id for batch/template flows (M5). Random UUID when omitted. */
|
|
245
262
|
id?: string;
|
|
246
263
|
/** (3) Originating coordinator session id (for session-anchored completion routing). */
|
|
@@ -155,12 +155,17 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
155
155
|
private presentationMode;
|
|
156
156
|
private providerSessionId?;
|
|
157
157
|
private launchMode;
|
|
158
|
+
private initialThinkingLevel?;
|
|
158
159
|
private readonly startedAt;
|
|
159
160
|
private onProviderSessionResolved?;
|
|
160
161
|
constructor(provider: ProviderModule, workingDir: string, cliArgs?: string[], instanceId?: string, transportFactory?: PtyTransportFactory, options?: {
|
|
161
162
|
providerSessionId?: string;
|
|
162
163
|
launchMode?: 'new' | 'resume' | 'manual';
|
|
163
164
|
extraEnv?: Record<string, string>;
|
|
165
|
+
/** BRAIN-ROUTING: standard thinking level to apply post-launch via the
|
|
166
|
+
* provider's thinkingControlId (runtime-control providers like hermes).
|
|
167
|
+
* Providers using thinkingLaunchArgs get it at spawn instead and ignore this. */
|
|
168
|
+
initialThinkingLevel?: string;
|
|
164
169
|
onProviderSessionResolved?: (info: {
|
|
165
170
|
instanceId: string;
|
|
166
171
|
providerType: string;
|
|
@@ -321,6 +326,15 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
321
326
|
*/
|
|
322
327
|
private lastCompletionSummary;
|
|
323
328
|
private enforceFreshSessionLaunchIfNeeded;
|
|
329
|
+
/**
|
|
330
|
+
* BRAIN-ROUTING (runtime-control thinking axis): for a provider that selects
|
|
331
|
+
* reasoning effort via a runtime control instead of a launch arg (e.g. hermes
|
|
332
|
+
* `reasoning`), apply the requested initialThinkingLevel after spawn by invoking
|
|
333
|
+
* that control's setScript. The provider names the control via thinkingControlId.
|
|
334
|
+
* The standard level is mapped through thinkingLevelMap first (same as the
|
|
335
|
+
* launch-arg path). Best-effort: any failure logs and never blocks launch.
|
|
336
|
+
*/
|
|
337
|
+
private applyInitialThinkingLevelViaControl;
|
|
324
338
|
private completionHasFinalAssistantMessage;
|
|
325
339
|
private recordPendingTranscriptProbe;
|
|
326
340
|
/**
|
|
@@ -499,6 +499,53 @@ export interface ProviderModule {
|
|
|
499
499
|
* request never fails a launch). Absent → no launch-time model selection for CLI.
|
|
500
500
|
*/
|
|
501
501
|
modelLaunchArgs?: string[];
|
|
502
|
+
/**
|
|
503
|
+
* BRAIN-ROUTING (model axis): suggested model values for this provider, surfaced
|
|
504
|
+
* as dropdown options in the new-session dialog (e.g. claude ['opus','sonnet',
|
|
505
|
+
* 'haiku']; codex ['gpt-5.5','gpt-5-codex']). Advisory only — the UI allows free
|
|
506
|
+
* text too, so the list going stale never blocks a model the provider accepts.
|
|
507
|
+
*/
|
|
508
|
+
modelOptions?: string[];
|
|
509
|
+
/**
|
|
510
|
+
* BRAIN-ROUTING (thinking axis): template for expanding an `initialThinkingLevel`
|
|
511
|
+
* selection into launch args for a CLI provider, parallel to modelLaunchArgs.
|
|
512
|
+
* `{{level}}` is substituted with the provider-appropriate reasoning-effort value
|
|
513
|
+
* (already mapped from the standard low|medium|high level, see thinkingLevelMap).
|
|
514
|
+
* Examples: claude-cli `['--effort', '{{level}}']` → `--effort high`; codex-cli
|
|
515
|
+
* `['-c', 'model_reasoning_effort={{level}}']`. Applied at session launch when
|
|
516
|
+
* `initialThinkingLevel` is passed AND this provider is a plain CLI. A CLI provider
|
|
517
|
+
* with no template silently ignores the thinking level (best-effort; never fails a
|
|
518
|
+
* launch). ACP providers instead route thinking through setConfigOption('thought_level').
|
|
519
|
+
*/
|
|
520
|
+
thinkingLaunchArgs?: string[];
|
|
521
|
+
/**
|
|
522
|
+
* BRAIN-ROUTING (thinking axis): optional per-provider mapping from the standard
|
|
523
|
+
* thinking levels (`low`|`medium`|`high`) to this provider's own reasoning-effort
|
|
524
|
+
* vocabulary, used to fill `{{level}}` in thinkingLaunchArgs. e.g. claude-cli might
|
|
525
|
+
* map `{ high: 'max' }`; codex-cli `{ high: 'xhigh' }`. A level absent from the map
|
|
526
|
+
* passes through unchanged (so `medium` → `medium` by default).
|
|
527
|
+
*/
|
|
528
|
+
thinkingLevelMap?: Partial<Record<'low' | 'medium' | 'high', string>>;
|
|
529
|
+
/**
|
|
530
|
+
* BRAIN-ROUTING (thinking axis): the reasoning-effort values this provider actually
|
|
531
|
+
* accepts, surfaced as the thinking-level dropdown options in the new-session
|
|
532
|
+
* dialog (e.g. claude ['low','medium','high','max']; codex ['minimal','low',
|
|
533
|
+
* 'medium','high','xhigh']). Absent → the UI falls back to the standard
|
|
534
|
+
* low/medium/high. These are the provider's OWN vocabulary and are passed through
|
|
535
|
+
* verbatim as initialThinkingLevel (not remapped by thinkingLevelMap, which only
|
|
536
|
+
* translates the mesh's standard low/medium/high presets).
|
|
537
|
+
*/
|
|
538
|
+
thinkingLevelOptions?: string[];
|
|
539
|
+
/**
|
|
540
|
+
* BRAIN-ROUTING (thinking axis, runtime-control providers): the `controls[].id`
|
|
541
|
+
* of a runtime reasoning-effort control to drive for the thinking level when the
|
|
542
|
+
* provider has no `thinkingLaunchArgs` (e.g. hermes-cli's `reasoning` select,
|
|
543
|
+
* which types `/reasoning <level>` into the PTY via its setScript). At launch,
|
|
544
|
+
* initialThinkingLevel (after thinkingLevelMap) is applied by invoking that
|
|
545
|
+
* control's setScript with `{ value: <level> }`. Ignored if the id doesn't match a
|
|
546
|
+
* control. Providers that use thinkingLaunchArgs don't need this.
|
|
547
|
+
*/
|
|
548
|
+
thinkingControlId?: string;
|
|
502
549
|
/** Delay before submitting typed CLI input (provider-specific TUI tuning) */
|
|
503
550
|
sendDelayMs?: number;
|
|
504
551
|
/** Submit key used after typing into CLI PTY (default: carriage return) */
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
|
|
14
14
|
import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
|
|
15
15
|
import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
|
|
16
|
-
import type { MagiKindPanelMap } from '@adhdev/mesh-shared';
|
|
16
|
+
import type { MagiKindPanelMap, DifficultyBrainMap } from '@adhdev/mesh-shared';
|
|
17
17
|
export interface RepoMesh {
|
|
18
18
|
id: string;
|
|
19
19
|
name: string;
|
|
@@ -555,6 +555,15 @@ export interface LocalMeshConfig {
|
|
|
555
555
|
* Optional; absent on pre-feature configs.
|
|
556
556
|
*/
|
|
557
557
|
magiKindPanels?: MagiKindPanelMap;
|
|
558
|
+
/**
|
|
559
|
+
* BRAIN-ROUTING: per-task-difficulty brain presets (machine-local), sibling of
|
|
560
|
+
* magiKindPanels. Keyed by difficulty (easy / medium / difficult / freeform);
|
|
561
|
+
* each maps to a BrainSlot (provider? / model? / thinkingLevel?). The coordinator
|
|
562
|
+
* classifies a task's difficulty at enqueue; the matching preset fills in the
|
|
563
|
+
* task's model / thinking level (an explicit task value wins). Optional; a mesh
|
|
564
|
+
* with none seeded uses DEFAULT_DIFFICULTY_BRAINS on first read.
|
|
565
|
+
*/
|
|
566
|
+
difficultyBrains?: DifficultyBrainMap;
|
|
558
567
|
}
|
|
559
568
|
export interface LocalMeshEntry {
|
|
560
569
|
id: string;
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -420,6 +420,10 @@ export interface AvailableProviderInfo {
|
|
|
420
420
|
lastVerification?: MachineProviderCheckResult;
|
|
421
421
|
/** Provider-declared Repo Mesh coordinator/MCP behavior. */
|
|
422
422
|
meshCoordinator?: ProviderMeshCoordinatorConfig;
|
|
423
|
+
/** BRAIN-ROUTING: suggested model values for the new-session model dropdown. */
|
|
424
|
+
modelOptions?: string[];
|
|
425
|
+
/** BRAIN-ROUTING: reasoning-effort values for the new-session thinking dropdown. */
|
|
426
|
+
thinkingLevelOptions?: string[];
|
|
423
427
|
/**
|
|
424
428
|
* Provider trust classification — derived from the on-disk layer the
|
|
425
429
|
* manifest came from and the shape of the manifest. Dashboards use
|
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.484",
|
|
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",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"author": "vilmire",
|
|
48
48
|
"license": "AGPL-3.0-or-later",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
51
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "0.9.82-rc.484",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.484",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -276,6 +276,10 @@ type CliStartOptions = {
|
|
|
276
276
|
resumeSessionId?: string;
|
|
277
277
|
settingsOverride?: Record<string, any>;
|
|
278
278
|
extraEnv?: Record<string, string>;
|
|
279
|
+
/** BRAIN-ROUTING thinking axis: standard level ('low'|'medium'|'high') applied
|
|
280
|
+
* at launch via the provider's thinkingLaunchArgs (CLI) or setConfigOption
|
|
281
|
+
* ('thought_level', ACP). Best-effort — ignored by providers with no support. */
|
|
282
|
+
initialThinkingLevel?: string;
|
|
279
283
|
};
|
|
280
284
|
|
|
281
285
|
const DEFAULT_COORDINATOR_DELEGATED_ENV_UNSETS = [
|
|
@@ -420,10 +424,32 @@ function expandResumeArgs(template: string[] | undefined, sessionId: string): st
|
|
|
420
424
|
* there is no template or no model (a model request without a template is a no-op —
|
|
421
425
|
* see startSession, where the caller logs the skip). MAGI kind-panel model axis.
|
|
422
426
|
*/
|
|
423
|
-
function expandModelLaunchArgs(template: string[] | undefined, model: string | undefined): string[] | undefined {
|
|
427
|
+
export function expandModelLaunchArgs(template: string[] | undefined, model: string | undefined): string[] | undefined {
|
|
424
428
|
const m = typeof model === 'string' ? model.trim() : '';
|
|
425
429
|
if (!m || !Array.isArray(template) || template.length === 0) return undefined;
|
|
426
|
-
|
|
430
|
+
// Substitute {{model}} anywhere in a token, not only when it is the whole token,
|
|
431
|
+
// so templates like ['-c', 'model={{model}}'] (codex) expand as well as the
|
|
432
|
+
// standalone ['--model', '{{model}}'] (claude) form.
|
|
433
|
+
return template.map((part) => part.includes('{{model}}') ? part.split('{{model}}').join(m) : part);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Expand a provider's `thinkingLaunchArgs` template with the requested thinking
|
|
438
|
+
* level, parallel to expandModelLaunchArgs. The standard level ('low'|'medium'|
|
|
439
|
+
* 'high') is first mapped through the provider's `thinkingLevelMap` (a level absent
|
|
440
|
+
* from the map passes through unchanged), then substituted into every `{{level}}`
|
|
441
|
+
* token. Returns undefined when there is no template or no level (best-effort; a
|
|
442
|
+
* thinking request without a template is a no-op). BRAIN-ROUTING thinking axis.
|
|
443
|
+
*/
|
|
444
|
+
export function expandThinkingLaunchArgs(
|
|
445
|
+
template: string[] | undefined,
|
|
446
|
+
level: string | undefined,
|
|
447
|
+
levelMap: Partial<Record<string, string>> | undefined,
|
|
448
|
+
): string[] | undefined {
|
|
449
|
+
const raw = typeof level === 'string' ? level.trim() : '';
|
|
450
|
+
if (!raw || !Array.isArray(template) || template.length === 0) return undefined;
|
|
451
|
+
const mapped = (levelMap && typeof levelMap[raw] === 'string' && levelMap[raw]!.trim()) ? levelMap[raw]!.trim() : raw;
|
|
452
|
+
return template.map((part) => part.includes('{{level}}') ? part.replace('{{level}}', mapped) : part);
|
|
427
453
|
}
|
|
428
454
|
|
|
429
455
|
function readSubcommandSessionId(args: string[], subcommands: string[]): string | undefined {
|
|
@@ -708,6 +734,9 @@ export class DaemonCliManager {
|
|
|
708
734
|
providerSessionId?: string;
|
|
709
735
|
launchMode?: CliLaunchMode;
|
|
710
736
|
extraEnv?: Record<string, string>;
|
|
737
|
+
/** BRAIN-ROUTING: post-launch thinking level for runtime-control providers
|
|
738
|
+
* (e.g. hermes reasoning). Passed through to the instance. */
|
|
739
|
+
initialThinkingLevel?: string;
|
|
711
740
|
/**
|
|
712
741
|
* On an attach (attachExisting=true), the real spawn time (ms epoch) of the
|
|
713
742
|
* session-host runtime being restored — a PAST timestamp. Used to restore the
|
|
@@ -956,6 +985,19 @@ export class DaemonCliManager {
|
|
|
956
985
|
}
|
|
957
986
|
}
|
|
958
987
|
|
|
988
|
+
// Brain routing thinking axis for ACP: route the standard level through the
|
|
989
|
+
// agent's thought_level config option. Best-effort — throws if the agent declares
|
|
990
|
+
// no thought_level category (see setConfigOption), so we swallow and warn.
|
|
991
|
+
if (options?.initialThinkingLevel) {
|
|
992
|
+
const lvl = options.initialThinkingLevel;
|
|
993
|
+
try {
|
|
994
|
+
await acpInstance.setConfigOption('thought_level', lvl);
|
|
995
|
+
console.log(colorize('green', ` 🧠 Initial thinking level set: ${lvl}`));
|
|
996
|
+
} catch (e: any) {
|
|
997
|
+
LOG.warn('CLI', `[ACP] Initial thinking level set failed (provider may not support thought_level): ${e?.message}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
959
1001
|
this.persistRecentActivity({
|
|
960
1002
|
kind: 'acp',
|
|
961
1003
|
providerType: normalizedType,
|
|
@@ -1003,8 +1045,22 @@ export class DaemonCliManager {
|
|
|
1003
1045
|
LOG.warn('CLI', `[${normalizedType}] initialModel='${initialModel}' requested but provider declares no modelLaunchArgs template — launching without model selection.`);
|
|
1004
1046
|
}
|
|
1005
1047
|
|
|
1048
|
+
// ─── Thinking axis (brain routing): expand initialThinkingLevel → launch args ───
|
|
1049
|
+
// Parallel to the model axis: a plain CLI provider selects reasoning effort at spawn
|
|
1050
|
+
// via the manifest's thinkingLaunchArgs template ('{{level}}' → the mapped level).
|
|
1051
|
+
// Best-effort; a provider with no template (or no requested level) is a no-op. ACP
|
|
1052
|
+
// providers route thinking through setConfigOption('thought_level') above.
|
|
1053
|
+
const initialThinkingLevel = options?.initialThinkingLevel;
|
|
1054
|
+
const thinkingLaunchArgs = expandThinkingLaunchArgs(provider?.thinkingLaunchArgs, initialThinkingLevel, provider?.thinkingLevelMap);
|
|
1055
|
+
const cliArgsWithBrain = thinkingLaunchArgs
|
|
1056
|
+
? [...thinkingLaunchArgs, ...(cliArgsWithModel || [])]
|
|
1057
|
+
: cliArgsWithModel;
|
|
1058
|
+
if (initialThinkingLevel && !thinkingLaunchArgs) {
|
|
1059
|
+
LOG.warn('CLI', `[${normalizedType}] initialThinkingLevel='${initialThinkingLevel}' requested but provider declares no thinkingLaunchArgs template — launching without thinking-level selection.`);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1006
1062
|
// ─── Resolve launch options → provider session binding ───
|
|
1007
|
-
const sessionBinding = resolveCliSessionBinding(provider, normalizedType,
|
|
1063
|
+
const sessionBinding = resolveCliSessionBinding(provider, normalizedType, cliArgsWithBrain, options?.resumeSessionId);
|
|
1008
1064
|
const resolvedCliArgs = sessionBinding.cliArgs;
|
|
1009
1065
|
|
|
1010
1066
|
// If InstanceManager exists, manage as CliProviderInstance unified
|
|
@@ -1024,6 +1080,10 @@ export class DaemonCliManager {
|
|
|
1024
1080
|
providerSessionId: sessionBinding.providerSessionId,
|
|
1025
1081
|
launchMode: sessionBinding.launchMode,
|
|
1026
1082
|
extraEnv: options?.extraEnv,
|
|
1083
|
+
// BRAIN-ROUTING: for a provider with no thinkingLaunchArgs but a
|
|
1084
|
+
// runtime reasoning control (hermes), apply the level post-launch.
|
|
1085
|
+
// The launch-arg providers (claude/codex) already consumed it at spawn.
|
|
1086
|
+
...(options?.initialThinkingLevel && !provider?.thinkingLaunchArgs ? { initialThinkingLevel: options.initialThinkingLevel } : {}),
|
|
1027
1087
|
onProviderSessionResolved: ({ providerSessionId, providerName, providerType, workspace }) => {
|
|
1028
1088
|
this.persistRecentActivity({
|
|
1029
1089
|
kind: 'cli',
|
|
@@ -1486,6 +1546,7 @@ export class DaemonCliManager {
|
|
|
1486
1546
|
resumeSessionId: args?.resumeSessionId,
|
|
1487
1547
|
settingsOverride,
|
|
1488
1548
|
extraEnv: delegatedLaunch ? delegatedLaunch.env : args?.env,
|
|
1549
|
+
...(typeof args?.initialThinkingLevel === 'string' && args.initialThinkingLevel.trim() ? { initialThinkingLevel: args.initialThinkingLevel.trim() } : {}),
|
|
1489
1550
|
},
|
|
1490
1551
|
);
|
|
1491
1552
|
|
|
@@ -457,6 +457,30 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
457
457
|
}
|
|
458
458
|
},
|
|
459
459
|
|
|
460
|
+
// ─── Brain routing: per-difficulty brain presets (machine-local) ───
|
|
461
|
+
// getDifficultyBrains returns the seeded defaults when nothing is configured,
|
|
462
|
+
// so the editor always shows a usable mapping. set replaces the whole map.
|
|
463
|
+
difficulty_brains_get: async (_ctx: MedFamilyContext, _args: any) => {
|
|
464
|
+
try {
|
|
465
|
+
const { getDifficultyBrains } = await import('../../config/mesh-config.js');
|
|
466
|
+
return { success: true, difficultyBrains: getDifficultyBrains() };
|
|
467
|
+
} catch (e: any) {
|
|
468
|
+
return { success: false, error: e.message };
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
difficulty_brains_set: async (_ctx: MedFamilyContext, args: any) => {
|
|
473
|
+
try {
|
|
474
|
+
const { setDifficultyBrains } = await import('../../config/mesh-config.js');
|
|
475
|
+
// normalizeDifficultyBrainMap (inside setDifficultyBrains) drops unknown
|
|
476
|
+
// keys and empty slots. An empty result clears the override → defaults.
|
|
477
|
+
const difficultyBrains = setDifficultyBrains(args?.difficultyBrains);
|
|
478
|
+
return { success: true, difficultyBrains };
|
|
479
|
+
} catch (e: any) {
|
|
480
|
+
return { success: false, error: e.message };
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
|
|
460
484
|
add_mesh_node: async (ctx: MedFamilyContext, args: any) => {
|
|
461
485
|
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
462
486
|
const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
|
|
@@ -22,7 +22,8 @@ import type {
|
|
|
22
22
|
RepoMeshHostMetadata,
|
|
23
23
|
RepoMeshDaemonRole,
|
|
24
24
|
} from '../repo-mesh-types.js';
|
|
25
|
-
import type { MagiKindPanelMap, MagiSlot, MagiTaskKind } from '@adhdev/mesh-shared';
|
|
25
|
+
import type { MagiKindPanelMap, MagiSlot, MagiTaskKind, DifficultyBrainMap } from '@adhdev/mesh-shared';
|
|
26
|
+
import { normalizeDifficultyBrainMap, DEFAULT_DIFFICULTY_BRAINS } from '@adhdev/mesh-shared';
|
|
26
27
|
import { mergeAndNormalizePolicy } from '../repo-mesh-types.js';
|
|
27
28
|
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
28
29
|
|
|
@@ -743,3 +744,31 @@ export function removeMagiKindPanel(kind: string): boolean {
|
|
|
743
744
|
saveMeshConfig(stored);
|
|
744
745
|
return true;
|
|
745
746
|
}
|
|
747
|
+
|
|
748
|
+
// ─── Brain routing: per-difficulty brain presets (machine-local) ───
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* The difficulty→brain presets, machine-local. When nothing is configured yet,
|
|
752
|
+
* returns the sensible DEFAULT_DIFFICULTY_BRAINS so the coordinator always has a
|
|
753
|
+
* usable mapping (the operator can override via setDifficultyBrains). Returns a
|
|
754
|
+
* normalized copy — never the stored reference.
|
|
755
|
+
*/
|
|
756
|
+
export function getDifficultyBrains(): DifficultyBrainMap {
|
|
757
|
+
const stored = loadMeshConfig().difficultyBrains;
|
|
758
|
+
const normalized = normalizeDifficultyBrainMap(stored);
|
|
759
|
+
return Object.keys(normalized).length > 0 ? normalized : { ...DEFAULT_DIFFICULTY_BRAINS };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Replace the difficulty→brain presets wholesale (the editor pushes the full map).
|
|
764
|
+
* Passing an empty/normalized-empty map clears the override, so getDifficultyBrains
|
|
765
|
+
* falls back to the defaults again. Returns the normalized, persisted map.
|
|
766
|
+
*/
|
|
767
|
+
export function setDifficultyBrains(map: unknown): DifficultyBrainMap {
|
|
768
|
+
const normalized = normalizeDifficultyBrainMap(map);
|
|
769
|
+
const stored = loadMeshConfig();
|
|
770
|
+
if (Object.keys(normalized).length > 0) stored.difficultyBrains = normalized;
|
|
771
|
+
else delete stored.difficultyBrains;
|
|
772
|
+
saveMeshConfig(stored);
|
|
773
|
+
return normalized;
|
|
774
|
+
}
|
|
@@ -32,6 +32,8 @@ import type {
|
|
|
32
32
|
RepoMeshNodeStatus,
|
|
33
33
|
} from '../repo-mesh-types.js';
|
|
34
34
|
import { mergeAndNormalizePolicy } from '../repo-mesh-types.js';
|
|
35
|
+
import { getDifficultyBrains } from '../config/mesh-config.js';
|
|
36
|
+
import { MESH_TASK_DIFFICULTIES } from '@adhdev/mesh-shared';
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* Cheap, locally-derived "what just happened" snapshot for the coordinator
|
|
@@ -279,6 +281,9 @@ Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `\nDefault branch: \`
|
|
|
279
281
|
// ── Policy ──
|
|
280
282
|
sections.push(buildPolicySection(mergeAndNormalizePolicy(undefined, mesh.policy)));
|
|
281
283
|
|
|
284
|
+
// ── Brain presets (difficulty → model/thinking) ──
|
|
285
|
+
sections.push(buildBrainPresetsSection());
|
|
286
|
+
|
|
282
287
|
// ── Tools ──
|
|
283
288
|
sections.push(TOOLS_SECTION);
|
|
284
289
|
|
|
@@ -599,6 +604,36 @@ function truncateNote(text: string): string {
|
|
|
599
604
|
return `${text.slice(0, OPERATING_NOTE_MAX_CHARS).trimEnd()}… [truncated]`;
|
|
600
605
|
}
|
|
601
606
|
|
|
607
|
+
/**
|
|
608
|
+
* Render the difficulty→brain presets so the coordinator knows what each
|
|
609
|
+
* `difficulty` value resolves to (which model / thinking level). Machine-local,
|
|
610
|
+
* read live at prompt-build time — seeded defaults when nothing is configured.
|
|
611
|
+
*/
|
|
612
|
+
function buildBrainPresetsSection(): string {
|
|
613
|
+
let brains;
|
|
614
|
+
try { brains = getDifficultyBrains(); } catch { brains = {}; }
|
|
615
|
+
const lines = [
|
|
616
|
+
'## Brain presets',
|
|
617
|
+
'',
|
|
618
|
+
'When you pass `difficulty` on `mesh_enqueue_task`, it resolves to this model / thinking level (an explicit model/thinkingLevel on the task overrides it). Pick easy for trivial work to save tokens, difficult for hard reasoning.',
|
|
619
|
+
'',
|
|
620
|
+
];
|
|
621
|
+
for (const key of MESH_TASK_DIFFICULTIES) {
|
|
622
|
+
const slot = (brains as Record<string, { provider?: string; model?: string; thinkingLevel?: string } | undefined>)[key];
|
|
623
|
+
if (!slot || (!slot.provider && !slot.model && !slot.thinkingLevel)) {
|
|
624
|
+
lines.push(`- **${key}**: (no preset — ordinary routing)`);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const parts = [
|
|
628
|
+
slot.provider ? `provider: \`${slot.provider}\`` : '',
|
|
629
|
+
slot.model ? `model: \`${slot.model}\`` : '',
|
|
630
|
+
slot.thinkingLevel ? `thinking: \`${slot.thinkingLevel}\`` : '',
|
|
631
|
+
].filter(Boolean).join(' | ');
|
|
632
|
+
lines.push(`- **${key}**: ${parts}`);
|
|
633
|
+
}
|
|
634
|
+
return lines.join('\n');
|
|
635
|
+
}
|
|
636
|
+
|
|
602
637
|
function buildPolicySection(policy: RepoMeshPolicy): string {
|
|
603
638
|
const rules: string[] = [];
|
|
604
639
|
if (policy.requirePreTaskCheckpoint) rules.push('- Create a git checkpoint **before** starting each task');
|
|
@@ -737,6 +772,7 @@ function buildRulesSection(coordinatorCliType?: string): string {
|
|
|
737
772
|
- **Front-load task messages.** Include everything the agent needs (files, problem, expected fix) in \`mesh_enqueue_task\` / \`mesh_send_task\`. Append a structured result request at the end: ask the worker to conclude with a JSON block containing \`status\`, \`changedFiles\`, \`gitStatus\`, \`validationResults\`, \`errors\`, \`nextAction\`. The daemon parses this automatically; you can read it from \`mesh_task_history\`.
|
|
738
773
|
- **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start fresh only for independent work, provider mismatch, transcript contamination, or required worktree isolation.
|
|
739
774
|
- **Worktree affinity.** A worktree is a durable per-branch workspace; keep all of a branch's code_change/fix/review work on its worktree node by targeting \`required_tags: ["worktree=<branch>"]\` or \`target_node_id\`. Get the id/branch from the \`mesh_clone_node\` result or a live \`mesh_status\` — the Configured Nodes snapshot won't list a worktree cloned after launch. Untargeted same-branch follow-ups drift to the base node. Only \`convergence\` (merge/push) runs on the base, never pinned to the worktree.
|
|
775
|
+
- **Classify task difficulty to save tokens.** For each task you enqueue, judge its execution difficulty and pass \`difficulty\`: \`easy\` (extraction, renames, doc tweaks, trivial fixes), \`medium\` (ordinary feature/bugfix work), \`difficult\` (architecture, tricky debugging, multi-file refactors, subtle reasoning), or \`freeform\`. The mesh's per-difficulty brain preset then runs easy tasks on a cheaper model at low reasoning effort and hard tasks on a stronger model at high effort — real token savings on simple work. The current presets are shown in the "Brain presets" section below. You may still pass an explicit \`model\`/\`thinkingLevel\` to override the preset for one task.
|
|
740
776
|
- **Respect explicit provider requests.** Map: Hermes → \`hermes-cli\`, Claude/Claude Code → \`claude-cli\`, Codex → \`codex-cli\`, Gemini → \`gemini-cli\`, Antigravity → \`antigravity-cli\`. Never substitute the coordinator's own runtime.
|
|
741
777
|
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
742
778
|
- **Limit parallelism.** Start with 1–2 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing. This caps *concurrent* load — it does not mean serialize independent work: when a new, independent request arrives and there is headroom under \`maxParallelTasks\`, dispatch it right away rather than waiting for an in-flight task or a user nudge (read-only diagnosis especially, since it has no merge cost).
|
|
@@ -75,6 +75,18 @@ export interface PendingMeshCoordinatorEvent {
|
|
|
75
75
|
dispatchedBy?: CoordinatorIdentity;
|
|
76
76
|
/** Present only for unicast scope: the coordinator this event is addressed to. */
|
|
77
77
|
intendedFor?: CoordinatorIdentity;
|
|
78
|
+
/**
|
|
79
|
+
* True when this event was stamped as a broadcast SOLELY because no owning
|
|
80
|
+
* coordinator identity was resolvable at emit time (self-fallback: dispatchedBy
|
|
81
|
+
* is THIS daemon's own machineId, not a real coordinator). Such a broadcast has
|
|
82
|
+
* no owner, so the MAGI-REPLICA-COMPLETION-EVENT-LEAK guard — which only exists
|
|
83
|
+
* to stop a NON-owner coordinator from consuming an OWNED terminal event — must
|
|
84
|
+
* not apply: an ownerless terminal broadcast is a genuine "deliver to any
|
|
85
|
+
* coordinator that drains on this machine" event and identity-matching its
|
|
86
|
+
* self-id dispatchedBy against the drainer would wrongly route it away.
|
|
87
|
+
* Absent (undefined/false) on a normally-owned event → the leak guard applies.
|
|
88
|
+
*/
|
|
89
|
+
dispatchedBySelfFallback?: boolean;
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
/**
|
|
@@ -419,7 +431,21 @@ function routeV2EventsForDrainer(
|
|
|
419
431
|
// matching semantics as unicast (identityDeliversTo), so the true owner —
|
|
420
432
|
// possibly addressed under a different daemon-id form — still receives it.
|
|
421
433
|
if (validated.scope === 'broadcast' && isTerminalTaskEvent(validated.event)) {
|
|
422
|
-
|
|
434
|
+
// An ownerless self-fallback broadcast (dispatchedBy is this daemon's
|
|
435
|
+
// own machineId because no coordinator identity existed at emit) has no
|
|
436
|
+
// coordinator owner — but it must still stay on ITS machine: a replica
|
|
437
|
+
// completion emitted on machine A must never fan out to a coordinator on
|
|
438
|
+
// machine B (the MAGI-REPLICA leak). So for a self-fallback event, match
|
|
439
|
+
// at the MACHINE (daemonId) level — deliver iff the drainer is on the
|
|
440
|
+
// same machine as the self-dispatcher — instead of the full
|
|
441
|
+
// identityDeliversTo (which also compares runId/session and would route
|
|
442
|
+
// the event away from a same-machine coordinator whose id form differs,
|
|
443
|
+
// the exact symptom for refine:* / agent:generating_completed reaching a
|
|
444
|
+
// stdio MCP coordinator). Non-self-fallback broadcasts keep the strict
|
|
445
|
+
// owner check.
|
|
446
|
+
const deliverSelfFallback = event.dispatchedBySelfFallback
|
|
447
|
+
&& daemonIdsEquivalent(validated.dispatchedBy.daemonId, drainer.daemonId);
|
|
448
|
+
if (deliverSelfFallback || identityDeliversTo(validated.dispatchedBy, drainer)) {
|
|
423
449
|
ctx.batchSeen.add(eventId);
|
|
424
450
|
bump('v2Delivered');
|
|
425
451
|
kept.push(event);
|
|
@@ -858,6 +884,13 @@ export function stampPendingEventV2(
|
|
|
858
884
|
});
|
|
859
885
|
if (!stamp) return event; // no coordinator identity at all (no self id) → stays a v1 event
|
|
860
886
|
|
|
887
|
+
// Mark an ownerless (self-fallback) broadcast so the drain-side leak guard can
|
|
888
|
+
// tell it apart from a genuinely owned broadcast terminal event. selfFallback is
|
|
889
|
+
// true only when no coordinator identity existed and we minted the stamp under
|
|
890
|
+
// this daemon's own machineId; a broadcast that stays broadcast for that reason
|
|
891
|
+
// has no owner to leak from and must reach whatever coordinator drains here.
|
|
892
|
+
const dispatchedBySelfFallback = selfFallback && stamp.scope === 'broadcast';
|
|
893
|
+
|
|
861
894
|
return {
|
|
862
895
|
...event,
|
|
863
896
|
protocolVersion: stamp.protocolVersion,
|
|
@@ -865,6 +898,7 @@ export function stampPendingEventV2(
|
|
|
865
898
|
scope: stamp.scope,
|
|
866
899
|
dispatchedBy: stamp.dispatchedBy,
|
|
867
900
|
...(stamp.intendedFor ? { intendedFor: stamp.intendedFor } : {}),
|
|
901
|
+
...(dispatchedBySelfFallback ? { dispatchedBySelfFallback: true } : {}),
|
|
868
902
|
};
|
|
869
903
|
}
|
|
870
904
|
|
|
@@ -76,17 +76,34 @@ export function getMeshWithCache(components: DaemonComponents, meshId: string):
|
|
|
76
76
|
*
|
|
77
77
|
* Fix: union the local-config nodes with any inline-cache-ONLY nodes, so the claim
|
|
78
78
|
* view matches the command (send_task) view. Base (non-worktree) nodes present in
|
|
79
|
-
* local config stay config-authoritative — their
|
|
80
|
-
* localMesh, so base node claim/matching is byte-for-byte unchanged. Only nodes
|
|
79
|
+
* local config stay config-authoritative — their STATIC fields are taken verbatim
|
|
80
|
+
* from localMesh, so base node claim/matching is byte-for-byte unchanged. Only nodes
|
|
81
81
|
* that exist solely in the inline cache (the cloned worktree nodes) are appended.
|
|
82
82
|
* Identity comparison uses the shared 3-form normalizer (id / nodeId / node_id),
|
|
83
83
|
* identical to every other claim-path consumer — the matching logic is untouched,
|
|
84
84
|
* only which nodes are visible.
|
|
85
|
+
*
|
|
86
|
+
* BOOTSTRAP-DEFER VIEW-CONSISTENCY (this fix): for a worktree node that IS registered
|
|
87
|
+
* in local config, the union previously took the config node verbatim and discarded the
|
|
88
|
+
* inline-cache entry entirely. But the inline cache holds the FRESHER runtime bootstrap
|
|
89
|
+
* state — markWorktreeBootstrapTerminalState stamps worktreeBootstrap.status='complete'
|
|
90
|
+
* synchronously into the inline cache, while local config lags behind the detached async
|
|
91
|
+
* persist chain (and on the coordinator may never receive it at all). A config-registered
|
|
92
|
+
* worktree node therefore read a permanently stale 'running' here, so
|
|
93
|
+
* shouldDeferDispatchForBootstrap deferred its claim forever. We now MERGE the inline
|
|
94
|
+
* cache's dynamic runtime bootstrap state onto the config node (config keeps its static
|
|
95
|
+
* fields; worktreeBootstrap is preferred from the inline cache when the inline entry
|
|
96
|
+
* carries a status) so the gate view sees the terminal stamp. Regression-safe: when the
|
|
97
|
+
* inline entry has no bootstrap status the config value is kept, and when bootstrap is
|
|
98
|
+
* genuinely still 'running' (no terminal stamp yet) the gate still defers — only a node
|
|
99
|
+
* whose inline stamp has actually reached a terminal state opens the gate.
|
|
85
100
|
*/
|
|
86
101
|
function mergeInlineCacheOnlyNodes(localMesh: any, cachedMesh: any): any {
|
|
87
102
|
const localNodes = Array.isArray(localMesh?.nodes) ? localMesh.nodes : [];
|
|
88
103
|
const cachedNodes = Array.isArray(cachedMesh?.nodes) ? cachedMesh.nodes : [];
|
|
89
104
|
if (!cachedNodes.length) return localMesh;
|
|
105
|
+
// Index inline-cache nodes by identity so we can (a) append cache-only nodes and
|
|
106
|
+
// (b) prefer the inline runtime bootstrap state on config-registered nodes.
|
|
90
107
|
const cacheOnly = cachedNodes.filter((cachedNode: any) => {
|
|
91
108
|
const cachedId = readMeshNodeId(cachedNode);
|
|
92
109
|
// Unidentifiable cache entries can never be a claim/route target — skip them
|
|
@@ -94,8 +111,29 @@ function mergeInlineCacheOnlyNodes(localMesh: any, cachedMesh: any): any {
|
|
|
94
111
|
if (!cachedId) return false;
|
|
95
112
|
return !localNodes.some((localNode: any) => meshNodeIdMatches(localNode, cachedId));
|
|
96
113
|
});
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
// Overlay the inline cache's fresher worktreeBootstrap state onto any config node
|
|
115
|
+
// that also exists in the inline cache. Only override when the inline entry actually
|
|
116
|
+
// carries a bootstrap status (an incomplete inline entry never masks a genuine config
|
|
117
|
+
// 'running'), mirroring the inline-first read the bootstrap gate does directly.
|
|
118
|
+
let overlaidLocalNodes: any[] = localNodes;
|
|
119
|
+
let overlaid = false;
|
|
120
|
+
for (let i = 0; i < localNodes.length; i++) {
|
|
121
|
+
const localNode = localNodes[i];
|
|
122
|
+
const localId = readMeshNodeId(localNode);
|
|
123
|
+
if (!localId) continue;
|
|
124
|
+
const inlineMatch = cachedNodes.find((cachedNode: any) => meshNodeIdMatches(cachedNode, localId));
|
|
125
|
+
const inlineBootstrapStatus = readNonEmptyString(inlineMatch?.worktreeBootstrap?.status);
|
|
126
|
+
if (!inlineMatch || !inlineBootstrapStatus) continue;
|
|
127
|
+
if (!overlaid) {
|
|
128
|
+
overlaidLocalNodes = [...localNodes];
|
|
129
|
+
overlaid = true;
|
|
130
|
+
}
|
|
131
|
+
// Keep the config node's static fields; prefer the inline cache's dynamic
|
|
132
|
+
// bootstrap runtime state (fresher terminal stamp).
|
|
133
|
+
overlaidLocalNodes[i] = { ...localNode, worktreeBootstrap: inlineMatch.worktreeBootstrap };
|
|
134
|
+
}
|
|
135
|
+
if (!cacheOnly.length && !overlaid) return localMesh;
|
|
136
|
+
return { ...localMesh, nodes: [...overlaidLocalNodes, ...cacheOnly] };
|
|
99
137
|
}
|
|
100
138
|
|
|
101
139
|
// ---------------------------------------------------------------------------
|
|
@@ -1880,6 +1918,8 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
|
|
|
1880
1918
|
// MAGI-KIND-PANEL model axis: forward the task's model override so the
|
|
1881
1919
|
// remote worker session launches with it (initialModel). Best-effort.
|
|
1882
1920
|
...(typeof task.model === 'string' && task.model.trim() ? { initialModel: task.model.trim() } : {}),
|
|
1921
|
+
// BRAIN-ROUTING thinking axis: forward the task's thinking level (initialThinkingLevel).
|
|
1922
|
+
...(typeof task.thinkingLevel === 'string' && task.thinkingLevel.trim() ? { initialThinkingLevel: task.thinkingLevel.trim() } : {}),
|
|
1883
1923
|
});
|
|
1884
1924
|
} catch (e: any) {
|
|
1885
1925
|
markAutoLaunch(meshId, task.id, { status: 'failed', reason: `remote_launch_dispatch_failed: ${e?.message || String(e)}`, nodeId, providerType: resolved.providerType });
|
|
@@ -1912,6 +1952,8 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
|
|
|
1912
1952
|
// MAGI-KIND-PANEL model axis: local launch forwards the task's model
|
|
1913
1953
|
// override as initialModel (CLI → modelLaunchArgs; ACP → setConfigOption).
|
|
1914
1954
|
...(typeof task.model === 'string' && task.model.trim() ? { initialModel: task.model.trim() } : {}),
|
|
1955
|
+
// BRAIN-ROUTING thinking axis: forward the task's thinking level (initialThinkingLevel).
|
|
1956
|
+
...(typeof task.thinkingLevel === 'string' && task.thinkingLevel.trim() ? { initialThinkingLevel: task.thinkingLevel.trim() } : {}),
|
|
1915
1957
|
});
|
|
1916
1958
|
if (!launchResult?.success) {
|
|
1917
1959
|
const reason = launchResult?.error || 'launch_cli_failed';
|