@adhdev/daemon-core 0.9.82-rc.438 → 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/index.d.ts +1 -1
- package/dist/index.js +211 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +211 -34
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-init.d.ts +44 -2
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +1 -1
- package/package.json +2 -2
- package/src/commands/med-family/mesh-crud.ts +89 -0
- package/src/config/chat-history.ts +10 -0
- package/src/index.ts +1 -0
- package/src/mesh/coordinator-prompt.ts +34 -1
- package/src/mesh/mesh-init.ts +95 -5
- package/src/providers/cli-provider-instance.ts +24 -2
- package/src/providers/native-history/dispatcher.ts +8 -2
- package/src/providers/native-history/hermes-cli-transcript.ts +27 -11
- package/src/providers/spec/native-history-executor.ts +41 -26
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
|
/**
|
|
@@ -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[]>;
|
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",
|
|
@@ -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' };
|
|
@@ -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,
|
package/src/index.ts
CHANGED
|
@@ -204,6 +204,7 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
204
204
|
// them without taking a direct @adhdev/mesh-shared dependency).
|
|
205
205
|
export type {
|
|
206
206
|
MagiPanel, MagiPanelMember, MagiPanelMap, MagiMode, MagiTaskKind, MagiPanelDefaultKind,
|
|
207
|
+
MagiSlot, MagiKindPanelMap,
|
|
207
208
|
MagiClaim, MagiClaimStance, MagiAgentResponse,
|
|
208
209
|
MagiResponseSource, MagiReplicaGitRef, MagiGitSkew, MagiSynthesizedResponse,
|
|
209
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.`
|
package/src/mesh/mesh-init.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
|
|
@@ -34,11 +40,21 @@ import {
|
|
|
34
40
|
validateMeshWorktreeBootstrapConfig,
|
|
35
41
|
type RepoMeshWorktreeBootstrapConfig,
|
|
36
42
|
} from './worktree-bootstrap-config.js';
|
|
43
|
+
import {
|
|
44
|
+
CHANGE_IMPACT_CONFIG_LOCATIONS,
|
|
45
|
+
loadChangeImpactConfig,
|
|
46
|
+
suggestChangeImpactConfig,
|
|
47
|
+
validateChangeImpactConfig,
|
|
48
|
+
type ChangeImpactConfig,
|
|
49
|
+
} from '../git/change-impact-config.js';
|
|
50
|
+
import { listMagiKindPanels } from '../config/mesh-config.js';
|
|
51
|
+
import type { MagiKindPanelMap } from '@adhdev/mesh-shared';
|
|
37
52
|
import type { CLIInfo } from '../detection/cli-detector.js';
|
|
38
53
|
|
|
39
54
|
/** Canonical write targets — the first/preferred location for each config family. */
|
|
40
55
|
export const MESH_INIT_REFINE_CONFIG_PATH = MESH_REFINE_CONFIG_LOCATIONS[0];
|
|
41
56
|
export const MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH = MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS[0];
|
|
57
|
+
export const MESH_INIT_CHANGE_IMPACT_CONFIG_PATH = CHANGE_IMPACT_CONFIG_LOCATIONS[0];
|
|
42
58
|
|
|
43
59
|
/**
|
|
44
60
|
* Default lockfiles whose change should invalidate a 'ready' bootstrap.
|
|
@@ -61,7 +77,7 @@ export interface MeshInitConfigFileResult {
|
|
|
61
77
|
written: boolean;
|
|
62
78
|
/** Why it was not written (already_exists / not_suggested / no change). */
|
|
63
79
|
skippedReason?: 'already_exists' | 'no_suggestion';
|
|
64
|
-
config?: RepoMeshRefineConfig | RepoMeshWorktreeBootstrapConfig;
|
|
80
|
+
config?: RepoMeshRefineConfig | RepoMeshWorktreeBootstrapConfig | ChangeImpactConfig;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
/**
|
|
@@ -158,16 +174,50 @@ export interface RunMeshInitOptions {
|
|
|
158
174
|
overwrite?: boolean;
|
|
159
175
|
}
|
|
160
176
|
|
|
177
|
+
/**
|
|
178
|
+
* A snapshot of the currently-saved config for each domain, so the coordinator can
|
|
179
|
+
* render a current-vs-suggested diff to the user before an init/reinit overwrite.
|
|
180
|
+
* This is READ-ONLY echo — every field is what is on disk / machine-local right now,
|
|
181
|
+
* never a suggestion. Absent domains are reported as `undefined` config.
|
|
182
|
+
*/
|
|
183
|
+
export interface MeshInitCurrentConfigEcho {
|
|
184
|
+
/** Currently-saved `.adhdev/refine.json` (repo-committed) — undefined when absent/invalid. */
|
|
185
|
+
refine?: RepoMeshRefineConfig;
|
|
186
|
+
/** Currently-saved `.adhdev/worktree_bootstrap.json` (repo-committed). */
|
|
187
|
+
worktreeBootstrap?: RepoMeshWorktreeBootstrapConfig;
|
|
188
|
+
/** Currently-saved `.adhdev/change-impact.json` (repo-committed). */
|
|
189
|
+
changeImpact?: ChangeImpactConfig;
|
|
190
|
+
/** Per-domain source type so the coordinator can tell "absent" from "invalid". */
|
|
191
|
+
sourceTypes: {
|
|
192
|
+
refine: string;
|
|
193
|
+
worktreeBootstrap: string;
|
|
194
|
+
changeImpact: string;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Currently-configured MAGI kind→panel slot bindings (machine-local
|
|
198
|
+
* ~/.adhdev/meshes.json). Empty object when none configured. This is a
|
|
199
|
+
* machine-local echo — the coordinator labels it as such vs. the repo files.
|
|
200
|
+
*/
|
|
201
|
+
magiKindPanels: MagiKindPanelMap;
|
|
202
|
+
}
|
|
203
|
+
|
|
161
204
|
export interface RunMeshInitResult {
|
|
162
205
|
success: true;
|
|
163
206
|
workspace: string;
|
|
164
207
|
dryRun: boolean;
|
|
165
208
|
refine: MeshInitConfigFileResult;
|
|
166
209
|
worktreeBootstrap: MeshInitConfigFileResult;
|
|
210
|
+
changeImpact: MeshInitConfigFileResult;
|
|
167
211
|
providers: {
|
|
168
212
|
providerPriority: string[];
|
|
169
213
|
installedProviders: Array<{ id: string; displayName: string; version?: string }>;
|
|
170
214
|
};
|
|
215
|
+
/**
|
|
216
|
+
* Read-only snapshot of the currently-saved config per domain (repo files +
|
|
217
|
+
* machine-local kind panels). Lets the coordinator present a current-vs-suggested
|
|
218
|
+
* diff to the user before any overwrite. Always populated (dry-run and write).
|
|
219
|
+
*/
|
|
220
|
+
currentConfig: MeshInitCurrentConfigEcho;
|
|
171
221
|
note: string;
|
|
172
222
|
}
|
|
173
223
|
|
|
@@ -210,18 +260,58 @@ export function runMeshInit(
|
|
|
210
260
|
overwrite,
|
|
211
261
|
});
|
|
212
262
|
|
|
263
|
+
// change-impact classification (.adhdev/change-impact.json). suggest/validate/save
|
|
264
|
+
// mirror refine's engine exactly, so it drops into the same applyConfigSuggestion
|
|
265
|
+
// gate: existing-wins unless overwrite, dry-run by default, validated before write.
|
|
266
|
+
const changeImpactLoaded = loadChangeImpactConfig(workspace);
|
|
267
|
+
const changeImpact = applyConfigSuggestion({
|
|
268
|
+
workspace,
|
|
269
|
+
relativePath: MESH_INIT_CHANGE_IMPACT_CONFIG_PATH,
|
|
270
|
+
existing: changeImpactLoaded.sourceType === 'repo_file' ? changeImpactLoaded.config : undefined,
|
|
271
|
+
suggestedConfig: suggestChangeImpactConfig(workspace).suggestedConfig,
|
|
272
|
+
validate: (config) => validateChangeImpactConfig(config, MESH_INIT_CHANGE_IMPACT_CONFIG_PATH).valid,
|
|
273
|
+
write,
|
|
274
|
+
overwrite,
|
|
275
|
+
});
|
|
276
|
+
|
|
213
277
|
const providers = suggestNodeProviderPriority(detected);
|
|
214
278
|
|
|
279
|
+
// Read-only echo of the currently-saved config per domain so the coordinator can
|
|
280
|
+
// present a current-vs-suggested diff before any overwrite (init vs reinit). This
|
|
281
|
+
// never suggests — it reports what is on disk / machine-local right now.
|
|
282
|
+
const refineLoaded = loadMeshRefineConfig(mesh, workspace);
|
|
283
|
+
const bootstrapLoaded = loadMeshWorktreeBootstrapConfig(mesh, workspace);
|
|
284
|
+
let magiKindPanels: MagiKindPanelMap = {};
|
|
285
|
+
try {
|
|
286
|
+
magiKindPanels = listMagiKindPanels();
|
|
287
|
+
} catch {
|
|
288
|
+
// Machine-local config unreadable — echo an empty binding rather than failing init.
|
|
289
|
+
magiKindPanels = {};
|
|
290
|
+
}
|
|
291
|
+
const currentConfig: MeshInitCurrentConfigEcho = {
|
|
292
|
+
refine: refineLoaded.config,
|
|
293
|
+
worktreeBootstrap: bootstrapLoaded.config,
|
|
294
|
+
changeImpact: changeImpactLoaded.sourceType === 'repo_file' ? changeImpactLoaded.config : undefined,
|
|
295
|
+
sourceTypes: {
|
|
296
|
+
refine: refineLoaded.sourceType,
|
|
297
|
+
worktreeBootstrap: bootstrapLoaded.sourceType,
|
|
298
|
+
changeImpact: changeImpactLoaded.sourceType,
|
|
299
|
+
},
|
|
300
|
+
magiKindPanels,
|
|
301
|
+
};
|
|
302
|
+
|
|
215
303
|
return {
|
|
216
304
|
success: true,
|
|
217
305
|
workspace,
|
|
218
306
|
dryRun: !write,
|
|
219
307
|
refine,
|
|
220
308
|
worktreeBootstrap,
|
|
309
|
+
changeImpact,
|
|
221
310
|
providers,
|
|
311
|
+
currentConfig,
|
|
222
312
|
note: write
|
|
223
|
-
? 'Configs written to disk are the execution source of truth; suggestions are scaffold and only take effect once saved. providerPriority is a recommendation — apply it to node policy via clone/policy update.'
|
|
224
|
-
: 'Dry-run: no files written. Re-run with write=true to persist the suggested configs. Heuristic suggestions never execute until saved as repo config.',
|
|
313
|
+
? 'Configs written to disk are the execution source of truth; suggestions are scaffold and only take effect once saved. providerPriority is a recommendation — apply it to node policy via clone/policy update. currentConfig echoes what was on disk before this run.'
|
|
314
|
+
: 'Dry-run: no files written. Re-run with write=true to persist the suggested configs. Heuristic suggestions never execute until saved as repo config. Use currentConfig to diff current-vs-suggested before overwriting.',
|
|
225
315
|
};
|
|
226
316
|
}
|
|
227
317
|
|
|
@@ -234,7 +324,7 @@ function applyConfigSuggestion(input: {
|
|
|
234
324
|
workspace: string;
|
|
235
325
|
relativePath: string;
|
|
236
326
|
existing?: unknown;
|
|
237
|
-
suggestedConfig?: RepoMeshRefineConfig | RepoMeshWorktreeBootstrapConfig;
|
|
327
|
+
suggestedConfig?: RepoMeshRefineConfig | RepoMeshWorktreeBootstrapConfig | ChangeImpactConfig;
|
|
238
328
|
validate: (config: unknown) => boolean;
|
|
239
329
|
write: boolean;
|
|
240
330
|
overwrite: boolean;
|
|
@@ -2813,7 +2813,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2813
2813
|
typeof data.providerSessionId === 'string' ? data.providerSessionId : '',
|
|
2814
2814
|
);
|
|
2815
2815
|
if (patchedProviderSessionId) {
|
|
2816
|
-
|
|
2816
|
+
// A provider-response id is authoritative when it carries an
|
|
2817
|
+
// explicit `new_session` marker (the CLI genuinely started a new
|
|
2818
|
+
// conversation). Without that marker it's just an observed id and
|
|
2819
|
+
// must not hijack an existing binding (see promoteProviderSessionId).
|
|
2820
|
+
this.promoteProviderSessionId(patchedProviderSessionId, {
|
|
2821
|
+
authoritative: data.sessionEvent === 'new_session',
|
|
2822
|
+
});
|
|
2817
2823
|
}
|
|
2818
2824
|
|
|
2819
2825
|
if (data.sessionEvent === 'new_session') {
|
|
@@ -3167,10 +3173,26 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3167
3173
|
return lines.join('\n');
|
|
3168
3174
|
}
|
|
3169
3175
|
|
|
3170
|
-
private promoteProviderSessionId(sessionId: string): void {
|
|
3176
|
+
private promoteProviderSessionId(sessionId: string, opts: { authoritative?: boolean } = {}): void {
|
|
3171
3177
|
const nextSessionId = String(sessionId || '').trim();
|
|
3172
3178
|
if (!nextSessionId || nextSessionId === this.providerSessionId) return;
|
|
3173
3179
|
|
|
3180
|
+
// Sticky binding: once this instance is bound to a provider session,
|
|
3181
|
+
// an *observed* id (one discovered from a status parse or the native
|
|
3182
|
+
// history reader) must NOT hijack the live binding. hermes ≥0.14
|
|
3183
|
+
// spawns a fresh `sessions` row per internal sub-session, so a
|
|
3184
|
+
// newest-wins native read surfaces a different id mid-turn on every
|
|
3185
|
+
// poll; accepting it would re-bind the instance, re-hydrate unbounded
|
|
3186
|
+
// history (daemon saturation) and reset completion detection so the
|
|
3187
|
+
// turn never finalizes. Only an *authoritative* change — the first
|
|
3188
|
+
// bind (no id yet) or an explicit provider `new_session`/resume — may
|
|
3189
|
+
// replace an existing binding. Legitimate resume/new-session paths
|
|
3190
|
+
// pass authoritative:true and are unaffected.
|
|
3191
|
+
if (this.providerSessionId && !opts.authoritative) {
|
|
3192
|
+
LOG.debug('CLI', `[${this.type}] ignoring non-authoritative session id ${nextSessionId} (bound to ${this.providerSessionId})`);
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3174
3196
|
const previousHistorySessionId = this.providerSessionId || this.instanceId;
|
|
3175
3197
|
const previousProviderSessionId = this.providerSessionId;
|
|
3176
3198
|
this.providerSessionId = nextSessionId;
|
|
@@ -66,7 +66,7 @@ export function createNativeHistoryDispatcher(reader: ReaderId): (input: NativeH
|
|
|
66
66
|
try { fs.statSync(sourcePath); } catch { /* best-effort metadata refresh */ }
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const session = readByReader(reader, sourcePath, sessionId, workspace);
|
|
69
|
+
const session = readByReader(reader, sourcePath, sessionId, workspace, requestedProviderSid);
|
|
70
70
|
if (!session) return null;
|
|
71
71
|
|
|
72
72
|
if (requestedProviderSid && session.providerSessionId && session.providerSessionId !== requestedProviderSid) {
|
|
@@ -265,12 +265,18 @@ function readByReader(
|
|
|
265
265
|
sourcePath: string,
|
|
266
266
|
sessionId: string,
|
|
267
267
|
workspace: string,
|
|
268
|
+
requestedProviderSid: string,
|
|
268
269
|
): any | null {
|
|
269
270
|
switch (reader) {
|
|
270
271
|
case 'claude-cli': return readClaudeCliSession(sourcePath);
|
|
271
272
|
case 'codex-cli': return readCodexCliSession(sourcePath);
|
|
272
273
|
case 'antigravity-cli': return readAntigravityCliSession(sourcePath, sessionId || undefined, workspace || undefined);
|
|
273
|
-
|
|
274
|
+
// hermes reads a *shared* state.db and would otherwise pick the newest
|
|
275
|
+
// source='cli' session, which drifts every read (hermes ≥0.14 writes a
|
|
276
|
+
// fresh row per internal sub-session). Pass the bound id so it reads
|
|
277
|
+
// THAT session directly instead of newest-wins. claude/codex resolve a
|
|
278
|
+
// per-session file upstream, so they need no equivalent pin here.
|
|
279
|
+
case 'hermes-cli': return readHermesCliSession(sourcePath, requestedProviderSid || undefined);
|
|
274
280
|
}
|
|
275
281
|
}
|
|
276
282
|
|
|
@@ -93,26 +93,42 @@ function loadMessagesForSession(db: any, sessionId: string): NativeHistoryMessag
|
|
|
93
93
|
return out;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
export function readSession(sessionPath: string): NativeHistorySession | null {
|
|
96
|
+
export function readSession(sessionPath: string, requestedSessionId?: string): NativeHistorySession | null {
|
|
97
97
|
if (!sessionPath) return null;
|
|
98
98
|
|
|
99
|
-
// Path mode A: SQLite — sourcePath is the state.db path.
|
|
100
|
-
// newest source='cli' session that has at least one persisted message.
|
|
99
|
+
// Path mode A: SQLite — sourcePath is the state.db path.
|
|
101
100
|
if (sessionPath === HERMES_STATE_DB) {
|
|
102
101
|
const db = openDb();
|
|
103
102
|
if (!db) return null;
|
|
104
103
|
try {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
const pinned = String(requestedSessionId || '').trim();
|
|
105
|
+
let sessionId: string;
|
|
106
|
+
if (pinned) {
|
|
107
|
+
// Session pin: the caller is already bound to a specific
|
|
108
|
+
// provider session, so read THAT session directly instead of
|
|
109
|
+
// the newest-wins pick below. hermes ≥0.14 writes a fresh
|
|
110
|
+
// `sessions` row per internal sub-session, so an unpinned
|
|
111
|
+
// `ORDER BY started_at DESC LIMIT 1` drifts to a different id
|
|
112
|
+
// on every read → re-bind churn + reading completion evidence
|
|
113
|
+
// from the wrong session. loadMessagesForSession returning
|
|
114
|
+
// rows validates the id exists.
|
|
115
|
+
sessionId = pinned;
|
|
116
|
+
} else {
|
|
117
|
+
// No bound id yet (discovery): pick the newest source='cli'
|
|
118
|
+
// session that has at least one persisted message.
|
|
119
|
+
const row: any = db.prepare(
|
|
120
|
+
`SELECT id, started_at FROM sessions
|
|
121
|
+
WHERE source = 'cli' AND message_count > 0
|
|
122
|
+
ORDER BY started_at DESC LIMIT 1`,
|
|
123
|
+
).get();
|
|
124
|
+
if (!row) return null;
|
|
125
|
+
sessionId = String(row.id);
|
|
126
|
+
}
|
|
127
|
+
const messages = loadMessagesForSession(db, sessionId);
|
|
112
128
|
if (messages.length === 0) return null;
|
|
113
129
|
return {
|
|
114
130
|
messages,
|
|
115
|
-
providerSessionId:
|
|
131
|
+
providerSessionId: sessionId,
|
|
116
132
|
source: 'provider-native',
|
|
117
133
|
sourcePath: sessionPath,
|
|
118
134
|
sourceMtimeMs: statMtimeMs(sessionPath),
|