@adhdev/daemon-core 0.9.82-rc.484 → 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.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +311 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +310 -34
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-queue-assignment.d.ts +4 -0
- package/dist/mesh/mesh-work-queue.d.ts +9 -0
- package/dist/repo-mesh-types.d.ts +18 -2
- package/dist/shared-types.d.ts +8 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/package.json +3 -3
- package/src/cli-adapters/provider-cli-adapter.ts +70 -1
- package/src/commands/handler.ts +21 -1
- package/src/commands/high-family/mesh-coordinator-launch.ts +17 -1
- package/src/commands/high-family/mesh-status.ts +8 -0
- package/src/commands/med-family/cli-agent.ts +29 -0
- package/src/index.ts +1 -1
- package/src/mesh/coordinator-prompt.ts +4 -1
- package/src/mesh/mesh-queue-assignment.ts +227 -40
- package/src/mesh/mesh-work-queue.ts +14 -0
- package/src/repo-mesh-types.ts +28 -3
- package/src/shared-types.ts +8 -0
- package/src/status/builders.ts +45 -2
- package/src/status/snapshot.ts +1 -1
|
@@ -51,6 +51,10 @@ export declare function __resolveSchedulingStrategyForTests(mesh: any): RepoMesh
|
|
|
51
51
|
* scheduling pipeline can be unit-tested without standing up live CLI sessions. */
|
|
52
52
|
export declare function __orderEligibleNodesForTests(meshId: string, strategy: RepoMeshSchedulingStrategy, nodes: RankableNode[], opts?: {
|
|
53
53
|
bumpCursor?: boolean;
|
|
54
|
+
task?: {
|
|
55
|
+
difficulty?: string;
|
|
56
|
+
requiredTags?: string[];
|
|
57
|
+
};
|
|
54
58
|
}): RankableNode[];
|
|
55
59
|
/** One idle session eligible to claim a queued task, together with the resolved
|
|
56
60
|
* mesh node record it belongs to. Local candidates come from live CLI instances,
|
|
@@ -126,6 +126,15 @@ export interface MeshWorkQueueEntry {
|
|
|
126
126
|
* setConfigOption('thought_level')). Rides in payload JSON. Best-effort like model.
|
|
127
127
|
*/
|
|
128
128
|
thinkingLevel?: string;
|
|
129
|
+
/**
|
|
130
|
+
* SLOT-ROUTING (ORCHESTRATION_NODE_SLOTS.md): the coordinator's difficulty
|
|
131
|
+
* classification for this task ('easy'|'medium'|'difficult'|'freeform'),
|
|
132
|
+
* PERSISTED on the entry so the scheduler can match it against node capability
|
|
133
|
+
* slots at assignment time. Previously an enqueue-only option consumed to
|
|
134
|
+
* resolve model/thinkingLevel and then discarded; keeping it lets task→node
|
|
135
|
+
* fitness matching run. Absent on tasks enqueued without a difficulty.
|
|
136
|
+
*/
|
|
137
|
+
difficulty?: string;
|
|
129
138
|
/**
|
|
130
139
|
* M1: why this task is held back (e.g. "dependency_failed:<taskId>").
|
|
131
140
|
* Only set by the system on dependency failure under the 'block' policy;
|
|
@@ -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, DifficultyBrainMap } from '@adhdev/mesh-shared';
|
|
16
|
+
import type { MagiKindPanelMap, DifficultyBrainMap, NodeCapabilitySlot } from '@adhdev/mesh-shared';
|
|
17
17
|
export interface RepoMesh {
|
|
18
18
|
id: string;
|
|
19
19
|
name: string;
|
|
@@ -114,7 +114,7 @@ export type RepoMeshMagiSessionCleanupMode = 'preserve' | 'stop_and_delete';
|
|
|
114
114
|
* Distribution is explicit opt-in: a strategy other than 'first_eligible' must be
|
|
115
115
|
* configured for any load-spreading to occur.
|
|
116
116
|
*/
|
|
117
|
-
export type RepoMeshSchedulingStrategy = 'first_eligible' | 'least_loaded' | 'round_robin' | 'priority_only';
|
|
117
|
+
export type RepoMeshSchedulingStrategy = 'first_eligible' | 'least_loaded' | 'round_robin' | 'priority_only' | 'fitness';
|
|
118
118
|
export declare const MESH_SCHEDULING_STRATEGIES: RepoMeshSchedulingStrategy[];
|
|
119
119
|
export declare const DEFAULT_MESH_SCHEDULING_STRATEGY: RepoMeshSchedulingStrategy;
|
|
120
120
|
/**
|
|
@@ -310,8 +310,22 @@ export interface RepoMeshNodePolicy {
|
|
|
310
310
|
* enforced as an additional, stricter-wins constraint on top of the global
|
|
311
311
|
* maxParallelTasks/taskMode caps. Missing/empty: the node behaves exactly as
|
|
312
312
|
* before (global caps only). Routing is governed solely by required_tags.
|
|
313
|
+
*
|
|
314
|
+
* SUPERSEDED by `slots` (ORCHESTRATION_NODE_SLOTS.md). Kept for back-compat:
|
|
315
|
+
* when `slots` is absent, providerRoles + providerPriority + the machine-global
|
|
316
|
+
* difficultyBrains are auto-derived into slots via deriveSlotsFromLegacy.
|
|
313
317
|
*/
|
|
314
318
|
providerRoles?: RepoMeshProviderRole[];
|
|
319
|
+
/**
|
|
320
|
+
* Node capability slots (ORCHESTRATION_NODE_SLOTS.md) — the ordered "Preferred
|
|
321
|
+
* AI tools" profile that is the single source of truth for task routing, MAGI
|
|
322
|
+
* fan-out, and orchestrator-proposed edits. Each slot bundles provider + model
|
|
323
|
+
* + thinkingLevel + difficulty range + capability tags + per-slot maxParallel.
|
|
324
|
+
* Order = preference. When absent, the scheduler derives slots from the legacy
|
|
325
|
+
* providerPriority/providerRoles/difficultyBrains (deriveSlotsFromLegacy) so
|
|
326
|
+
* existing nodes keep working without reconfiguration.
|
|
327
|
+
*/
|
|
328
|
+
slots?: NodeCapabilitySlot[];
|
|
315
329
|
/**
|
|
316
330
|
* Per-node override for RepoMeshPolicy.delegatedWorkerAutoApprove. When set, takes
|
|
317
331
|
* precedence over the mesh-level policy for worker sessions launched onto this node.
|
|
@@ -858,6 +872,8 @@ export interface RepoMeshNodeStatus {
|
|
|
858
872
|
activeSessions: string[];
|
|
859
873
|
activeSessionDetails?: RepoMeshSessionStatus[];
|
|
860
874
|
providerPriority?: string[];
|
|
875
|
+
/** Explicitly-configured node capability slots (ORCHESTRATION_NODE_SLOTS.md). */
|
|
876
|
+
slots?: NodeCapabilitySlot[];
|
|
861
877
|
launchReady?: boolean;
|
|
862
878
|
/** True when the node is clean, ahead=0, behind>0, and safe for fast-forward consideration. */
|
|
863
879
|
autoFastForwardEligible?: boolean;
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -287,6 +287,13 @@ export interface SessionEntry {
|
|
|
287
287
|
completionMarker?: string;
|
|
288
288
|
seenCompletionMarker?: string;
|
|
289
289
|
surfaceHidden?: boolean;
|
|
290
|
+
/**
|
|
291
|
+
* User (or coordinator-policy) muted: suppress attention side-effects
|
|
292
|
+
* (notifications, toasts, completion audio) for this session WITHOUT removing
|
|
293
|
+
* it from the inbox list. Distinct from surfaceHidden (which collapses it from
|
|
294
|
+
* the list). Daemon-owned, in-memory; rides the status snapshot.
|
|
295
|
+
*/
|
|
296
|
+
muted?: boolean;
|
|
290
297
|
settings?: Record<string, any>;
|
|
291
298
|
/**
|
|
292
299
|
* True owning-daemon id for a session a coordinator synthesises into its own
|
|
@@ -361,6 +368,7 @@ export interface CompactSessionEntry {
|
|
|
361
368
|
completionMarker?: string;
|
|
362
369
|
seenCompletionMarker?: string;
|
|
363
370
|
surfaceHidden?: boolean;
|
|
371
|
+
muted?: boolean;
|
|
364
372
|
controlValues?: Record<string, string | number | boolean>;
|
|
365
373
|
providerControls?: ProviderControlSchema[];
|
|
366
374
|
summaryMetadata?: ProviderSummaryMetadata;
|
|
@@ -64,6 +64,7 @@ export interface RecentReadDebugSnapshot {
|
|
|
64
64
|
messageUpdatedAt: number;
|
|
65
65
|
}
|
|
66
66
|
export declare function shouldEmitRecentReadDebugLog(cache: Map<string, string>, snapshot: RecentReadDebugSnapshot): boolean;
|
|
67
|
+
export declare function buildAvailableProviders(providerLoader: StatusSnapshotOptions['providerLoader']): AvailableProviderInfo[];
|
|
67
68
|
export declare function buildMachineInfo(profile?: 'full' | 'live' | 'metadata'): MachineInfo;
|
|
68
69
|
/**
|
|
69
70
|
* Resolve the last user-visible (non-system) message for a session as a preview.
|
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.486",
|
|
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.486",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.486",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -208,6 +208,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
208
208
|
private lastScreenSnapshot = '';
|
|
209
209
|
private lastScreenText = '';
|
|
210
210
|
private lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
211
|
+
// (FALSEIDLE Path-C) Count of CONSECUTIVE getStatus polls that observed a
|
|
212
|
+
// gate-eligible static-idle screen (detect=idle, no modal, quiet, empty
|
|
213
|
+
// partial buffer). For a mesh/autonomous worker we require several such
|
|
214
|
+
// polls in a row before confirming static-idle (see getStatus), so a
|
|
215
|
+
// single momentarily-silent point-sample of a still-live turn cannot flip
|
|
216
|
+
// it. Reset to 0 the instant any poll is ineligible.
|
|
217
|
+
private staticIdlePollStreak = 0;
|
|
211
218
|
|
|
212
219
|
// Server log forwarding
|
|
213
220
|
private serverConn: any = null;
|
|
@@ -287,6 +294,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
287
294
|
result: any;
|
|
288
295
|
} | null = null;
|
|
289
296
|
private static readonly SCREEN_SNAPSHOT_MIN_INTERVAL_MS = 250;
|
|
297
|
+
// (FALSEIDLE Path-C) Consecutive gate-eligible getStatus polls a mesh/autonomous
|
|
298
|
+
// session must show before the poll-static-idle confirm fires. 2 = one extra
|
|
299
|
+
// status tick of hysteresis: enough to reject a single momentary-silence
|
|
300
|
+
// point-sample of a still-live turn, cheap enough not to materially delay a
|
|
301
|
+
// genuine boot-wedge release (the wedge screen is stably static, so it clears
|
|
302
|
+
// every consecutive poll and confirms on the 2nd).
|
|
303
|
+
private static readonly STATIC_IDLE_POLL_CONFIRM_COUNT = 2;
|
|
290
304
|
|
|
291
305
|
private readonly providerResolutionMeta: ProviderResolutionMeta;
|
|
292
306
|
|
|
@@ -394,6 +408,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
394
408
|
return this.timeouts.statusActivityHold;
|
|
395
409
|
}
|
|
396
410
|
|
|
411
|
+
// (FALSEIDLE Path-C) Whether this session is a mesh worker or coordinator's
|
|
412
|
+
// own autonomous session. Mirrors CliProviderInstance.isAutonomousMeshSession
|
|
413
|
+
// over the runtimeSettings the instance mirrors down via updateRuntimeSettings
|
|
414
|
+
// (meshNodeFor / meshActiveTaskId / meshNodeId / launchedByCoordinator =
|
|
415
|
+
// isMeshWorkerSession, plus meshCoordinatorFor for the coordinator's own turn).
|
|
416
|
+
// Such a session has no human at the keyboard to correct a premature idle, so
|
|
417
|
+
// the poll-static-idle confirm is debounced for it (multiple consecutive idle
|
|
418
|
+
// polls) rather than fired on a single point-sample.
|
|
419
|
+
private isAutonomousMeshSession(): boolean {
|
|
420
|
+
const s = this.runtimeSettings;
|
|
421
|
+
return !!(s?.meshNodeFor || s?.meshActiveTaskId || s?.meshNodeId
|
|
422
|
+
|| s?.launchedByCoordinator || s?.meshCoordinatorFor);
|
|
423
|
+
}
|
|
424
|
+
|
|
397
425
|
// Resolved timeouts
|
|
398
426
|
private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
|
|
399
427
|
|
|
@@ -956,15 +984,56 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
956
984
|
const quietForMs = this.lastNonEmptyOutputAt
|
|
957
985
|
? (now - this.lastNonEmptyOutputAt)
|
|
958
986
|
: Number.MAX_SAFE_INTEGER;
|
|
987
|
+
let eligible = false;
|
|
959
988
|
if (quietForMs >= this.getStatusActivityHoldMs()) {
|
|
960
989
|
const screenText = this.terminalScreen.getText();
|
|
961
990
|
const pollDetect = this.runDetectStatus(screenText || this.recentOutputBuffer);
|
|
962
991
|
const pollModal = this.runParseApproval(screenText)
|
|
963
992
|
|| this.runParseApproval(this.recentOutputBuffer);
|
|
964
|
-
|
|
993
|
+
// (FALSEIDLE Path-C) Final-assistant / pending-response discriminator.
|
|
994
|
+
// Paths A and B refuse to finalize a turn whose partial-response buffer
|
|
995
|
+
// is still non-empty (getCompletedFinalizationBlock 'partial_response_pending'
|
|
996
|
+
// / completionFinalAssistantEvidence turnClosed at cli-provider-instance.ts).
|
|
997
|
+
// Path C (this poll) previously OMITTED it, so a genuinely-live but
|
|
998
|
+
// momentarily-silent turn — silent thinking, a backgrounded/long tool child,
|
|
999
|
+
// the gap between two assistant bubbles — whose currentTurnScope anchor was
|
|
1000
|
+
// lost still satisfied the weaker gate and flipped to idle prematurely.
|
|
1001
|
+
// Require an EMPTY partial buffer here too. getPartialResponse() returns the
|
|
1002
|
+
// accumulated assistant stream while isWaitingForResponse (which
|
|
1003
|
+
// applyGenerating leaves set on the boot-banner wedge too), so this does NOT
|
|
1004
|
+
// reintroduce the D4b wedge: the attach/boot-banner seeds only the static
|
|
1005
|
+
// ready screen — no assistant turn ever streamed — so its partial buffer is
|
|
1006
|
+
// empty and the gate still releases it. A mid-turn quiet gap holds a
|
|
1007
|
+
// non-empty buffer and is deferred.
|
|
1008
|
+
const partial = this.getPartialResponse();
|
|
1009
|
+
const partialPending = typeof partial === 'string' && partial.trim().length > 0;
|
|
1010
|
+
eligible = pollDetect === 'idle' && !pollModal && !partialPending;
|
|
1011
|
+
}
|
|
1012
|
+
if (eligible) {
|
|
1013
|
+
// (FALSEIDLE Path-C) Debounce for autonomous mesh sessions. A worker /
|
|
1014
|
+
// coordinator has no human to correct a premature idle, and a single
|
|
1015
|
+
// runDetectStatus point-sample can land in a live turn's momentary silence.
|
|
1016
|
+
// Require STATIC_IDLE_POLL_CONFIRM_COUNT consecutive eligible polls before
|
|
1017
|
+
// confirming, so the FSM must observe a sustained static-idle screen — a
|
|
1018
|
+
// turn that resumes (fresh output, or a re-armed turn scope) resets the
|
|
1019
|
+
// streak. The status poll runs on the 30s-idle / 5s-generating heartbeat and
|
|
1020
|
+
// this getStatus gate is re-hit each dashboard status tick, so 2 confirms is
|
|
1021
|
+
// ~one extra tick of hysteresis — enough to reject a one-sample silence gap
|
|
1022
|
+
// without materially delaying a genuine boot-wedge release. Foreground /
|
|
1023
|
+
// attended sessions keep the single-poll confirm (a human is watching Send).
|
|
1024
|
+
const requiredStreak = this.isAutonomousMeshSession()
|
|
1025
|
+
? ProviderCliAdapter.STATIC_IDLE_POLL_CONFIRM_COUNT
|
|
1026
|
+
: 1;
|
|
1027
|
+
this.staticIdlePollStreak += 1;
|
|
1028
|
+
if (this.staticIdlePollStreak >= requiredStreak) {
|
|
965
1029
|
this.engine.confirmPollStaticIdle('poll_static_idle');
|
|
1030
|
+
this.staticIdlePollStreak = 0;
|
|
966
1031
|
}
|
|
1032
|
+
} else {
|
|
1033
|
+
this.staticIdlePollStreak = 0;
|
|
967
1034
|
}
|
|
1035
|
+
} else {
|
|
1036
|
+
this.staticIdlePollStreak = 0;
|
|
968
1037
|
}
|
|
969
1038
|
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
970
1039
|
let effectiveModal = startupModal || this.engine.activeModal;
|
package/src/commands/handler.ts
CHANGED
|
@@ -1080,7 +1080,14 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
1080
1080
|
if (!fs.existsSync(installRoot)) return { success: true, providers: [] };
|
|
1081
1081
|
|
|
1082
1082
|
const CATEGORIES = ['cli', 'ide', 'extension', 'acp'] as const;
|
|
1083
|
-
const items: Array<{
|
|
1083
|
+
const items: Array<{
|
|
1084
|
+
type: string;
|
|
1085
|
+
category: string;
|
|
1086
|
+
version: string;
|
|
1087
|
+
path: string;
|
|
1088
|
+
modelOptions?: string[];
|
|
1089
|
+
thinkingLevelOptions?: string[];
|
|
1090
|
+
}> = [];
|
|
1084
1091
|
|
|
1085
1092
|
for (const category of CATEGORIES) {
|
|
1086
1093
|
const categoryDir = path.join(installRoot, category);
|
|
@@ -1095,11 +1102,24 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
1095
1102
|
if (!manifestPath) continue;
|
|
1096
1103
|
try {
|
|
1097
1104
|
const m = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
1105
|
+
// Surface the manifest's advisory model / thinking-level lists so
|
|
1106
|
+
// consumers of this endpoint (standalone New-session dialog, mesh
|
|
1107
|
+
// node slot editor) get the same provider-specific dropdowns the
|
|
1108
|
+
// status-snapshot path already carries — otherwise every provider
|
|
1109
|
+
// (codex included) falls back to a free-text Model field.
|
|
1110
|
+
const modelOptions = Array.isArray(m.modelOptions)
|
|
1111
|
+
? m.modelOptions.filter((x: unknown): x is string => typeof x === 'string' && !!x.trim())
|
|
1112
|
+
: [];
|
|
1113
|
+
const thinkingLevelOptions = Array.isArray(m.thinkingLevelOptions)
|
|
1114
|
+
? m.thinkingLevelOptions.filter((x: unknown): x is string => typeof x === 'string' && !!x.trim())
|
|
1115
|
+
: [];
|
|
1098
1116
|
items.push({
|
|
1099
1117
|
type,
|
|
1100
1118
|
category,
|
|
1101
1119
|
version: typeof m.providerVersion === 'string' ? m.providerVersion : '0.0.0',
|
|
1102
1120
|
path: manifestPath,
|
|
1121
|
+
...(modelOptions.length ? { modelOptions } : {}),
|
|
1122
|
+
...(thinkingLevelOptions.length ? { thinkingLevelOptions } : {}),
|
|
1103
1123
|
});
|
|
1104
1124
|
} catch {
|
|
1105
1125
|
// Corrupt manifest — skip but don't fail the whole listing.
|
|
@@ -48,6 +48,18 @@ export const meshCoordinatorLaunchHandlers: Record<string, HighFamilyHandler> =
|
|
|
48
48
|
const extraSystemPrompt = typeof args?.extraSystemPrompt === 'string'
|
|
49
49
|
? args.extraSystemPrompt.trim()
|
|
50
50
|
: '';
|
|
51
|
+
// Optional per-launch model / thinking-level override for the
|
|
52
|
+
// coordinator session. Passed straight through to launch_cli,
|
|
53
|
+
// which applies them best-effort for providers that support it
|
|
54
|
+
// (claude --effort, codex model_reasoning_effort). Blank / absent
|
|
55
|
+
// => provider default. This is a launch-time override only; it is
|
|
56
|
+
// NOT persisted to the mesh coordinator config.
|
|
57
|
+
const initialModel = typeof args?.initialModel === 'string' && args.initialModel.trim()
|
|
58
|
+
? args.initialModel.trim()
|
|
59
|
+
: null;
|
|
60
|
+
const initialThinkingLevel = typeof args?.initialThinkingLevel === 'string' && args.initialThinkingLevel.trim()
|
|
61
|
+
? args.initialThinkingLevel.trim()
|
|
62
|
+
: null;
|
|
51
63
|
if (!meshId) return { success: false, error: 'meshId required' };
|
|
52
64
|
|
|
53
65
|
try {
|
|
@@ -390,6 +402,8 @@ export const meshCoordinatorLaunchHandlers: Record<string, HighFamilyHandler> =
|
|
|
390
402
|
cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : undefined,
|
|
391
403
|
env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : undefined,
|
|
392
404
|
settings: { meshCoordinatorFor: meshId },
|
|
405
|
+
...(initialModel ? { initialModel } : {}),
|
|
406
|
+
...(initialThinkingLevel ? { initialThinkingLevel } : {}),
|
|
393
407
|
});
|
|
394
408
|
|
|
395
409
|
// R48 inject-then-remove. Spawn was just kicked off above; agy and
|
|
@@ -609,7 +623,9 @@ export const meshCoordinatorLaunchHandlers: Record<string, HighFamilyHandler> =
|
|
|
609
623
|
env: Object.keys(launchEnv).length > 0 ? launchEnv : undefined,
|
|
610
624
|
settings: {
|
|
611
625
|
meshCoordinatorFor: meshId
|
|
612
|
-
}
|
|
626
|
+
},
|
|
627
|
+
...(initialModel ? { initialModel } : {}),
|
|
628
|
+
...(initialThinkingLevel ? { initialThinkingLevel } : {}),
|
|
613
629
|
});
|
|
614
630
|
|
|
615
631
|
// R48 inject-then-remove. See the cli_command branch for context;
|
|
@@ -18,6 +18,7 @@ import { getGitRepoStatus } from '../../git/git-status.js';
|
|
|
18
18
|
import {
|
|
19
19
|
normalizeMeshNodeId,
|
|
20
20
|
daemonIdsEquivalent,
|
|
21
|
+
normalizeNodeCapabilitySlots,
|
|
21
22
|
} from '@adhdev/mesh-shared';
|
|
22
23
|
import {
|
|
23
24
|
getPendingMeshCoordinatorEvents,
|
|
@@ -357,6 +358,13 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
|
|
|
357
358
|
? { daemonBuildVersion: node.reportedDaemonBuildVersion }
|
|
358
359
|
: {}),
|
|
359
360
|
providerPriority,
|
|
361
|
+
// ORCHESTRATION_NODE_SLOTS.md: surface the node's capability
|
|
362
|
+
// slots so the dashboard slot editor can read them. Only
|
|
363
|
+
// emitted when explicitly configured (derived-from-legacy
|
|
364
|
+
// slots stay implicit — the editor shows the legacy fields).
|
|
365
|
+
...(Array.isArray((node.policy as any)?.slots) && (node.policy as any).slots.length
|
|
366
|
+
? { slots: normalizeNodeCapabilitySlots((node.policy as any).slots) }
|
|
367
|
+
: {}),
|
|
360
368
|
activeSessions: [],
|
|
361
369
|
activeSessionDetails: [],
|
|
362
370
|
launchReady: false,
|
|
@@ -64,6 +64,35 @@ export const cliAgentHandlers: Record<string, MedFamilyHandler> = {
|
|
|
64
64
|
return ctx.deps.cliManager.handleCliCommand('record_provider_pty', args);
|
|
65
65
|
},
|
|
66
66
|
|
|
67
|
+
// Daemon-owned per-session user Mute/Hide. Replaces the old browser-local
|
|
68
|
+
// localStorage layer: the user's manual hide/mute for a conversation is stored
|
|
69
|
+
// in-memory on the live session's settings (userHidden / userMuted) and rides
|
|
70
|
+
// the SAME status snapshot pipeline as the coordinator-policy surfaceHidden
|
|
71
|
+
// flag, so every client of this daemon sees the same state. In-memory only —
|
|
72
|
+
// resets on daemon restart (coordinator-spawned sessions re-derive their hidden
|
|
73
|
+
// default from mesh policy on relaunch). Passing null/undefined for a field
|
|
74
|
+
// leaves it unchanged; pass an explicit boolean to set, or false to clear an
|
|
75
|
+
// earlier hide/mute (e.g. unmute a coordinator-spawned worker overrides the
|
|
76
|
+
// policy default until restart).
|
|
77
|
+
set_conversation_prefs: async (ctx: MedFamilyContext, args: any) => {
|
|
78
|
+
const sessionId = readStringValue(args?.sessionId, (args as any)?.targetSessionId, (args as any)?.instanceId);
|
|
79
|
+
if (!sessionId) return { success: false, error: 'sessionId required' };
|
|
80
|
+
const inst = ctx.deps.instanceManager.getInstance(sessionId);
|
|
81
|
+
if (!inst || typeof inst.updateSettings !== 'function') {
|
|
82
|
+
return { success: false, error: 'Session not found or does not support preferences' };
|
|
83
|
+
}
|
|
84
|
+
const patch: Record<string, unknown> = {};
|
|
85
|
+
if (typeof args?.hidden === 'boolean') patch.userHidden = args.hidden;
|
|
86
|
+
if (typeof args?.muted === 'boolean') patch.userMuted = args.muted;
|
|
87
|
+
if (!Object.keys(patch).length) return { success: false, error: 'Nothing to update (hidden and/or muted required)' };
|
|
88
|
+
inst.updateSettings(patch);
|
|
89
|
+
// Push a fresh status snapshot so all clients see the updated
|
|
90
|
+
// surfaceHidden/muted immediately (cloud path). The standalone server has a
|
|
91
|
+
// parallel broadcast gate keyed on the command name (see daemon-standalone).
|
|
92
|
+
ctx.deps.onStatusChange?.();
|
|
93
|
+
return { success: true, sessionId, ...patch };
|
|
94
|
+
},
|
|
95
|
+
|
|
67
96
|
agent_command: async (ctx: MedFamilyContext, args: any) => {
|
|
68
97
|
// Relay-safety stamp: a dispatch carrying meshContext.coordinatorDaemonId
|
|
69
98
|
// (mesh_send_task / queue assignment over P2P) is the worker daemon's chance
|
package/src/index.ts
CHANGED
|
@@ -381,7 +381,7 @@ export type {
|
|
|
381
381
|
// ── Status ──
|
|
382
382
|
export { DaemonStatusReporter } from './status/reporter.js';
|
|
383
383
|
export { buildSessionEntries, findCdpManager, hasCdpManager, isCdpConnected } from './status/builders.js';
|
|
384
|
-
export { buildStatusSnapshot, buildMachineInfo, getLastDisplayMessage } from './status/snapshot.js';
|
|
384
|
+
export { buildStatusSnapshot, buildMachineInfo, buildAvailableProviders, getLastDisplayMessage } from './status/snapshot.js';
|
|
385
385
|
export { getDaemonBuildInfo } from './build-info.js';
|
|
386
386
|
export type { DaemonBuildInfo } from './build-info.js';
|
|
387
387
|
export { normalizeManagedStatus, isManagedStatusWorking, isManagedStatusWaiting, normalizeActiveChatData } from './status/normalize.js';
|
|
@@ -702,7 +702,9 @@ const TOOLS_SECTION = `## Available Tools
|
|
|
702
702
|
| \`mesh_magi_review\` | Cross-verify a read-only investigation across a standing panel of independent mesh agents (different machines/providers) instead of a single worker |
|
|
703
703
|
| \`mesh_magi_collect\` | Collect + synthesize a previously dispatched MAGI fan-out by its consensus group id (async companion to mesh_magi_review wait:false) |
|
|
704
704
|
| \`mesh_magi_kind_panel_set\` | Bind a task_kind → MAGI kind-panel slots (the SOLE MAGI panel-resolution surface; machine-local, wholesale replacement — approve current-vs-new first) |
|
|
705
|
-
| \`mesh_magi_kind_panel_list\` | List configured task_kind → MAGI kind-panel slot bindings (machine-local, read-only)
|
|
705
|
+
| \`mesh_magi_kind_panel_list\` | List configured task_kind → MAGI kind-panel slot bindings (machine-local, read-only) |
|
|
706
|
+
| \`mesh_node_slots_list\` | List a node's capability slots (its AI-tool profile: provider/model/thinking + difficulty range + capability tags), read-only |
|
|
707
|
+
| \`mesh_node_slots_set\` | PROPOSE (dry-run) or APPLY a node's capability slots — how you autonomously retune a node's tool profile; WHOLESALE replacement, present current-vs-proposed and get user approval before write=true |`;
|
|
706
708
|
|
|
707
709
|
const TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
708
710
|
|
|
@@ -773,6 +775,7 @@ function buildRulesSection(coordinatorCliType?: string): string {
|
|
|
773
775
|
- **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.
|
|
774
776
|
- **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
777
|
- **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.
|
|
778
|
+
- **Retune node profiles when routing is a poor fit — but only with approval.** A node's capability slots (its provider/model/thinking + difficulty range + capability tags, seen via \`mesh_node_slots_list\`) are what task→node fitness routing matches against. If you notice a persistent mismatch — e.g. every \`difficult\` task lands on a node whose only slot is a cheap model, or a capability a node clearly has isn't declared — you MAY propose a slot change with \`mesh_node_slots_set\` (write=false). That returns current-vs-proposed; present that diff to the user with a one-line reason and apply (write=true) ONLY after they approve. It is a WHOLESALE replacement of the node's slots, so include the slots you want to keep. Never rewrite a node's profile silently or without a clear routing reason.
|
|
776
779
|
- **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.
|
|
777
780
|
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
778
781
|
- **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).
|