@adhdev/daemon-core 0.9.82-rc.437 → 0.9.82-rc.439
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/config/mesh-config.d.ts +21 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +363 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +358 -38
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-init.d.ts +44 -2
- package/dist/mesh/mesh-work-queue.d.ts +10 -0
- package/dist/providers/contracts.d.ts +10 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +1 -1
- package/dist/repo-mesh-types.d.ts +9 -1
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +28 -1
- package/src/commands/med-family/mesh-crud.ts +131 -0
- package/src/config/chat-history.ts +10 -0
- package/src/config/mesh-config.ts +99 -1
- package/src/index.ts +2 -0
- package/src/mesh/coordinator-prompt.ts +34 -1
- package/src/mesh/mesh-init.ts +95 -5
- package/src/mesh/mesh-queue-assignment.ts +6 -0
- package/src/mesh/mesh-work-queue.ts +11 -0
- package/src/providers/cli-provider-instance.ts +24 -2
- package/src/providers/contracts.ts +10 -0
- package/src/providers/native-history/dispatcher.ts +8 -2
- package/src/providers/native-history/hermes-cli-transcript.ts +27 -11
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +5 -0
- package/src/providers/spec/native-history-executor.ts +41 -26
- package/src/repo-mesh-types.ts +9 -1
package/dist/mesh/mesh-init.d.ts
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* One-shot helper that onboards an existing git project into Repo Mesh:
|
|
5
5
|
* 1. suggest + write `.adhdev/refine.json` (Refinery validation config)
|
|
6
6
|
* 2. suggest + write `.adhdev/worktree_bootstrap.json` (worktree bootstrap config)
|
|
7
|
-
* 3.
|
|
7
|
+
* 3. suggest + write `.adhdev/change-impact.json` (change-impact classification)
|
|
8
|
+
* 4. recommend a node providerPriority from the installed CLI providers
|
|
9
|
+
*
|
|
10
|
+
* mesh_init now covers all THREE repo-committed `.adhdev/*` config families that
|
|
11
|
+
* carry a suggest→validate→write engine (refine + worktree_bootstrap +
|
|
12
|
+
* change-impact). `.adhdev/mesh.json` (coordinator prompt + operating notes) has
|
|
13
|
+
* its own dedicated export/write path (export_mesh_json_config / write_mesh_json_config).
|
|
8
14
|
*
|
|
9
15
|
* Design contract (matches the rest of the mesh tooling):
|
|
10
16
|
* - Heuristics are suggestion/scaffold only. The written files are the
|
|
@@ -19,17 +25,20 @@
|
|
|
19
25
|
*/
|
|
20
26
|
import { type RepoMeshRefineConfig, type RepoMeshRefineValidationCommandConfig } from './refine-config.js';
|
|
21
27
|
import { type RepoMeshWorktreeBootstrapConfig } from './worktree-bootstrap-config.js';
|
|
28
|
+
import { type ChangeImpactConfig } from '../git/change-impact-config.js';
|
|
29
|
+
import type { MagiKindPanelMap } from '@adhdev/mesh-shared';
|
|
22
30
|
import type { CLIInfo } from '../detection/cli-detector.js';
|
|
23
31
|
/** Canonical write targets — the first/preferred location for each config family. */
|
|
24
32
|
export declare const MESH_INIT_REFINE_CONFIG_PATH: string;
|
|
25
33
|
export declare const MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH: string;
|
|
34
|
+
export declare const MESH_INIT_CHANGE_IMPACT_CONFIG_PATH: string;
|
|
26
35
|
export interface MeshInitConfigFileResult {
|
|
27
36
|
path: string;
|
|
28
37
|
relativePath: string;
|
|
29
38
|
written: boolean;
|
|
30
39
|
/** Why it was not written (already_exists / not_suggested / no change). */
|
|
31
40
|
skippedReason?: 'already_exists' | 'no_suggestion';
|
|
32
|
-
config?: RepoMeshRefineConfig | RepoMeshWorktreeBootstrapConfig;
|
|
41
|
+
config?: RepoMeshRefineConfig | RepoMeshWorktreeBootstrapConfig | ChangeImpactConfig;
|
|
33
42
|
}
|
|
34
43
|
/**
|
|
35
44
|
* Build a worktree_bootstrap scaffold from package scripts. Mirrors
|
|
@@ -58,12 +67,39 @@ export interface RunMeshInitOptions {
|
|
|
58
67
|
/** When true, overwrite an existing config file. Defaults to false (never clobber). */
|
|
59
68
|
overwrite?: boolean;
|
|
60
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* A snapshot of the currently-saved config for each domain, so the coordinator can
|
|
72
|
+
* render a current-vs-suggested diff to the user before an init/reinit overwrite.
|
|
73
|
+
* This is READ-ONLY echo — every field is what is on disk / machine-local right now,
|
|
74
|
+
* never a suggestion. Absent domains are reported as `undefined` config.
|
|
75
|
+
*/
|
|
76
|
+
export interface MeshInitCurrentConfigEcho {
|
|
77
|
+
/** Currently-saved `.adhdev/refine.json` (repo-committed) — undefined when absent/invalid. */
|
|
78
|
+
refine?: RepoMeshRefineConfig;
|
|
79
|
+
/** Currently-saved `.adhdev/worktree_bootstrap.json` (repo-committed). */
|
|
80
|
+
worktreeBootstrap?: RepoMeshWorktreeBootstrapConfig;
|
|
81
|
+
/** Currently-saved `.adhdev/change-impact.json` (repo-committed). */
|
|
82
|
+
changeImpact?: ChangeImpactConfig;
|
|
83
|
+
/** Per-domain source type so the coordinator can tell "absent" from "invalid". */
|
|
84
|
+
sourceTypes: {
|
|
85
|
+
refine: string;
|
|
86
|
+
worktreeBootstrap: string;
|
|
87
|
+
changeImpact: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Currently-configured MAGI kind→panel slot bindings (machine-local
|
|
91
|
+
* ~/.adhdev/meshes.json). Empty object when none configured. This is a
|
|
92
|
+
* machine-local echo — the coordinator labels it as such vs. the repo files.
|
|
93
|
+
*/
|
|
94
|
+
magiKindPanels: MagiKindPanelMap;
|
|
95
|
+
}
|
|
61
96
|
export interface RunMeshInitResult {
|
|
62
97
|
success: true;
|
|
63
98
|
workspace: string;
|
|
64
99
|
dryRun: boolean;
|
|
65
100
|
refine: MeshInitConfigFileResult;
|
|
66
101
|
worktreeBootstrap: MeshInitConfigFileResult;
|
|
102
|
+
changeImpact: MeshInitConfigFileResult;
|
|
67
103
|
providers: {
|
|
68
104
|
providerPriority: string[];
|
|
69
105
|
installedProviders: Array<{
|
|
@@ -72,6 +108,12 @@ export interface RunMeshInitResult {
|
|
|
72
108
|
version?: string;
|
|
73
109
|
}>;
|
|
74
110
|
};
|
|
111
|
+
/**
|
|
112
|
+
* Read-only snapshot of the currently-saved config per domain (repo files +
|
|
113
|
+
* machine-local kind panels). Lets the coordinator present a current-vs-suggested
|
|
114
|
+
* diff to the user before any overwrite. Always populated (dry-run and write).
|
|
115
|
+
*/
|
|
116
|
+
currentConfig: MeshInitCurrentConfigEcho;
|
|
75
117
|
note: string;
|
|
76
118
|
}
|
|
77
119
|
/**
|
|
@@ -72,6 +72,14 @@ export interface MeshWorkQueueEntry {
|
|
|
72
72
|
* replicas. Absent on ordinary tasks. Rides in the payload JSON (no column).
|
|
73
73
|
*/
|
|
74
74
|
consensusGroupId?: string;
|
|
75
|
+
/**
|
|
76
|
+
* MAGI-KIND-PANEL (model axis): model override for the session that executes this
|
|
77
|
+
* task. When the task auto-launches a session, this is passed to launch_cli as
|
|
78
|
+
* `initialModel` (ACP → setConfigOption; CLI → modelLaunchArgs template). Absent on
|
|
79
|
+
* ordinary tasks. Rides in the payload JSON (no column). Best-effort — a provider
|
|
80
|
+
* that cannot honor the model still runs the task (never a fatal launch error).
|
|
81
|
+
*/
|
|
82
|
+
model?: string;
|
|
75
83
|
/**
|
|
76
84
|
* M1: why this task is held back (e.g. "dependency_failed:<taskId>").
|
|
77
85
|
* Only set by the system on dependency failure under the 'block' policy;
|
|
@@ -186,6 +194,8 @@ export declare function enqueueTask(meshId: string, message: string, opts?: {
|
|
|
186
194
|
missionId?: string;
|
|
187
195
|
/** MAGI: consensus group id shared by every replica of a mesh_magi_review fan-out. */
|
|
188
196
|
consensusGroupId?: string;
|
|
197
|
+
/** MAGI-KIND-PANEL: model override forwarded to the executing session's launch (initialModel). */
|
|
198
|
+
model?: string;
|
|
189
199
|
/** Explicit task id for batch/template flows (M5). Random UUID when omitted. */
|
|
190
200
|
id?: string;
|
|
191
201
|
/** (3) Originating coordinator session id (for session-anchored completion routing). */
|
|
@@ -489,6 +489,16 @@ export interface ProviderModule {
|
|
|
489
489
|
/** Auto-implement spawn config — controls how this provider is invoked for autonomous script generation */
|
|
490
490
|
autoImpl?: ProviderAutoImplSpawnConfig;
|
|
491
491
|
};
|
|
492
|
+
/**
|
|
493
|
+
* MAGI-KIND-PANEL (model axis): template for expanding an `initialModel` selection
|
|
494
|
+
* into launch args for a CLI provider. `{{model}}` is substituted with the model
|
|
495
|
+
* string; e.g. `['--model', '{{model}}']` for claude-cli → `--model opus`. Applied
|
|
496
|
+
* at session launch when `initialModel` is passed AND this provider is a plain CLI
|
|
497
|
+
* (ACP providers instead route the model through setConfigOption). A CLI provider
|
|
498
|
+
* with no template silently ignores `initialModel` at launch (best-effort; a model
|
|
499
|
+
* request never fails a launch). Absent → no launch-time model selection for CLI.
|
|
500
|
+
*/
|
|
501
|
+
modelLaunchArgs?: string[];
|
|
492
502
|
/** Delay before submitting typed CLI input (provider-specific TUI tuning) */
|
|
493
503
|
sendDelayMs?: number;
|
|
494
504
|
/** Submit key used after typing into CLI PTY (default: carriage return) */
|
|
@@ -26,5 +26,5 @@ export interface NativeHistorySessionMeta {
|
|
|
26
26
|
preview?: string;
|
|
27
27
|
workspace?: string;
|
|
28
28
|
}
|
|
29
|
-
export declare function readSession(sessionPath: string): NativeHistorySession | null;
|
|
29
|
+
export declare function readSession(sessionPath: string, requestedSessionId?: string): NativeHistorySession | null;
|
|
30
30
|
export declare function listSessions(_watchPath: string): Promise<NativeHistorySessionMeta[]>;
|
|
@@ -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 { MagiPanelMap } from '@adhdev/mesh-shared';
|
|
16
|
+
import type { MagiPanelMap, MagiKindPanelMap } from '@adhdev/mesh-shared';
|
|
17
17
|
export interface RepoMesh {
|
|
18
18
|
id: string;
|
|
19
19
|
name: string;
|
|
@@ -536,6 +536,14 @@ export interface LocalMeshConfig {
|
|
|
536
536
|
* Optional: absent on configs written before MAGI existed.
|
|
537
537
|
*/
|
|
538
538
|
magiPanels?: MagiPanelMap;
|
|
539
|
+
/**
|
|
540
|
+
* MAGI-KIND-PANEL: per-task_kind panel bindings (machine-local). Keyed by
|
|
541
|
+
* task_kind (rca / design / claim_audit / freeform); each maps to ≥1
|
|
542
|
+
* `(node × provider × model?)` slot. A `mesh_magi_review` invoked with a bare
|
|
543
|
+
* `task_kind` resolves its panel from here — an unconfigured kind is a hard
|
|
544
|
+
* error, never a synthesized fallback. Optional; absent on pre-feature configs.
|
|
545
|
+
*/
|
|
546
|
+
magiKindPanels?: MagiKindPanelMap;
|
|
539
547
|
}
|
|
540
548
|
export interface LocalMeshEntry {
|
|
541
549
|
id: string;
|
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.439",
|
|
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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.439",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -369,6 +369,18 @@ function expandResumeArgs(template: string[] | undefined, sessionId: string): st
|
|
|
369
369
|
return template.map((part) => part === '{{id}}' ? sessionId : part);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Expand a provider's `modelLaunchArgs` template with the requested model, mirroring
|
|
374
|
+
* expandResumeArgs. `{{model}}` → the trimmed model string. Returns undefined when
|
|
375
|
+
* there is no template or no model (a model request without a template is a no-op —
|
|
376
|
+
* see startSession, where the caller logs the skip). MAGI kind-panel model axis.
|
|
377
|
+
*/
|
|
378
|
+
function expandModelLaunchArgs(template: string[] | undefined, model: string | undefined): string[] | undefined {
|
|
379
|
+
const m = typeof model === 'string' ? model.trim() : '';
|
|
380
|
+
if (!m || !Array.isArray(template) || template.length === 0) return undefined;
|
|
381
|
+
return template.map((part) => part === '{{model}}' ? m : part);
|
|
382
|
+
}
|
|
383
|
+
|
|
372
384
|
function readSubcommandSessionId(args: string[], subcommands: string[]): string | undefined {
|
|
373
385
|
const resumeIndex = args.findIndex((arg) => subcommands.includes(arg));
|
|
374
386
|
if (resumeIndex < 0) return undefined;
|
|
@@ -895,8 +907,23 @@ export class DaemonCliManager {
|
|
|
895
907
|
console.log(colorize('cyan', ` 📦 Using provider: ${provider.name} (${provider.type})`));
|
|
896
908
|
}
|
|
897
909
|
|
|
910
|
+
// ─── Model axis (MAGI kind-panel): expand initialModel → launch args ───
|
|
911
|
+
// For a plain CLI provider the model is selected at spawn time via the manifest's
|
|
912
|
+
// modelLaunchArgs template ('{{model}}' → the requested model). ACP providers took
|
|
913
|
+
// the setConfigOption path above and never reach here. A provider with no template,
|
|
914
|
+
// or no requested model, is a no-op — model selection is best-effort and must never
|
|
915
|
+
// fail a launch. The model args are prepended so a caller's explicit cliArgs (e.g. a
|
|
916
|
+
// resume flag) still win positionally where order matters.
|
|
917
|
+
const modelLaunchArgs = expandModelLaunchArgs(provider?.modelLaunchArgs, initialModel);
|
|
918
|
+
const cliArgsWithModel = modelLaunchArgs
|
|
919
|
+
? [...modelLaunchArgs, ...(cliArgs || [])]
|
|
920
|
+
: cliArgs;
|
|
921
|
+
if (initialModel && !modelLaunchArgs) {
|
|
922
|
+
LOG.warn('CLI', `[${normalizedType}] initialModel='${initialModel}' requested but provider declares no modelLaunchArgs template — launching without model selection.`);
|
|
923
|
+
}
|
|
924
|
+
|
|
898
925
|
// ─── Resolve launch options → provider session binding ───
|
|
899
|
-
const sessionBinding = resolveCliSessionBinding(provider, normalizedType,
|
|
926
|
+
const sessionBinding = resolveCliSessionBinding(provider, normalizedType, cliArgsWithModel, options?.resumeSessionId);
|
|
900
927
|
const resolvedCliArgs = sessionBinding.cliArgs;
|
|
901
928
|
|
|
902
929
|
// If InstanceManager exists, manage as CliProviderInstance unified
|
|
@@ -232,6 +232,95 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
232
232
|
}
|
|
233
233
|
},
|
|
234
234
|
|
|
235
|
+
// Gated WRITE path for `.adhdev/mesh.json` — the sibling of export_mesh_json_config
|
|
236
|
+
// (which only DRAFTS). Same write/overwrite/dry-run contract as mesh_init's config
|
|
237
|
+
// writer: defaults to dry-run (no write), never clobbers an existing repo mesh.json
|
|
238
|
+
// unless overwrite=true, and validates the scaffold before persisting. The scaffold
|
|
239
|
+
// is built from the machine-local mesh entry (coordinator prompt override/append);
|
|
240
|
+
// policy/operating-notes are intentionally NOT exported (see buildMeshJsonConfigScaffold).
|
|
241
|
+
write_mesh_json_config: async (_ctx: MedFamilyContext, args: any) => {
|
|
242
|
+
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
243
|
+
if (!meshId) return { success: false, error: 'meshId required' };
|
|
244
|
+
const workspace = typeof args?.workspace === 'string' && args.workspace.trim() ? args.workspace.trim() : process.cwd();
|
|
245
|
+
const write = args?.write === true;
|
|
246
|
+
const overwrite = args?.overwrite === true;
|
|
247
|
+
try {
|
|
248
|
+
const { getMesh } = await import('../../config/mesh-config.js');
|
|
249
|
+
const mesh = getMesh(meshId);
|
|
250
|
+
if (!mesh) return { success: false, error: 'Mesh not found' };
|
|
251
|
+
const {
|
|
252
|
+
buildMeshJsonConfigScaffold,
|
|
253
|
+
serializeMeshJsonConfigScaffold,
|
|
254
|
+
loadRepoMeshJsonConfig,
|
|
255
|
+
normalizeRepoMeshDeclarativeConfig,
|
|
256
|
+
MESH_JSON_CONFIG_LOCATIONS,
|
|
257
|
+
} = await import('../../config/mesh-json-config.js');
|
|
258
|
+
const { mkdirSync, writeFileSync } = await import('fs');
|
|
259
|
+
const { dirname, join } = await import('path');
|
|
260
|
+
|
|
261
|
+
const scaffold = buildMeshJsonConfigScaffold(mesh);
|
|
262
|
+
const scaffoldJson = serializeMeshJsonConfigScaffold(scaffold);
|
|
263
|
+
const relativePath = MESH_JSON_CONFIG_LOCATIONS[0];
|
|
264
|
+
const absolutePath = join(workspace, relativePath);
|
|
265
|
+
|
|
266
|
+
// Validate before we ever touch disk — never write an unusable mesh.json.
|
|
267
|
+
const validation = normalizeRepoMeshDeclarativeConfig(scaffold);
|
|
268
|
+
if (!validation.valid) {
|
|
269
|
+
return { success: false, meshId, error: `invalid mesh.json scaffold: ${validation.errors.join('; ')}` };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// existing-wins: a repo mesh.json already present is kept unless overwrite=true.
|
|
273
|
+
const existing = loadRepoMeshJsonConfig(workspace);
|
|
274
|
+
const existingPresent = existing.sourceType === 'repo_file' || existing.sourceType === 'invalid';
|
|
275
|
+
if (existingPresent && !overwrite) {
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
meshId,
|
|
279
|
+
written: false,
|
|
280
|
+
dryRun: !write,
|
|
281
|
+
skippedReason: 'already_exists',
|
|
282
|
+
path: absolutePath,
|
|
283
|
+
relativePath,
|
|
284
|
+
existing: existing.config,
|
|
285
|
+
existingSourceType: existing.sourceType,
|
|
286
|
+
scaffold,
|
|
287
|
+
scaffoldJson,
|
|
288
|
+
note: 'A repo mesh.json already exists — kept as-is. Re-run with overwrite=true to replace it (this silently drops operator hand-edits, so present a current-vs-suggested diff first).',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!write) {
|
|
293
|
+
return {
|
|
294
|
+
success: true,
|
|
295
|
+
meshId,
|
|
296
|
+
written: false,
|
|
297
|
+
dryRun: true,
|
|
298
|
+
path: absolutePath,
|
|
299
|
+
relativePath,
|
|
300
|
+
scaffold,
|
|
301
|
+
scaffoldJson,
|
|
302
|
+
note: 'Dry-run: nothing written. Re-run with write=true to persist to the repo (commit target). meshes.json is untouched.',
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
307
|
+
writeFileSync(absolutePath, `${scaffoldJson}\n`, 'utf-8');
|
|
308
|
+
return {
|
|
309
|
+
success: true,
|
|
310
|
+
meshId,
|
|
311
|
+
written: true,
|
|
312
|
+
dryRun: false,
|
|
313
|
+
path: absolutePath,
|
|
314
|
+
relativePath,
|
|
315
|
+
scaffold,
|
|
316
|
+
scaffoldJson,
|
|
317
|
+
note: 'Wrote .adhdev/mesh.json (repo commit target). Commit it to the repo; meshes.json (machine-local) is unchanged.',
|
|
318
|
+
};
|
|
319
|
+
} catch (e: any) {
|
|
320
|
+
return { success: false, error: e.message };
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
|
|
235
324
|
delete_mesh: async (_ctx: MedFamilyContext, args: any) => {
|
|
236
325
|
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
237
326
|
if (!meshId) return { success: false, error: 'meshId required' };
|
|
@@ -302,6 +391,48 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
302
391
|
}
|
|
303
392
|
},
|
|
304
393
|
|
|
394
|
+
// ─── MAGI kind → panel bindings (MAGI-KIND-PANEL, machine-local config) ───
|
|
395
|
+
// Per-task_kind slot lists in ~/.adhdev/meshes.json `magiKindPanels`. Same
|
|
396
|
+
// owner-only gating and structured-error precedent as the magi_panel_* handlers
|
|
397
|
+
// above (not listed in canPeerUsePrivilegedShareCommand → owner-only). set/remove
|
|
398
|
+
// are WRITE commands; list is read-only. normalizeMagiSlots (inside setMagiKindPanel)
|
|
399
|
+
// surfaces invalid_magi_kind_panel: … messages verbatim for the editor.
|
|
400
|
+
magi_kind_panel_list: async (_ctx: MedFamilyContext, _args: any) => {
|
|
401
|
+
try {
|
|
402
|
+
const { listMagiKindPanels } = await import('../../config/mesh-config.js');
|
|
403
|
+
return { success: true, kindPanels: listMagiKindPanels() };
|
|
404
|
+
} catch (e: any) {
|
|
405
|
+
return { success: false, error: e.message };
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
magi_kind_panel_set: async (_ctx: MedFamilyContext, args: any) => {
|
|
410
|
+
const kind = typeof args?.kind === 'string' ? args.kind.trim() : '';
|
|
411
|
+
if (!kind) return { success: false, error: 'invalid_magi_kind_panel: task_kind is required' };
|
|
412
|
+
try {
|
|
413
|
+
const { setMagiKindPanel } = await import('../../config/mesh-config.js');
|
|
414
|
+
// normalizeMagiTaskKindKey + normalizeMagiSlots (inside setMagiKindPanel)
|
|
415
|
+
// validate the kind and each slot (provider required; model/nodeId optional;
|
|
416
|
+
// replica counts clamped). Structured errors flow back as `error`.
|
|
417
|
+
const slots = setMagiKindPanel(kind, args?.slots);
|
|
418
|
+
return { success: true, kind, slots };
|
|
419
|
+
} catch (e: any) {
|
|
420
|
+
return { success: false, error: e.message };
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
magi_kind_panel_remove: async (_ctx: MedFamilyContext, args: any) => {
|
|
425
|
+
const kind = typeof args?.kind === 'string' ? args.kind.trim() : '';
|
|
426
|
+
if (!kind) return { success: false, error: 'invalid_magi_kind_panel: task_kind is required' };
|
|
427
|
+
try {
|
|
428
|
+
const { removeMagiKindPanel } = await import('../../config/mesh-config.js');
|
|
429
|
+
const removed = removeMagiKindPanel(kind);
|
|
430
|
+
return { success: true, removed };
|
|
431
|
+
} catch (e: any) {
|
|
432
|
+
return { success: false, error: e.message };
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
|
|
305
436
|
add_mesh_node: async (ctx: MedFamilyContext, args: any) => {
|
|
306
437
|
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
307
438
|
const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
|
|
@@ -1820,6 +1820,16 @@ function callProviderNativeHistoryRead(
|
|
|
1820
1820
|
const result = fn({
|
|
1821
1821
|
agentType,
|
|
1822
1822
|
sessionId: normalizedSessionId,
|
|
1823
|
+
// Arm the native-history executor's session pin guard. When the
|
|
1824
|
+
// instance is already bound to a provider session, pass that id as
|
|
1825
|
+
// `providerSessionId` so the executor rejects any *other* newest
|
|
1826
|
+
// session it would otherwise pick (hermes ≥0.14 creates a fresh
|
|
1827
|
+
// `sessions` row per internal sub-session, so an unpinned
|
|
1828
|
+
// newest-wins query drifts to a different id on every read →
|
|
1829
|
+
// re-bind churn + unbounded history re-hydration). When there is no
|
|
1830
|
+
// bound id yet (first-bind / workspace-only discovery) this is '',
|
|
1831
|
+
// which leaves the guard disarmed so discovery still works.
|
|
1832
|
+
providerSessionId: normalizedSessionId,
|
|
1823
1833
|
historySessionId: normalizedSessionId,
|
|
1824
1834
|
workspace,
|
|
1825
1835
|
format: canonicalHistory?.format,
|
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
RepoMeshHostMetadata,
|
|
23
23
|
RepoMeshDaemonRole,
|
|
24
24
|
} from '../repo-mesh-types.js';
|
|
25
|
-
import type { MagiPanel, MagiPanelMember, MagiPanelDefaultKind } from '@adhdev/mesh-shared';
|
|
25
|
+
import type { MagiPanel, MagiPanelMember, MagiPanelDefaultKind, MagiKindPanelMap, MagiSlot, MagiTaskKind } from '@adhdev/mesh-shared';
|
|
26
26
|
import { mergeAndNormalizePolicy } from '../repo-mesh-types.js';
|
|
27
27
|
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
28
28
|
|
|
@@ -650,11 +650,13 @@ export function normalizeMagiPanel(config: unknown): MagiPanel {
|
|
|
650
650
|
throw new Error(`invalid_magi_panel: member[${idx}].provider is required`);
|
|
651
651
|
}
|
|
652
652
|
const nodeId = typeof m.nodeId === 'string' && m.nodeId.trim() ? m.nodeId.trim() : undefined;
|
|
653
|
+
const model = typeof m.model === 'string' && m.model.trim() ? m.model.trim() : undefined;
|
|
653
654
|
const capabilityTags = normalizeCapabilityTags(m.capabilityTags);
|
|
654
655
|
const n = normalizeReplicaCount(m.n);
|
|
655
656
|
return {
|
|
656
657
|
provider,
|
|
657
658
|
...(nodeId ? { nodeId } : {}),
|
|
659
|
+
...(model ? { model } : {}),
|
|
658
660
|
...(capabilityTags ? { capabilityTags } : {}),
|
|
659
661
|
...(n !== undefined ? { n } : {}),
|
|
660
662
|
};
|
|
@@ -726,3 +728,99 @@ export function removeMagiPanel(name: string): boolean {
|
|
|
726
728
|
saveMeshConfig(stored);
|
|
727
729
|
return true;
|
|
728
730
|
}
|
|
731
|
+
|
|
732
|
+
// ─── MAGI kind → panel bindings (MAGI-KIND-PANEL) ─────────
|
|
733
|
+
//
|
|
734
|
+
// Per-task_kind slot lists (machine-local, meshes.json `magiKindPanels`). A bare
|
|
735
|
+
// `mesh_magi_review({task_kind})` resolves its panel exclusively from here — an
|
|
736
|
+
// unconfigured kind is a hard error, never a synthesized fallback. Mirrors the named
|
|
737
|
+
// panel accessors above (normalize / list / get / set / remove).
|
|
738
|
+
|
|
739
|
+
/** The task kinds a kind-panel can be bound to. Unlike a named panel's defaultKind,
|
|
740
|
+
* 'freeform' IS a valid kind-panel key (this is a direct kind→slots binding). */
|
|
741
|
+
const MAGI_KIND_PANEL_KINDS: readonly MagiTaskKind[] = ['claim_audit', 'rca', 'design', 'freeform'];
|
|
742
|
+
const MAX_MAGI_KIND_SLOTS = 24;
|
|
743
|
+
|
|
744
|
+
function normalizeMagiTaskKindKey(raw: unknown): MagiTaskKind {
|
|
745
|
+
const s = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
746
|
+
if (!(MAGI_KIND_PANEL_KINDS as readonly string[]).includes(s)) {
|
|
747
|
+
throw new Error(`invalid_magi_kind_panel: task_kind must be one of ${MAGI_KIND_PANEL_KINDS.join(' / ')} (got '${s || '(empty)'}')`);
|
|
748
|
+
}
|
|
749
|
+
return s as MagiTaskKind;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Validate + normalize a kind-panel's slots. Mirrors normalizeMagiPanel's member
|
|
754
|
+
* normalization: provider required per slot, trims strings, drops empties, clamps
|
|
755
|
+
* replica counts, and additionally carries an optional per-slot `model`. Throws on
|
|
756
|
+
* structurally invalid input (empty list / no provider) so the write returns a clear
|
|
757
|
+
* error. Returns the normalized slot array.
|
|
758
|
+
*/
|
|
759
|
+
export function normalizeMagiSlots(slots: unknown): MagiSlot[] {
|
|
760
|
+
if (!Array.isArray(slots) || slots.length === 0) {
|
|
761
|
+
throw new Error('invalid_magi_kind_panel: slots must be a non-empty array');
|
|
762
|
+
}
|
|
763
|
+
if (slots.length > MAX_MAGI_KIND_SLOTS) {
|
|
764
|
+
throw new Error(`invalid_magi_kind_panel: too many slots (max ${MAX_MAGI_KIND_SLOTS})`);
|
|
765
|
+
}
|
|
766
|
+
return slots.map((entry, idx) => {
|
|
767
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
768
|
+
throw new Error(`invalid_magi_kind_panel: slot[${idx}] must be an object`);
|
|
769
|
+
}
|
|
770
|
+
const s = entry as Record<string, unknown>;
|
|
771
|
+
const provider = typeof s.provider === 'string' ? s.provider.trim() : '';
|
|
772
|
+
if (!provider) {
|
|
773
|
+
throw new Error(`invalid_magi_kind_panel: slot[${idx}].provider is required`);
|
|
774
|
+
}
|
|
775
|
+
const nodeId = typeof s.nodeId === 'string' && s.nodeId.trim() ? s.nodeId.trim() : undefined;
|
|
776
|
+
const model = typeof s.model === 'string' && s.model.trim() ? s.model.trim() : undefined;
|
|
777
|
+
const capabilityTags = normalizeCapabilityTags(s.capabilityTags);
|
|
778
|
+
const n = normalizeReplicaCount(s.n);
|
|
779
|
+
return {
|
|
780
|
+
provider,
|
|
781
|
+
...(nodeId ? { nodeId } : {}),
|
|
782
|
+
...(model ? { model } : {}),
|
|
783
|
+
...(capabilityTags ? { capabilityTags } : {}),
|
|
784
|
+
...(n !== undefined ? { n } : {}),
|
|
785
|
+
};
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** All configured kind-panels (machine-local), keyed by task_kind. Empty when none. */
|
|
790
|
+
export function listMagiKindPanels(): MagiKindPanelMap {
|
|
791
|
+
return loadMeshConfig().magiKindPanels ?? {};
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** The slot list for one task_kind, or undefined when the kind is not configured. */
|
|
795
|
+
export function getMagiKindPanel(kind: string): MagiSlot[] | undefined {
|
|
796
|
+
let key: MagiTaskKind;
|
|
797
|
+
try { key = normalizeMagiTaskKindKey(kind); } catch { return undefined; }
|
|
798
|
+
return loadMeshConfig().magiKindPanels?.[key];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Upsert the slot list for one task_kind. Unlike named panels this ALWAYS overwrites
|
|
803
|
+
* (a kind has exactly one binding) — the editor pushes the full desired slot set.
|
|
804
|
+
* Returns the normalized, persisted slots.
|
|
805
|
+
*/
|
|
806
|
+
export function setMagiKindPanel(kind: string, slots: unknown): MagiSlot[] {
|
|
807
|
+
const key = normalizeMagiTaskKindKey(kind);
|
|
808
|
+
const normalized = normalizeMagiSlots(slots);
|
|
809
|
+
const stored = loadMeshConfig();
|
|
810
|
+
const map = stored.magiKindPanels ?? {};
|
|
811
|
+
map[key] = normalized;
|
|
812
|
+
stored.magiKindPanels = map;
|
|
813
|
+
saveMeshConfig(stored);
|
|
814
|
+
return normalized;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/** Remove the binding for one task_kind. Returns true when a binding was removed. */
|
|
818
|
+
export function removeMagiKindPanel(kind: string): boolean {
|
|
819
|
+
let key: MagiTaskKind;
|
|
820
|
+
try { key = normalizeMagiTaskKindKey(kind); } catch { return false; }
|
|
821
|
+
const stored = loadMeshConfig();
|
|
822
|
+
if (!stored.magiKindPanels || !stored.magiKindPanels[key]) return false;
|
|
823
|
+
delete stored.magiKindPanels[key];
|
|
824
|
+
saveMeshConfig(stored);
|
|
825
|
+
return true;
|
|
826
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -196,6 +196,7 @@ export {
|
|
|
196
196
|
listMeshes, getMesh, getMeshByRepo, createMesh, updateMesh, deleteMesh,
|
|
197
197
|
addNode, removeNode, updateNode, normalizeRepoIdentity,
|
|
198
198
|
listMagiPanels, getMagiPanel, upsertMagiPanel, removeMagiPanel, normalizeMagiPanel,
|
|
199
|
+
listMagiKindPanels, getMagiKindPanel, setMagiKindPanel, removeMagiKindPanel, normalizeMagiSlots,
|
|
199
200
|
} from './config/mesh-config.js';
|
|
200
201
|
export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './config/mesh-config.js';
|
|
201
202
|
// MAGI panel / common-output / synthesis types (re-exported from the mesh-shared
|
|
@@ -203,6 +204,7 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
203
204
|
// them without taking a direct @adhdev/mesh-shared dependency).
|
|
204
205
|
export type {
|
|
205
206
|
MagiPanel, MagiPanelMember, MagiPanelMap, MagiMode, MagiTaskKind, MagiPanelDefaultKind,
|
|
207
|
+
MagiSlot, MagiKindPanelMap,
|
|
206
208
|
MagiClaim, MagiClaimStance, MagiAgentResponse,
|
|
207
209
|
MagiResponseSource, MagiReplicaGitRef, MagiGitSkew, MagiSynthesizedResponse,
|
|
208
210
|
MagiClusterCategory, MagiClusterMember, MagiClaimCluster, MagiSynthesis,
|
|
@@ -207,6 +207,9 @@ Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `\nDefault branch: \`
|
|
|
207
207
|
// ── Workflow ──
|
|
208
208
|
sections.push(WORKFLOW_SECTION);
|
|
209
209
|
|
|
210
|
+
// ── Onboarding / Reinit ──
|
|
211
|
+
sections.push(ONBOARDING_SECTION);
|
|
212
|
+
|
|
210
213
|
// ── Rules ──
|
|
211
214
|
sections.push(buildRulesSection(coordinatorCliType));
|
|
212
215
|
|
|
@@ -255,6 +258,7 @@ function readUserPromptFile(cliType: string | undefined, suffix: string): string
|
|
|
255
258
|
* {{policy}} — full policy section
|
|
256
259
|
* {{tools}} — the canonical tools table
|
|
257
260
|
* {{workflow}} — the canonical orchestration workflow
|
|
261
|
+
* {{onboarding}} — the guided init/reinit onboarding section
|
|
258
262
|
* {{rules}} — the canonical rules section (with coordinatorNote)
|
|
259
263
|
* {{toolExposurePreflight}} — the MCP-missing preflight reminder
|
|
260
264
|
*
|
|
@@ -281,6 +285,7 @@ function expandPromptPlaceholders(template: string, ctx: CoordinatorPromptContex
|
|
|
281
285
|
policy: buildPolicySection(mergeAndNormalizePolicy(undefined, mesh.policy)),
|
|
282
286
|
tools: TOOLS_SECTION,
|
|
283
287
|
workflow: WORKFLOW_SECTION,
|
|
288
|
+
onboarding: ONBOARDING_SECTION,
|
|
284
289
|
rules: buildRulesSection(coordinatorCliType),
|
|
285
290
|
toolExposurePreflight: TOOL_EXPOSURE_PREFLIGHT_SECTION,
|
|
286
291
|
};
|
|
@@ -493,7 +498,12 @@ const TOOLS_SECTION = `## Available Tools
|
|
|
493
498
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
494
499
|
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
495
500
|
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
|
|
496
|
-
| \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node
|
|
501
|
+
| \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |
|
|
502
|
+
| \`mesh_init\` | Guided onboarding for a fresh repo: dry-run scan → suggest \`.adhdev/*\` configs (refine/bootstrap/change-impact) + providerPriority + current-config echo; gated write on approval |
|
|
503
|
+
| \`mesh_reinit\` | Re-onboard an already-configured repo: re-suggest with overwrite semantics + current-vs-suggested diff; dry-run preview first, per-section approval before write |
|
|
504
|
+
| \`mesh_write_mesh_json_config\` | Gated write of \`.adhdev/mesh.json\` (repo coordinator-prompt config) from the mesh entry — dry-run/overwrite like mesh_init |
|
|
505
|
+
| \`mesh_magi_kind_panel_set\` | Bind a task_kind → MAGI kind-panel slots (machine-local, wholesale replacement — approve current-vs-new first) |
|
|
506
|
+
| \`mesh_magi_kind_panel_list\` | List configured task_kind → MAGI kind-panel slot bindings (machine-local, read-only) |`;
|
|
497
507
|
|
|
498
508
|
const TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
499
509
|
|
|
@@ -529,6 +539,29 @@ Follow these recovery rules:
|
|
|
529
539
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
530
540
|
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
|
|
531
541
|
|
|
542
|
+
const ONBOARDING_SECTION = `## Onboarding / Reinit
|
|
543
|
+
|
|
544
|
+
When the user asks to **set up / configure / onboard** this repo for Repo Mesh (or to **re-init / reconfigure** an already-onboarded repo), run ONE guided, approval-gated conversation. You draft, the user approves, the daemon writes. Never auto-write a heuristic suggestion without an explicit user approval turn.
|
|
545
|
+
|
|
546
|
+
**Save scopes — label every draft with its scope before asking for approval:**
|
|
547
|
+
- **repo-file (commit target)** — \`.adhdev/refine.json\`, \`.adhdev/worktree_bootstrap.json\`, \`.adhdev/change-impact.json\`, \`.adhdev/mesh.json\`. These are committed to the repository and shared with every machine/contributor.
|
|
548
|
+
- **machine-local** — MAGI kind→panel bindings and named MAGI panels, node providerPriority (\`~/.adhdev/meshes.json\`). These stay on this machine and are NOT committed.
|
|
549
|
+
|
|
550
|
+
**Guided sequence:**
|
|
551
|
+
1. **Scan (dry-run)** — Call \`mesh_init\` (write=false, the default). It returns per-domain suggested configs for refine / worktree_bootstrap / change-impact, a recommended providerPriority, AND \`currentConfig\` — the currently-saved config per domain (repo files + machine-local \`magiKindPanels\`). Nothing is written.
|
|
552
|
+
2. **Present drafts** — For each domain, show the user the suggested config with its **save scope label** (repo-file vs machine-local). When \`currentConfig\` already has a saved value for a domain (init on a partially-onboarded repo, or any reinit), present a **current-vs-suggested diff**, not just the suggestion.
|
|
553
|
+
3. **Approve → gated write** — Only after the user approves, call the matching gated-write tool:
|
|
554
|
+
- repo \`.adhdev/*\` config files → \`mesh_init\` with \`write=true\` (and \`overwrite=true\` ONLY for domains the user approved replacing).
|
|
555
|
+
- \`.adhdev/mesh.json\` (coordinator prompt / operating notes) → \`mesh_write_mesh_json_config\` (write=true, overwrite only if approved).
|
|
556
|
+
- machine-local MAGI kind→panel slots → \`mesh_magi_kind_panel_set\` (write=true). NOTE: a kind binding is a **wholesale replacement** of that kind's slot list — present the current-vs-new slots first.
|
|
557
|
+
- machine-local named MAGI panels → \`mesh_magi_panel_set\`. providerPriority → apply via node policy update.
|
|
558
|
+
|
|
559
|
+
**init vs reinit:**
|
|
560
|
+
- **\`mesh_init\`** — for a fresh, never-onboarded repo. Existing config files are kept (existing-wins) unless the user explicitly approves overwrite. Use for first-time setup.
|
|
561
|
+
- **\`mesh_reinit\`** — for a repo that is already onboarded and needs its config refreshed. It re-suggests with OVERWRITE semantics and returns the current-vs-suggested \`currentConfig\` echo. Its first call is a DRY-RUN preview: you MUST present the per-section current-vs-suggested diff and get EXPLICIT per-section approval before re-invoking with write=true. Overwrite is a wholesale replacement, so it silently drops operator hand-edits if you skip the diff — never do that.
|
|
562
|
+
|
|
563
|
+
`;
|
|
564
|
+
|
|
532
565
|
function buildRulesSection(coordinatorCliType?: string): string {
|
|
533
566
|
const coordinatorNote = coordinatorCliType
|
|
534
567
|
? `\n- **Coordinator runtime is not a delegation default.** This coordinator is running as \`${coordinatorCliType}\`, but delegated node sessions must follow the user's requested provider, not the coordinator's own runtime.`
|