@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
|
@@ -679,6 +679,19 @@ const ASSIGNED_STRANDED_DEADLINE_MS = 5 * 60_000;
|
|
|
679
679
|
// reclaimed out from under itself.
|
|
680
680
|
const DELIVERED_NO_TURN_DEADLINE_MS = 15 * 60_000;
|
|
681
681
|
|
|
682
|
+
// DELIVERED-NOT-CONSUMED (remote autoLaunch delivered≠consumed gap): how long a row may sit
|
|
683
|
+
// 'assigned' with a CONFIRMED delivery ('delivered') that was never CONSUMED ('acked' — the
|
|
684
|
+
// worker's agent:generating_started never arrived) before the watchdog re-drives it. Far shorter
|
|
685
|
+
// than DELIVERED_NO_TURN_DEADLINE_MS (15min): a remote autoLaunch marks markAutoLaunch(completed)
|
|
686
|
+
// and returns immediately, relying on agent:ready/reconcile to inject; if the launch→ready→claim
|
|
687
|
+
// window (widened on win32 by the 3–4s git spawn latency) drops the inject, the row sits 'assigned'
|
|
688
|
+
// but the delivery never flips past 'delivered' to 'acked'. The delivered-not-acked state is the
|
|
689
|
+
// cross-daemon consumption signal — positive evidence the worker never started the turn — so we can
|
|
690
|
+
// safely re-open the task after a SHORT grace (well above a normal generating_started round-trip so
|
|
691
|
+
// a merely-slow start is never torn off) instead of waiting the full 15min turn budget. Floored
|
|
692
|
+
// comfortably above the auto-launch cooldown so a legitimate late inject still has room to land.
|
|
693
|
+
const ASSIGNED_DELIVERED_UNCONSUMED_REDRIVE_MS = 25_000;
|
|
694
|
+
|
|
682
695
|
// RECLAIM-FALSEPOS: how many CONSECUTIVE UNKNOWN busy-verdict ticks (past the delivered-no-turn
|
|
683
696
|
// deadline) must accumulate before a delivered row whose worker session cannot be positively
|
|
684
697
|
// observed is reclaimed. An UNKNOWN verdict means the assigned session is not present in THIS
|
|
@@ -728,7 +741,62 @@ function recoverStrandedAssignedDispatches(components: DaemonComponents, meshId:
|
|
|
728
741
|
for (const row of assigned) {
|
|
729
742
|
const dispatchedAtMs = Date.parse(row.dispatchTimestamp ?? '');
|
|
730
743
|
if (!Number.isFinite(dispatchedAtMs)) continue; // no dispatch ts → can't age it
|
|
731
|
-
|
|
744
|
+
const ageMs = nowMs - dispatchedAtMs;
|
|
745
|
+
// DELIVERED-NOT-CONSUMED short-grace re-drive (remote autoLaunch delivered≠consumed gap).
|
|
746
|
+
// Runs BEFORE the ASSIGNED_STRANDED_DEADLINE_MS confirm-window gate below because its whole
|
|
747
|
+
// point is to recover a delivered-but-unconsumed row well inside that window. A remote
|
|
748
|
+
// autoLaunch marks the dispatch delivered (transport acked) but the worker may never emit
|
|
749
|
+
// agent:generating_started — the delivery then sits 'delivered' and never flips to 'acked',
|
|
750
|
+
// so the task is stranded 'assigned' with no live turn. This branch re-opens exactly that
|
|
751
|
+
// row after a short grace:
|
|
752
|
+
// - the delivery IS confirmed handed off (taskHasConfirmedDelivery) but was NEVER consumed
|
|
753
|
+
// (!taskDeliveryConsumed → no 'acked'/'completed' delivery) — the cross-daemon "worker
|
|
754
|
+
// never started the turn" signal, valid even for a REMOTE session whose local busy
|
|
755
|
+
// verdict is UNKNOWN;
|
|
756
|
+
// - AND the busy verdict is NOT GENERATING — a locally-present generating session IS
|
|
757
|
+
// consuming (ack lost/late), so never touch it (regression guard against tearing a live
|
|
758
|
+
// worker off its turn);
|
|
759
|
+
// - AND no terminal ledger evidence exists (the completion already landed → leave it).
|
|
760
|
+
// reclaimStrandedAssignedTask returns the row to 'pending' (bounded by MAX_STRANDED_RECLAIMS)
|
|
761
|
+
// so PHASE 3 re-dispatches it this same tick onto a fresh idle session — idempotent: it only
|
|
762
|
+
// mutates a still-'assigned' row, so a completion/ack that raced in already moved the row off
|
|
763
|
+
// 'assigned' and this is a no-op.
|
|
764
|
+
if (
|
|
765
|
+
ageMs >= ASSIGNED_DELIVERED_UNCONSUMED_REDRIVE_MS
|
|
766
|
+
&& ageMs < ASSIGNED_STRANDED_DEADLINE_MS
|
|
767
|
+
&& store.taskHasConfirmedDelivery(meshId, row.id)
|
|
768
|
+
&& !store.taskDeliveryConsumed(meshId, row.id)
|
|
769
|
+
) {
|
|
770
|
+
const terminal = findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id });
|
|
771
|
+
if (terminal) {
|
|
772
|
+
const status = terminal.kind === 'task_completed' ? 'completed' : 'failed';
|
|
773
|
+
updateTaskStatus(meshId, row.id, status);
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const verdict = row.assignedSessionId
|
|
777
|
+
? resolveSessionBusyVerdict(components, row.assignedSessionId)
|
|
778
|
+
: 'IDLE_CONFIRMED'; // no session bound → nothing live generating to protect
|
|
779
|
+
if (verdict !== 'GENERATING') {
|
|
780
|
+
const redriven = reclaimStrandedAssignedTask(meshId, row.id, {
|
|
781
|
+
reason: 'delivered_not_consumed_redrive',
|
|
782
|
+
ageMs,
|
|
783
|
+
});
|
|
784
|
+
if (redriven) {
|
|
785
|
+
LOG.warn('MeshReconcile', `Re-drove delivered-but-unconsumed task ${row.id} on mesh ${meshId} `
|
|
786
|
+
+ `(node=${row.assignedNodeId ?? '?'} session=${row.assignedSessionId ?? '?'}, delivered but no `
|
|
787
|
+
+ `generating_started in ${Math.round(ageMs / 1000)}s, verdict ${verdict} → ${redriven.status})`);
|
|
788
|
+
traceMeshEventDrop('assigned_delivered_not_consumed_redrive', {
|
|
789
|
+
taskId: row.id,
|
|
790
|
+
sessionId: row.assignedSessionId,
|
|
791
|
+
nodeId: row.assignedNodeId,
|
|
792
|
+
meshId,
|
|
793
|
+
event: 'agent:generating_started',
|
|
794
|
+
}, `delivered_not_consumed ${Math.round(ageMs / 1000)}s → ${redriven.status}`);
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (ageMs < ASSIGNED_STRANDED_DEADLINE_MS) continue; // still in confirm window
|
|
732
800
|
const terminal = findTerminalLedgerEvidenceForTask({
|
|
733
801
|
meshId,
|
|
734
802
|
taskId: row.id,
|
|
@@ -1488,6 +1488,28 @@ export class MeshRuntimeStore {
|
|
|
1488
1488
|
return !!row;
|
|
1489
1489
|
}
|
|
1490
1490
|
|
|
1491
|
+
/**
|
|
1492
|
+
* DELIVERED-NOT-CONSUMED re-drive support: true when at least one delivery record for
|
|
1493
|
+
* the task has reached a CONSUMED status ('acked' / 'completed'). Distinct from
|
|
1494
|
+
* {@link taskHasConfirmedDelivery} ('delivered' | 'acked' | 'completed'): a delivery is
|
|
1495
|
+
* flipped to 'delivered' the instant the transport hands the dispatch off, but only
|
|
1496
|
+
* flipped to 'acked' when the worker's agent:generating_started event arrives (see the
|
|
1497
|
+
* generating_started handler in mesh-event-forwarding) — i.e. when the session has
|
|
1498
|
+
* actually begun the turn. That distinction is the cross-daemon consumption signal the
|
|
1499
|
+
* short-grace re-drive uses: a row whose delivery is 'delivered' but never 'acked' was
|
|
1500
|
+
* handed to a REMOTE worker that never started generating — the remote autoLaunch
|
|
1501
|
+
* delivered≠consumed gap — even when the session's busy verdict is UNKNOWN (not locally
|
|
1502
|
+
* observable). Indexed by (mesh_id, task_id).
|
|
1503
|
+
*/
|
|
1504
|
+
taskDeliveryConsumed(meshId: string, taskId: string): boolean {
|
|
1505
|
+
const row = this.db.prepare(`
|
|
1506
|
+
SELECT 1 FROM mesh_session_delivery
|
|
1507
|
+
WHERE mesh_id = ? AND task_id = ? AND status IN ('acked','completed')
|
|
1508
|
+
LIMIT 1
|
|
1509
|
+
`).get(meshId, taskId) as { 1: number } | undefined;
|
|
1510
|
+
return !!row;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1491
1513
|
expireStaleSessionDeliveries(meshId: string): void {
|
|
1492
1514
|
const now = new Date().toISOString();
|
|
1493
1515
|
this.db.prepare(`
|
|
@@ -3,13 +3,13 @@ import { requireMeshHostQueueOwner } from './mesh-host-ownership.js';
|
|
|
3
3
|
import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
|
|
4
4
|
import { MESH_CONVERGE_REFINE_TAG, resolveAutoConvergeCodeChange } from '../repo-mesh-types.js';
|
|
5
5
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
6
|
-
import { getMesh } from '../config/mesh-config.js';
|
|
6
|
+
import { getMesh, getDifficultyBrains } from '../config/mesh-config.js';
|
|
7
7
|
import { LOG } from '../logging/logger.js';
|
|
8
8
|
import { appendLedgerEntry } from './mesh-ledger.js';
|
|
9
9
|
import type { MeshLedgerKind } from './mesh-ledger.js';
|
|
10
10
|
import { createSessionDelivery } from './mesh-delivery-policy.js';
|
|
11
11
|
import { isTaskDispatchInFlight, endTaskDispatchInFlight } from './mesh-task-inflight.js';
|
|
12
|
-
import { sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
12
|
+
import { sessionIdsEquivalent, isMeshTaskDifficulty, type MeshTaskDifficulty } from '@adhdev/mesh-shared';
|
|
13
13
|
|
|
14
14
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
15
15
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
@@ -584,6 +584,13 @@ export interface MeshWorkQueueEntry {
|
|
|
584
584
|
* that cannot honor the model still runs the task (never a fatal launch error).
|
|
585
585
|
*/
|
|
586
586
|
model?: string;
|
|
587
|
+
/**
|
|
588
|
+
* BRAIN-ROUTING (thinking axis): standard reasoning level ('low'|'medium'|'high')
|
|
589
|
+
* for the session that executes this task. When the task auto-launches, this is
|
|
590
|
+
* passed to launch_cli as `initialThinkingLevel` (CLI → thinkingLaunchArgs; ACP →
|
|
591
|
+
* setConfigOption('thought_level')). Rides in payload JSON. Best-effort like model.
|
|
592
|
+
*/
|
|
593
|
+
thinkingLevel?: string;
|
|
587
594
|
/**
|
|
588
595
|
* M1: why this task is held back (e.g. "dependency_failed:<taskId>").
|
|
589
596
|
* Only set by the system on dependency failure under the 'block' policy;
|
|
@@ -862,6 +869,16 @@ export function enqueueTask(
|
|
|
862
869
|
consensusGroupId?: string;
|
|
863
870
|
/** MAGI-KIND-PANEL: model override forwarded to the executing session's launch (initialModel). */
|
|
864
871
|
model?: string;
|
|
872
|
+
/** BRAIN-ROUTING: standard thinking level forwarded to launch (initialThinkingLevel). */
|
|
873
|
+
thinkingLevel?: string;
|
|
874
|
+
/**
|
|
875
|
+
* BRAIN-ROUTING: task execution difficulty ('easy'|'medium'|'difficult'|
|
|
876
|
+
* 'freeform'). When set, the mesh's difficulty→brain preset fills in model /
|
|
877
|
+
* thinkingLevel that were not passed explicitly (an explicit model/thinkingLevel
|
|
878
|
+
* wins). Purely a convenience resolver — the stored task still carries the
|
|
879
|
+
* resolved model/thinkingLevel, so downstream launch is unchanged.
|
|
880
|
+
*/
|
|
881
|
+
difficulty?: string;
|
|
865
882
|
/** Explicit task id for batch/template flows (M5). Random UUID when omitted. */
|
|
866
883
|
id?: string;
|
|
867
884
|
/** (3) Originating coordinator session id (for session-anchored completion routing). */
|
|
@@ -881,6 +898,21 @@ export function enqueueTask(
|
|
|
881
898
|
const maxRetries = typeof opts?.maxRetries === 'number' && Number.isFinite(opts.maxRetries) && opts.maxRetries >= 0
|
|
882
899
|
? Math.floor(opts.maxRetries)
|
|
883
900
|
: undefined;
|
|
901
|
+
// BRAIN-ROUTING: resolve the difficulty preset into effective model / thinking
|
|
902
|
+
// level. An explicit opts.model / opts.thinkingLevel always wins; the preset only
|
|
903
|
+
// fills what the caller left blank. Best-effort — a missing/invalid difficulty or
|
|
904
|
+
// an unconfigured preset just leaves the explicit values (or none) in place.
|
|
905
|
+
let effectiveModel = typeof opts?.model === 'string' && opts.model.trim() ? opts.model.trim() : undefined;
|
|
906
|
+
let effectiveThinkingLevel = typeof opts?.thinkingLevel === 'string' && opts.thinkingLevel.trim() ? opts.thinkingLevel.trim() : undefined;
|
|
907
|
+
if (isMeshTaskDifficulty(opts?.difficulty)) {
|
|
908
|
+
try {
|
|
909
|
+
const preset = getDifficultyBrains()[opts!.difficulty as MeshTaskDifficulty];
|
|
910
|
+
if (preset) {
|
|
911
|
+
if (!effectiveModel && preset.model) effectiveModel = preset.model;
|
|
912
|
+
if (!effectiveThinkingLevel && preset.thinkingLevel) effectiveThinkingLevel = preset.thinkingLevel;
|
|
913
|
+
}
|
|
914
|
+
} catch { /* preset read is best-effort — never block enqueue */ }
|
|
915
|
+
}
|
|
884
916
|
const result = withQueueLock(meshId, () => {
|
|
885
917
|
if (MeshRuntimeStore.getInstance().findQueueEntryById(meshId, id)) {
|
|
886
918
|
throw new Error(`duplicate_task_id: task '${id}' already exists in mesh '${meshId}'`);
|
|
@@ -917,7 +949,8 @@ export function enqueueTask(
|
|
|
917
949
|
...(maxRetries !== undefined ? { maxRetries } : {}),
|
|
918
950
|
...(typeof opts?.missionId === 'string' && opts.missionId.trim() ? { missionId: opts.missionId.trim() } : {}),
|
|
919
951
|
...(typeof opts?.consensusGroupId === 'string' && opts.consensusGroupId.trim() ? { consensusGroupId: opts.consensusGroupId.trim() } : {}),
|
|
920
|
-
...(
|
|
952
|
+
...(effectiveModel ? { model: effectiveModel } : {}),
|
|
953
|
+
...(effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {}),
|
|
921
954
|
...(typeof opts?.sourceCoordinatorSessionId === 'string' && opts.sourceCoordinatorSessionId.trim()
|
|
922
955
|
? { sourceCoordinatorSessionId: opts.sourceCoordinatorSessionId.trim() }
|
|
923
956
|
: {}),
|
|
@@ -312,6 +312,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
312
312
|
private presentationMode: 'terminal' | 'chat';
|
|
313
313
|
private providerSessionId?: string;
|
|
314
314
|
private launchMode: 'new' | 'resume' | 'manual';
|
|
315
|
+
private initialThinkingLevel?: string;
|
|
315
316
|
private readonly startedAt = Date.now();
|
|
316
317
|
private onProviderSessionResolved?: (info: {
|
|
317
318
|
instanceId: string;
|
|
@@ -332,6 +333,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
332
333
|
providerSessionId?: string;
|
|
333
334
|
launchMode?: 'new' | 'resume' | 'manual';
|
|
334
335
|
extraEnv?: Record<string, string>;
|
|
336
|
+
/** BRAIN-ROUTING: standard thinking level to apply post-launch via the
|
|
337
|
+
* provider's thinkingControlId (runtime-control providers like hermes).
|
|
338
|
+
* Providers using thinkingLaunchArgs get it at spawn instead and ignore this. */
|
|
339
|
+
initialThinkingLevel?: string;
|
|
335
340
|
onProviderSessionResolved?: (info: {
|
|
336
341
|
instanceId: string;
|
|
337
342
|
providerType: string;
|
|
@@ -347,6 +352,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
347
352
|
this.presentationMode = 'chat';
|
|
348
353
|
this.providerSessionId = options?.providerSessionId;
|
|
349
354
|
this.launchMode = options?.launchMode || 'new';
|
|
355
|
+
this.initialThinkingLevel = options?.initialThinkingLevel;
|
|
350
356
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
351
357
|
this.adapter = createCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory) as ProviderCliAdapter;
|
|
352
358
|
if (this.providerSessionId) {
|
|
@@ -392,6 +398,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
392
398
|
// PTY spawn
|
|
393
399
|
await this.adapter.spawn();
|
|
394
400
|
await this.enforceFreshSessionLaunchIfNeeded();
|
|
401
|
+
await this.applyInitialThinkingLevelViaControl();
|
|
395
402
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
396
403
|
if (this.providerSessionId && this.shouldHydrateExistingProviderHistory()) {
|
|
397
404
|
this.restorePersistedHistoryFromCurrentSession();
|
|
@@ -1209,6 +1216,45 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1209
1216
|
this.applyProviderResponse(parsed.payload, { phase: 'immediate' });
|
|
1210
1217
|
}
|
|
1211
1218
|
|
|
1219
|
+
/**
|
|
1220
|
+
* BRAIN-ROUTING (runtime-control thinking axis): for a provider that selects
|
|
1221
|
+
* reasoning effort via a runtime control instead of a launch arg (e.g. hermes
|
|
1222
|
+
* `reasoning`), apply the requested initialThinkingLevel after spawn by invoking
|
|
1223
|
+
* that control's setScript. The provider names the control via thinkingControlId.
|
|
1224
|
+
* The standard level is mapped through thinkingLevelMap first (same as the
|
|
1225
|
+
* launch-arg path). Best-effort: any failure logs and never blocks launch.
|
|
1226
|
+
*/
|
|
1227
|
+
private async applyInitialThinkingLevelViaControl(): Promise<void> {
|
|
1228
|
+
const level = typeof this.initialThinkingLevel === 'string' ? this.initialThinkingLevel.trim() : '';
|
|
1229
|
+
if (!level) return;
|
|
1230
|
+
const controlId = (this.provider as any).thinkingControlId;
|
|
1231
|
+
if (!controlId) return; // provider uses thinkingLaunchArgs (or has no support)
|
|
1232
|
+
const controls: any[] = Array.isArray((this.provider as any).controls) ? (this.provider as any).controls : [];
|
|
1233
|
+
const control = controls.find(c => c && c.id === controlId);
|
|
1234
|
+
if (!control || !control.setScript) return;
|
|
1235
|
+
// Map the standard level to the provider's own vocabulary (unchanged if absent).
|
|
1236
|
+
const map = (this.provider as any).thinkingLevelMap as Record<string, string> | undefined;
|
|
1237
|
+
const mapped = (map && typeof map[level] === 'string' && map[level].trim()) ? map[level].trim() : level;
|
|
1238
|
+
try {
|
|
1239
|
+
await waitForCliAdapterReady(this.adapter);
|
|
1240
|
+
const raw = await this.adapter.invokeScript(control.setScript, { value: mapped });
|
|
1241
|
+
const parsed = parseCliScriptResult(raw);
|
|
1242
|
+
if (!parsed.success) {
|
|
1243
|
+
LOG.warn('CLI', `[${this.type}] thinking control '${controlId}' set to '${mapped}' failed: ${parsed.payload?.error || 'unknown'}`);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const cliCommand = getCliScriptCommand(parsed.payload);
|
|
1247
|
+
if (cliCommand?.type === 'send_message' && cliCommand.text) {
|
|
1248
|
+
await this.adapter.sendMessage(cliCommand.text);
|
|
1249
|
+
} else if (cliCommand?.type === 'pty_write' && cliCommand.text) {
|
|
1250
|
+
await this.adapter.writeRaw(cliCommand.text + '\r');
|
|
1251
|
+
}
|
|
1252
|
+
LOG.info('CLI', `[${this.type}] applied thinking level '${mapped}' via control '${controlId}'`);
|
|
1253
|
+
} catch (e: any) {
|
|
1254
|
+
LOG.warn('CLI', `[${this.type}] thinking control apply threw: ${e?.message || e}`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1212
1258
|
private completionHasFinalAssistantMessage(messages: unknown, turnStartedAt?: number): boolean {
|
|
1213
1259
|
const visibleMessages = (Array.isArray(messages) ? messages : [])
|
|
1214
1260
|
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
@@ -604,6 +604,53 @@ export interface ProviderModule {
|
|
|
604
604
|
* request never fails a launch). Absent → no launch-time model selection for CLI.
|
|
605
605
|
*/
|
|
606
606
|
modelLaunchArgs?: string[];
|
|
607
|
+
/**
|
|
608
|
+
* BRAIN-ROUTING (model axis): suggested model values for this provider, surfaced
|
|
609
|
+
* as dropdown options in the new-session dialog (e.g. claude ['opus','sonnet',
|
|
610
|
+
* 'haiku']; codex ['gpt-5.5','gpt-5-codex']). Advisory only — the UI allows free
|
|
611
|
+
* text too, so the list going stale never blocks a model the provider accepts.
|
|
612
|
+
*/
|
|
613
|
+
modelOptions?: string[];
|
|
614
|
+
/**
|
|
615
|
+
* BRAIN-ROUTING (thinking axis): template for expanding an `initialThinkingLevel`
|
|
616
|
+
* selection into launch args for a CLI provider, parallel to modelLaunchArgs.
|
|
617
|
+
* `{{level}}` is substituted with the provider-appropriate reasoning-effort value
|
|
618
|
+
* (already mapped from the standard low|medium|high level, see thinkingLevelMap).
|
|
619
|
+
* Examples: claude-cli `['--effort', '{{level}}']` → `--effort high`; codex-cli
|
|
620
|
+
* `['-c', 'model_reasoning_effort={{level}}']`. Applied at session launch when
|
|
621
|
+
* `initialThinkingLevel` is passed AND this provider is a plain CLI. A CLI provider
|
|
622
|
+
* with no template silently ignores the thinking level (best-effort; never fails a
|
|
623
|
+
* launch). ACP providers instead route thinking through setConfigOption('thought_level').
|
|
624
|
+
*/
|
|
625
|
+
thinkingLaunchArgs?: string[];
|
|
626
|
+
/**
|
|
627
|
+
* BRAIN-ROUTING (thinking axis): optional per-provider mapping from the standard
|
|
628
|
+
* thinking levels (`low`|`medium`|`high`) to this provider's own reasoning-effort
|
|
629
|
+
* vocabulary, used to fill `{{level}}` in thinkingLaunchArgs. e.g. claude-cli might
|
|
630
|
+
* map `{ high: 'max' }`; codex-cli `{ high: 'xhigh' }`. A level absent from the map
|
|
631
|
+
* passes through unchanged (so `medium` → `medium` by default).
|
|
632
|
+
*/
|
|
633
|
+
thinkingLevelMap?: Partial<Record<'low' | 'medium' | 'high', string>>;
|
|
634
|
+
/**
|
|
635
|
+
* BRAIN-ROUTING (thinking axis): the reasoning-effort values this provider actually
|
|
636
|
+
* accepts, surfaced as the thinking-level dropdown options in the new-session
|
|
637
|
+
* dialog (e.g. claude ['low','medium','high','max']; codex ['minimal','low',
|
|
638
|
+
* 'medium','high','xhigh']). Absent → the UI falls back to the standard
|
|
639
|
+
* low/medium/high. These are the provider's OWN vocabulary and are passed through
|
|
640
|
+
* verbatim as initialThinkingLevel (not remapped by thinkingLevelMap, which only
|
|
641
|
+
* translates the mesh's standard low/medium/high presets).
|
|
642
|
+
*/
|
|
643
|
+
thinkingLevelOptions?: string[];
|
|
644
|
+
/**
|
|
645
|
+
* BRAIN-ROUTING (thinking axis, runtime-control providers): the `controls[].id`
|
|
646
|
+
* of a runtime reasoning-effort control to drive for the thinking level when the
|
|
647
|
+
* provider has no `thinkingLaunchArgs` (e.g. hermes-cli's `reasoning` select,
|
|
648
|
+
* which types `/reasoning <level>` into the PTY via its setScript). At launch,
|
|
649
|
+
* initialThinkingLevel (after thinkingLevelMap) is applied by invoking that
|
|
650
|
+
* control's setScript with `{ value: <level> }`. Ignored if the id doesn't match a
|
|
651
|
+
* control. Providers that use thinkingLaunchArgs don't need this.
|
|
652
|
+
*/
|
|
653
|
+
thinkingControlId?: string;
|
|
607
654
|
/** Delay before submitting typed CLI input (provider-specific TUI tuning) */
|
|
608
655
|
sendDelayMs?: number;
|
|
609
656
|
/** Submit key used after typing into CLI PTY (default: carriage return) */
|
|
@@ -66,6 +66,12 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
|
|
|
66
66
|
'providerVersion',
|
|
67
67
|
'status',
|
|
68
68
|
'details',
|
|
69
|
+
'modelLaunchArgs',
|
|
70
|
+
'modelOptions',
|
|
71
|
+
'thinkingLaunchArgs',
|
|
72
|
+
'thinkingLevelMap',
|
|
73
|
+
'thinkingLevelOptions',
|
|
74
|
+
'thinkingControlId',
|
|
69
75
|
'sendDelayMs',
|
|
70
76
|
'sendKey',
|
|
71
77
|
'submitStrategy',
|
|
@@ -129,6 +129,35 @@
|
|
|
129
129
|
"items": { "type": "string" },
|
|
130
130
|
"description": "Template for expanding an initialModel selection into launch args. '{{model}}' is substituted with the model string (e.g. ['--model', '{{model}}'] → --model opus). Applied at launch when a model is requested for this CLI provider (MAGI kind-panel model axis). Absent → no launch-time model selection."
|
|
131
131
|
},
|
|
132
|
+
"modelOptions": {
|
|
133
|
+
"type": "array",
|
|
134
|
+
"items": { "type": "string" },
|
|
135
|
+
"description": "Suggested model values shown as dropdown options in the new-session dialog (brain-routing model axis), e.g. ['opus','sonnet','haiku']. Advisory — the UI still accepts free text, so a stale list never blocks an accepted model."
|
|
136
|
+
},
|
|
137
|
+
"thinkingLaunchArgs": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"items": { "type": "string" },
|
|
140
|
+
"description": "Template for expanding an initialThinkingLevel selection into launch args (brain-routing thinking axis, parallel to modelLaunchArgs). '{{level}}' is substituted with the provider-mapped reasoning-effort value (e.g. ['--effort', '{{level}}'] → --effort high; ['-c', 'model_reasoning_effort={{level}}']). Applied at launch when a thinking level is requested. Absent → no launch-time thinking selection."
|
|
141
|
+
},
|
|
142
|
+
"thinkingLevelMap": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"properties": {
|
|
145
|
+
"low": { "type": "string" },
|
|
146
|
+
"medium": { "type": "string" },
|
|
147
|
+
"high": { "type": "string" }
|
|
148
|
+
},
|
|
149
|
+
"additionalProperties": false,
|
|
150
|
+
"description": "Optional map from the standard thinking levels (low/medium/high) to this provider's own reasoning-effort vocabulary, used to fill {{level}} in thinkingLaunchArgs. A level absent from the map passes through unchanged."
|
|
151
|
+
},
|
|
152
|
+
"thinkingLevelOptions": {
|
|
153
|
+
"type": "array",
|
|
154
|
+
"items": { "type": "string" },
|
|
155
|
+
"description": "Reasoning-effort values this provider accepts, shown as the thinking-level dropdown in the new-session dialog (e.g. ['low','medium','high','max']). Absent → the UI falls back to standard low/medium/high. Provider's own vocabulary, passed through verbatim."
|
|
156
|
+
},
|
|
157
|
+
"thinkingControlId": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"description": "For a provider with no thinkingLaunchArgs but a runtime reasoning-effort control (e.g. hermes 'reasoning'), the controls[].id to drive at launch for the thinking level. The control's setScript is invoked with { value: <mapped level> }."
|
|
160
|
+
},
|
|
132
161
|
"scriptCallBudgetMs": {
|
|
133
162
|
"type": "integer",
|
|
134
163
|
"minimum": 1,
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -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 } from '@adhdev/mesh-shared';
|
|
17
|
+
import type { MagiKindPanelMap, DifficultyBrainMap } from '@adhdev/mesh-shared';
|
|
18
18
|
|
|
19
19
|
// ─── Core Mesh Types ────────────────────────────
|
|
20
20
|
|
|
@@ -822,6 +822,15 @@ export interface LocalMeshConfig {
|
|
|
822
822
|
* Optional; absent on pre-feature configs.
|
|
823
823
|
*/
|
|
824
824
|
magiKindPanels?: MagiKindPanelMap;
|
|
825
|
+
/**
|
|
826
|
+
* BRAIN-ROUTING: per-task-difficulty brain presets (machine-local), sibling of
|
|
827
|
+
* magiKindPanels. Keyed by difficulty (easy / medium / difficult / freeform);
|
|
828
|
+
* each maps to a BrainSlot (provider? / model? / thinkingLevel?). The coordinator
|
|
829
|
+
* classifies a task's difficulty at enqueue; the matching preset fills in the
|
|
830
|
+
* task's model / thinking level (an explicit task value wins). Optional; a mesh
|
|
831
|
+
* with none seeded uses DEFAULT_DIFFICULTY_BRAINS on first read.
|
|
832
|
+
*/
|
|
833
|
+
difficultyBrains?: DifficultyBrainMap;
|
|
825
834
|
}
|
|
826
835
|
|
|
827
836
|
export interface LocalMeshEntry {
|
package/src/shared-types.ts
CHANGED
|
@@ -555,6 +555,10 @@ export interface AvailableProviderInfo {
|
|
|
555
555
|
lastVerification?: MachineProviderCheckResult;
|
|
556
556
|
/** Provider-declared Repo Mesh coordinator/MCP behavior. */
|
|
557
557
|
meshCoordinator?: ProviderMeshCoordinatorConfig;
|
|
558
|
+
/** BRAIN-ROUTING: suggested model values for the new-session model dropdown. */
|
|
559
|
+
modelOptions?: string[];
|
|
560
|
+
/** BRAIN-ROUTING: reasoning-effort values for the new-session thinking dropdown. */
|
|
561
|
+
thinkingLevelOptions?: string[];
|
|
558
562
|
/**
|
|
559
563
|
* Provider trust classification — derived from the on-disk layer the
|
|
560
564
|
* manifest came from and the shape of the manifest. Dashboards use
|
package/src/status/snapshot.ts
CHANGED
|
@@ -153,6 +153,8 @@ function buildAvailableProviders(
|
|
|
153
153
|
status?: string;
|
|
154
154
|
details?: string;
|
|
155
155
|
links?: Record<string, string>;
|
|
156
|
+
modelOptions?: string[];
|
|
157
|
+
thinkingLevelOptions?: string[];
|
|
156
158
|
}> = providerLoader.getAvailableProviderInfos?.() || providerLoader.getAll();
|
|
157
159
|
// Trust helpers come from daemon-core; resolve them lazily so the
|
|
158
160
|
// status snapshot path stays loadable in older bundles that don't
|
|
@@ -189,6 +191,8 @@ function buildAvailableProviders(
|
|
|
189
191
|
...(sourceLayer ? { sourceLayer } : {}),
|
|
190
192
|
...(sourceName ? { sourceName } : {}),
|
|
191
193
|
...(provider.providerVersion ? { providerVersion: provider.providerVersion } : {}),
|
|
194
|
+
...(Array.isArray(provider.modelOptions) && provider.modelOptions.length ? { modelOptions: provider.modelOptions } : {}),
|
|
195
|
+
...(Array.isArray(provider.thinkingLevelOptions) && provider.thinkingLevelOptions.length ? { thinkingLevelOptions: provider.thinkingLevelOptions } : {}),
|
|
192
196
|
...(provider.binary ? { binary: provider.binary } : {}),
|
|
193
197
|
...(provider.status ? { status: provider.status } : {}),
|
|
194
198
|
...(provider.details ? { details: provider.details } : {}),
|