@adhdev/daemon-core 0.9.82-rc.160 → 0.9.82-rc.161
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapter-types.d.ts +14 -1
- package/dist/commands/mesh-coordinator.d.ts +72 -1
- package/dist/config/chat-history.d.ts +2 -0
- package/dist/config/mesh-config.d.ts +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +4923 -1411
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4975 -1476
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +30 -0
- package/dist/mesh/coordinator-registry.d.ts +35 -1
- package/dist/providers/cli-provider-instance.d.ts +1 -1
- package/dist/providers/contracts.d.ts +48 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +1 -1
- package/dist/providers/native-history/claude-cli-transcript.d.ts +1 -1
- package/dist/providers/native-history/codex-cli-transcript.d.ts +1 -1
- package/dist/providers/native-history/dispatcher.d.ts +24 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +2 -0
- package/dist/providers/spec/adapter.d.ts +56 -0
- package/dist/providers/spec/cli-adapter.d.ts +76 -0
- package/dist/providers/spec/driver.d.ts +148 -0
- package/dist/providers/spec/evaluator.d.ts +47 -0
- package/dist/providers/spec/loader.d.ts +14 -0
- package/dist/providers/spec/native-history-executor.d.ts +39 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +507 -0
- package/dist/providers/spec/types.d.ts +211 -0
- package/dist/repo-mesh-types.d.ts +33 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/package.json +2 -1
- package/src/cli-adapter-types.ts +15 -1
- package/src/commands/chat-commands.ts +150 -12
- package/src/commands/cli-manager.ts +11 -0
- package/src/commands/mesh-coordinator.ts +235 -1
- package/src/commands/router.ts +238 -50
- package/src/config/chat-history.ts +11 -3
- package/src/config/mesh-config.ts +16 -1
- package/src/index.ts +19 -0
- package/src/mesh/coordinator-prompt.ts +164 -8
- package/src/mesh/coordinator-registry.ts +50 -4
- package/src/providers/cli-provider-instance.ts +8 -3
- package/src/providers/contracts.ts +53 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +2 -2
- package/src/providers/native-history/claude-cli-transcript.ts +1 -1
- package/src/providers/native-history/codex-cli-transcript.ts +1 -1
- package/src/providers/native-history/dispatcher.ts +227 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +7 -0
- package/src/providers/provider-loader.ts +126 -3
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +13 -0
- package/src/providers/spec/adapter.ts +153 -0
- package/src/providers/spec/cli-adapter.ts +318 -0
- package/src/providers/spec/driver.ts +498 -0
- package/src/providers/spec/evaluator.ts +268 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +612 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +507 -0
- package/src/providers/spec/schema.json +210 -0
- package/src/providers/spec/types.ts +230 -0
- package/src/repo-mesh-types.ts +33 -1
- package/src/sessions/registry.ts +3 -0
|
@@ -1421,6 +1421,8 @@ function callProviderNativeHistoryRead(
|
|
|
1421
1421
|
historySessionId: string | undefined,
|
|
1422
1422
|
workspace?: string,
|
|
1423
1423
|
excludeInProgressTurn?: boolean,
|
|
1424
|
+
sessionStartedAtMs?: number,
|
|
1425
|
+
envOverrides?: Record<string, string>,
|
|
1424
1426
|
): ProviderNativeHistoryReadResult | null {
|
|
1425
1427
|
const fn = getProviderNativeHistoryScript(scripts, canonicalHistory, 'readSession');
|
|
1426
1428
|
if (!fn) return null;
|
|
@@ -1433,7 +1435,9 @@ function callProviderNativeHistoryRead(
|
|
|
1433
1435
|
format: canonicalHistory?.format,
|
|
1434
1436
|
watchPath: canonicalHistory?.watchPath,
|
|
1435
1437
|
excludeInProgressTurn: excludeInProgressTurn === true,
|
|
1436
|
-
|
|
1438
|
+
sessionStartedAtMs,
|
|
1439
|
+
envOverrides,
|
|
1440
|
+
args: { sessionId: normalizedSessionId, historySessionId: normalizedSessionId, workspace, excludeInProgressTurn: excludeInProgressTurn === true, sessionStartedAtMs, envOverrides },
|
|
1437
1441
|
});
|
|
1438
1442
|
if (!result || typeof result !== 'object') return null;
|
|
1439
1443
|
const records = normalizeProviderNativeHistoryRecords(agentType, normalizedSessionId, (result as any).messages || (result as any).records);
|
|
@@ -1456,11 +1460,13 @@ function buildNativeHistoryReadResult(
|
|
|
1456
1460
|
historySessionId: string | undefined,
|
|
1457
1461
|
workspace?: string,
|
|
1458
1462
|
excludeInProgressTurn?: boolean,
|
|
1463
|
+
sessionStartedAtMs?: number,
|
|
1464
|
+
envOverrides?: Record<string, string>,
|
|
1459
1465
|
): ProviderNativeHistoryReadResult | null {
|
|
1460
1466
|
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
|
|
1461
1467
|
const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
|
|
1462
1468
|
if (!canonicalHistory || (!normalizedSessionId && !normalizedWorkspace) || !isNativeSourceCanonicalHistory(canonicalHistory)) return null;
|
|
1463
|
-
return callProviderNativeHistoryRead(agentType, canonicalHistory, scripts, normalizedSessionId, workspace, excludeInProgressTurn);
|
|
1469
|
+
return callProviderNativeHistoryRead(agentType, canonicalHistory, scripts, normalizedSessionId, workspace, excludeInProgressTurn, sessionStartedAtMs, envOverrides);
|
|
1464
1470
|
}
|
|
1465
1471
|
|
|
1466
1472
|
function materializeNativeHistoryToMirror(
|
|
@@ -1517,6 +1523,8 @@ export function readProviderChatHistory(
|
|
|
1517
1523
|
historyBehavior?: ProviderHistoryBehavior;
|
|
1518
1524
|
scripts?: ProviderNativeHistoryScripts;
|
|
1519
1525
|
excludeInProgressTurn?: boolean;
|
|
1526
|
+
sessionStartedAtMs?: number;
|
|
1527
|
+
envOverrides?: Record<string, string>;
|
|
1520
1528
|
} = {},
|
|
1521
1529
|
): {
|
|
1522
1530
|
messages: HistoryMessage[];
|
|
@@ -1530,7 +1538,7 @@ export function readProviderChatHistory(
|
|
|
1530
1538
|
unavailableReason?: string;
|
|
1531
1539
|
} {
|
|
1532
1540
|
if (isNativeSourceCanonicalHistory(options.canonicalHistory) && (options.historySessionId || options.workspace)) {
|
|
1533
|
-
const nativeResult = buildNativeHistoryReadResult(agentType, options.canonicalHistory, options.scripts, options.historySessionId, options.workspace, options.excludeInProgressTurn);
|
|
1541
|
+
const nativeResult = buildNativeHistoryReadResult(agentType, options.canonicalHistory, options.scripts, options.historySessionId, options.workspace, options.excludeInProgressTurn, options.sessionStartedAtMs, options.envOverrides);
|
|
1534
1542
|
if (!nativeResult) return { messages: [], hasMore: false, source: 'native-unavailable' };
|
|
1535
1543
|
return {
|
|
1536
1544
|
...pageHistoryRecords(agentType, nativeResult.records, options.offset || 0, options.limit || 30, options.excludeRecentCount || 0, options.historyBehavior),
|
|
@@ -484,7 +484,14 @@ export function removeNode(meshId: string, nodeId: string): boolean {
|
|
|
484
484
|
export function updateNode(
|
|
485
485
|
meshId: string,
|
|
486
486
|
nodeId: string,
|
|
487
|
-
opts: {
|
|
487
|
+
opts: {
|
|
488
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
489
|
+
policy?: RepoMeshNodePolicy;
|
|
490
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
491
|
+
/** Per-node instruction surfaced in the coordinator prompt. Pass an
|
|
492
|
+
* empty string or undefined to clear it. */
|
|
493
|
+
systemPrompt?: string;
|
|
494
|
+
},
|
|
488
495
|
): LocalMeshNodeEntry | undefined {
|
|
489
496
|
const config = loadMeshConfig();
|
|
490
497
|
const mesh = config.meshes.find(m => m.id === meshId);
|
|
@@ -496,6 +503,14 @@ export function updateNode(
|
|
|
496
503
|
if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
|
|
497
504
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
498
505
|
if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
|
|
506
|
+
if (Object.prototype.hasOwnProperty.call(opts, 'systemPrompt')) {
|
|
507
|
+
// Honor explicit clears: { systemPrompt: undefined } drops the field.
|
|
508
|
+
if (opts.systemPrompt && opts.systemPrompt.trim()) {
|
|
509
|
+
node.systemPrompt = opts.systemPrompt;
|
|
510
|
+
} else {
|
|
511
|
+
delete node.systemPrompt;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
499
514
|
mesh.updatedAt = new Date().toISOString();
|
|
500
515
|
saveMeshConfig(config);
|
|
501
516
|
return node;
|
package/src/index.ts
CHANGED
|
@@ -467,6 +467,25 @@ export {
|
|
|
467
467
|
type IpcStatusPayload,
|
|
468
468
|
} from './ipc/local-ipc-server.js';
|
|
469
469
|
|
|
470
|
+
// ── CLI Spec (adhdev:cli/spec@1) ──
|
|
471
|
+
export { evaluate as evaluateSpec, evaluate } from './providers/spec/evaluator.js';
|
|
472
|
+
export { loadSpec, resolveSpecPath } from './providers/spec/loader.js';
|
|
473
|
+
export { createNativeHistoryDispatcher } from './providers/native-history/index.js';
|
|
474
|
+
export type { ReaderId } from './providers/native-history/index.js';
|
|
475
|
+
export {
|
|
476
|
+
readClaudeCliSession, readCodexCliSession,
|
|
477
|
+
readAntigravityCliSession, readHermesCliSession,
|
|
478
|
+
} from './providers/native-history/index.js';
|
|
479
|
+
export type {
|
|
480
|
+
CliSpec, SpecState, ControlAction, Control,
|
|
481
|
+
NotificationRule, DelegateTrigger, Section,
|
|
482
|
+
} from './providers/spec/types.js';
|
|
483
|
+
export type { SpecEvaluation, TraceEntry } from './providers/spec/evaluator.js';
|
|
484
|
+
export { SpecDriver } from './providers/spec/driver.js';
|
|
485
|
+
export type { DashboardEvent, DashboardCommand, SpecDriverOpts } from './providers/spec/driver.js';
|
|
486
|
+
export { TerminalAdapter } from './providers/spec/adapter.js';
|
|
487
|
+
export type { TerminalAdapterOpts, TerminalAdapterHandlers } from './providers/spec/adapter.js';
|
|
488
|
+
|
|
470
489
|
// ── Provider SDK (v1) — selective re-exports for external tooling ──
|
|
471
490
|
// Tooling (registry publish, dashboard validators, the e2e harness) needs
|
|
472
491
|
// the manifest validator, the builder catalog, and the contract version.
|
|
@@ -8,8 +8,23 @@
|
|
|
8
8
|
* 3. How to orchestrate work across nodes
|
|
9
9
|
*
|
|
10
10
|
* The prompt is generated dynamically from the current mesh state.
|
|
11
|
+
*
|
|
12
|
+
* User customization:
|
|
13
|
+
* ~/.adhdev/coordinator-prompts/<cliType>.md — full override
|
|
14
|
+
* ~/.adhdev/coordinator-prompts/<cliType>.append.md — appended to default
|
|
15
|
+
* ~/.adhdev/coordinator-prompts/default.md — full override (any CLI)
|
|
16
|
+
* ~/.adhdev/coordinator-prompts/default.append.md — appended to default (any CLI)
|
|
17
|
+
*
|
|
18
|
+
* CLI-specific files take precedence over default.* files. The override file
|
|
19
|
+
* still gets the node/policy facts substituted via the same {{placeholders}}
|
|
20
|
+
* the daemon understands; an override that doesn't reference them just gets
|
|
21
|
+
* a static prompt, which is also fine.
|
|
11
22
|
*/
|
|
12
23
|
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as os from 'node:os';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
|
|
13
28
|
import type {
|
|
14
29
|
LocalMeshEntry,
|
|
15
30
|
RepoMeshPolicy,
|
|
@@ -27,8 +42,68 @@ export interface CoordinatorPromptContext {
|
|
|
27
42
|
coordinatorCliType?: string;
|
|
28
43
|
}
|
|
29
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Compose the final coordinator prompt from four layers, in this precedence:
|
|
47
|
+
*
|
|
48
|
+
* 1. Per-launch `extraSystemPrompt` (always appended, as "## Additional
|
|
49
|
+
* Context"). Never wins as a base — it's launch-scope context.
|
|
50
|
+
* 2. Mesh-level append (`mesh.coordinator.systemPromptAppend` or the legacy
|
|
51
|
+
* `systemPromptSuffix`). Stacks after whichever base won.
|
|
52
|
+
* 3. User-file append (`~/.adhdev/coordinator-prompts/<cli>.append.md` or
|
|
53
|
+
* `default.append.md`). Also stacks; same placeholder expansion as the
|
|
54
|
+
* override path.
|
|
55
|
+
* 4. Base prompt, picked in this order:
|
|
56
|
+
* a. `mesh.coordinator.systemPromptOverride` (mesh-level override)
|
|
57
|
+
* b. user-file override (`~/.adhdev/coordinator-prompts/<cli>.md` or
|
|
58
|
+
* `default.md`)
|
|
59
|
+
* c. daemon default (assembled from identity/nodes/policy/tools/…)
|
|
60
|
+
*
|
|
61
|
+
* That layering lets a user customize prompts at three increasing scopes
|
|
62
|
+
* (machine, mesh, single launch) without losing the daemon's stock rules.
|
|
63
|
+
*/
|
|
30
64
|
export function buildCoordinatorSystemPrompt(ctx: CoordinatorPromptContext): string {
|
|
31
|
-
const { mesh,
|
|
65
|
+
const { mesh, userInstruction, coordinatorCliType } = ctx;
|
|
66
|
+
|
|
67
|
+
// ── Pick the base prompt ──
|
|
68
|
+
const meshOverride = mesh.coordinator?.systemPromptOverride?.trim();
|
|
69
|
+
let base: string;
|
|
70
|
+
if (meshOverride) {
|
|
71
|
+
base = expandPromptPlaceholders(meshOverride, ctx);
|
|
72
|
+
} else {
|
|
73
|
+
const userOverride = readUserPromptFile(coordinatorCliType, 'md');
|
|
74
|
+
if (userOverride !== null) {
|
|
75
|
+
base = expandPromptPlaceholders(userOverride, ctx);
|
|
76
|
+
} else {
|
|
77
|
+
base = buildDefaultCoordinatorPrompt(ctx);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sections: string[] = [base];
|
|
82
|
+
|
|
83
|
+
// ── User-level append runs after whichever base won ──
|
|
84
|
+
const userAppend = readUserPromptFile(coordinatorCliType, 'append.md');
|
|
85
|
+
if (userAppend !== null) {
|
|
86
|
+
sections.push(expandPromptPlaceholders(userAppend, ctx));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Mesh-level append (prefer the new field, fall back to the legacy alias) ──
|
|
90
|
+
const meshAppend = (mesh.coordinator?.systemPromptAppend
|
|
91
|
+
?? mesh.coordinator?.systemPromptSuffix)?.trim();
|
|
92
|
+
if (meshAppend) {
|
|
93
|
+
sections.push(expandPromptPlaceholders(meshAppend, ctx));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Per-launch context lands last so it's the most recent thing the
|
|
97
|
+
// agent reads. Marked as Additional Context, not a rule update. ──
|
|
98
|
+
if (userInstruction) {
|
|
99
|
+
sections.push(`## Additional Context\n${userInstruction}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return sections.join('\n\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildDefaultCoordinatorPrompt(ctx: CoordinatorPromptContext): string {
|
|
106
|
+
const { mesh, status, coordinatorCliType } = ctx;
|
|
32
107
|
const sections: string[] = [];
|
|
33
108
|
|
|
34
109
|
// ── Identity ──
|
|
@@ -61,16 +136,77 @@ Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `\nDefault branch: \`
|
|
|
61
136
|
// ── Rules ──
|
|
62
137
|
sections.push(buildRulesSection(coordinatorCliType));
|
|
63
138
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
sections.push(`## Additional Context\n${userInstruction}`);
|
|
67
|
-
}
|
|
139
|
+
return sections.join('\n\n');
|
|
140
|
+
}
|
|
68
141
|
|
|
69
|
-
|
|
70
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Look up a user-customization file under ~/.adhdev/coordinator-prompts/.
|
|
144
|
+
*
|
|
145
|
+
* Lookup order:
|
|
146
|
+
* 1. <cliType>.<suffix> — provider-specific
|
|
147
|
+
* 2. default.<suffix> — shared across providers
|
|
148
|
+
*
|
|
149
|
+
* Returns null when neither exists; an empty/whitespace-only file is also
|
|
150
|
+
* treated as "no override" so users can drop in a stub without affecting
|
|
151
|
+
* behavior. Read errors (permission, IO) are swallowed and logged-as-null
|
|
152
|
+
* intentionally: a broken override file should never block coordinator
|
|
153
|
+
* launch, it should just behave as if the file weren't there.
|
|
154
|
+
*/
|
|
155
|
+
function readUserPromptFile(cliType: string | undefined, suffix: string): string | null {
|
|
156
|
+
const dir = path.join(os.homedir(), '.adhdev', 'coordinator-prompts');
|
|
157
|
+
const candidates: string[] = [];
|
|
158
|
+
if (cliType) candidates.push(path.join(dir, `${cliType}.${suffix}`));
|
|
159
|
+
candidates.push(path.join(dir, `default.${suffix}`));
|
|
160
|
+
for (const p of candidates) {
|
|
161
|
+
try {
|
|
162
|
+
const text = fs.readFileSync(p, 'utf8');
|
|
163
|
+
if (text.trim()) return text;
|
|
164
|
+
} catch { /* missing file is the common case — keep going */ }
|
|
71
165
|
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
72
168
|
|
|
73
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Expand `{{placeholder}}` tokens against current mesh state.
|
|
171
|
+
*
|
|
172
|
+
* Tokens we support today:
|
|
173
|
+
* {{meshName}} — mesh.name
|
|
174
|
+
* {{repo}} — mesh.repoIdentity
|
|
175
|
+
* {{defaultBranch}} — mesh.defaultBranch or empty
|
|
176
|
+
* {{cliType}} — coordinator CLI type or empty
|
|
177
|
+
* {{nodes}} — full node section (status if known, otherwise config)
|
|
178
|
+
* {{policy}} — full policy section
|
|
179
|
+
* {{tools}} — the canonical tools table
|
|
180
|
+
* {{workflow}} — the canonical orchestration workflow
|
|
181
|
+
* {{rules}} — the canonical rules section (with coordinatorNote)
|
|
182
|
+
* {{toolExposurePreflight}} — the MCP-missing preflight reminder
|
|
183
|
+
*
|
|
184
|
+
* Unknown tokens are left as-is — that way typos are obvious in the rendered
|
|
185
|
+
* prompt instead of silently disappearing. Tokens are not recursive: an
|
|
186
|
+
* expanded value's own {{...}} content stays literal.
|
|
187
|
+
*/
|
|
188
|
+
function expandPromptPlaceholders(template: string, ctx: CoordinatorPromptContext): string {
|
|
189
|
+
const { mesh, status, coordinatorCliType } = ctx;
|
|
190
|
+
const nodesSection = status?.nodes?.length
|
|
191
|
+
? buildNodeStatusSection(status.nodes)
|
|
192
|
+
: mesh.nodes.length
|
|
193
|
+
? buildNodeConfigSection(mesh)
|
|
194
|
+
: '## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.';
|
|
195
|
+
const replacements: Record<string, string> = {
|
|
196
|
+
meshName: mesh.name,
|
|
197
|
+
repo: mesh.repoIdentity,
|
|
198
|
+
defaultBranch: mesh.defaultBranch || '',
|
|
199
|
+
cliType: coordinatorCliType || '',
|
|
200
|
+
nodes: nodesSection,
|
|
201
|
+
policy: buildPolicySection({ ...DEFAULT_MESH_POLICY, ...(mesh.policy || {}) }),
|
|
202
|
+
tools: TOOLS_SECTION,
|
|
203
|
+
workflow: WORKFLOW_SECTION,
|
|
204
|
+
rules: buildRulesSection(coordinatorCliType),
|
|
205
|
+
toolExposurePreflight: TOOL_EXPOSURE_PREFLIGHT_SECTION,
|
|
206
|
+
};
|
|
207
|
+
return template.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g, (m, key) => {
|
|
208
|
+
return Object.prototype.hasOwnProperty.call(replacements, key) ? replacements[key] : m;
|
|
209
|
+
});
|
|
74
210
|
}
|
|
75
211
|
|
|
76
212
|
// ─── Section Builders ───────────────────────────
|
|
@@ -97,6 +233,10 @@ function buildNodeStatusSection(nodes: RepoMeshNodeStatus[]): string {
|
|
|
97
233
|
lines.push(`- ${healthIcon} **${n.machineLabel}** (nodeId: \`${n.nodeId}\`)`);
|
|
98
234
|
lines.push(` workspace: \`${n.workspace}\`${context ? ` | ${context}` : ''} | ${branch} | ${sessions}`);
|
|
99
235
|
if (n.error) lines.push(` ⚠️ ${n.error}`);
|
|
236
|
+
const nodePrompt = typeof (n as any).systemPrompt === 'string' ? (n as any).systemPrompt.trim() : '';
|
|
237
|
+
if (nodePrompt) {
|
|
238
|
+
lines.push(` 📌 Node instruction: ${indentFollowing(nodePrompt, ' ')}`);
|
|
239
|
+
}
|
|
100
240
|
}
|
|
101
241
|
return lines.join('\n');
|
|
102
242
|
}
|
|
@@ -117,11 +257,26 @@ function buildNodeConfigSection(mesh: LocalMeshEntry): string {
|
|
|
117
257
|
const explicitLabel = explicitMachineLabel ? ` label: **${explicitMachineLabel}** |` : '';
|
|
118
258
|
const providerPriority = n.policy?.providerPriority?.length ? ` | providers: ${n.policy.providerPriority.join(', ')}` : '';
|
|
119
259
|
lines.push(`- ${explicitLabel} nodeId: \`${n.id}\` | workspace: \`${n.workspace}\`${n.daemonId ? ` | daemon: \`${n.daemonId}\`` : ''}${providerPriority}${suffix}`);
|
|
260
|
+
const nodePrompt = typeof (n as any).systemPrompt === 'string' ? (n as any).systemPrompt.trim() : '';
|
|
261
|
+
if (nodePrompt) {
|
|
262
|
+
lines.push(` 📌 Node instruction: ${indentFollowing(nodePrompt, ' ')}`);
|
|
263
|
+
}
|
|
120
264
|
}
|
|
121
265
|
lines.push('', '_Use `mesh_status` to probe live health before delegating work._');
|
|
122
266
|
return lines.join('\n');
|
|
123
267
|
}
|
|
124
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Indent every line after the first by `pad`. Used so multi-line node
|
|
271
|
+
* instructions still visually nest under the node bullet without the
|
|
272
|
+
* second line dangling at column zero.
|
|
273
|
+
*/
|
|
274
|
+
function indentFollowing(text: string, pad: string): string {
|
|
275
|
+
const lines = text.split('\n');
|
|
276
|
+
if (lines.length === 1) return lines[0];
|
|
277
|
+
return [lines[0], ...lines.slice(1).map(l => pad + l)].join('\n');
|
|
278
|
+
}
|
|
279
|
+
|
|
125
280
|
function buildPolicySection(policy: RepoMeshPolicy): string {
|
|
126
281
|
const rules: string[] = [];
|
|
127
282
|
if (policy.requirePreTaskCheckpoint) rules.push('- Create a git checkpoint **before** starting each task');
|
|
@@ -219,6 +374,7 @@ function buildRulesSection(coordinatorCliType?: string): string {
|
|
|
219
374
|
- **Converge branches.** After worktree tasks: refine/fast-forward, or classify as \`pushed_feature_branch_needs_merge\` / \`blocked_review\` / \`cleanup_candidate\` / \`not_mergeable\`. Clean up with \`mesh_remove_node\`.
|
|
220
375
|
- **Refinery is config-driven.** \`mesh_refine_node\` must run validation from \`.adhdev/refine.{json,yaml,yml}\` or \`repo-mesh.refine.*\`. Heuristics are scaffolding only.
|
|
221
376
|
- **Submodule reachability = publish-needed.** \`submodule_reachability_failed\` → classify as \`blocked_review\`, request user approval to push to submodule main, then rerun \`mesh_refine_node\`.
|
|
377
|
+
- **Honor per-node instructions.** When a node carries a 📌 Node instruction in the nodes section, include the relevant parts of that instruction in the task message you send to that node. Don't paraphrase the instruction into your own words — quote it verbatim so the worker agent sees exactly what the user wrote.
|
|
222
378
|
- **Never fabricate tool results.** Always call the actual tool.
|
|
223
379
|
- **Keep the user informed.** One or two sentences after each delegation round.${coordinatorNote}`;
|
|
224
380
|
}
|
|
@@ -17,6 +17,20 @@ export interface CoordinatorRegistryEntry {
|
|
|
17
17
|
sessionId: string;
|
|
18
18
|
workspace?: string;
|
|
19
19
|
startedAt: number;
|
|
20
|
+
/** CLI type used to launch the coordinator (claude-cli / codex-cli / …). */
|
|
21
|
+
cliType?: string;
|
|
22
|
+
/** Final system prompt sent to the coordinator after all overrides, appends,
|
|
23
|
+
* and extraSystemPrompt have been applied. Surfaced via the session-info
|
|
24
|
+
* endpoint so users can audit exactly what prompt the agent saw. */
|
|
25
|
+
systemPrompt?: string;
|
|
26
|
+
/** Per-launch extraSystemPrompt the caller passed in, if any. Stored
|
|
27
|
+
* separately from `systemPrompt` so the UI can show what the user
|
|
28
|
+
* added vs. what the daemon's default template produced. */
|
|
29
|
+
extraSystemPrompt?: string;
|
|
30
|
+
/** How the prompt was actually injected (cli_arg / context_file / …). */
|
|
31
|
+
injection?: { mode: string; target?: string };
|
|
32
|
+
/** Path of the MCP config file the daemon wrote for this session. */
|
|
33
|
+
mcpConfigPath?: string;
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
const _registry = new Map<string, CoordinatorRegistryEntry>();
|
|
@@ -57,11 +71,43 @@ export function registerMeshCoordinator(entry: CoordinatorRegistryEntry): void {
|
|
|
57
71
|
saveRegistry();
|
|
58
72
|
}
|
|
59
73
|
|
|
60
|
-
/** Remove a coordinator session by sessionId. Persists to disk.
|
|
74
|
+
/** Remove a coordinator session by sessionId. Persists to disk.
|
|
75
|
+
*
|
|
76
|
+
* Also best-effort strips any context_file wrapper block we wrote into
|
|
77
|
+
* the workspace at launch time (AGENTS.md / GEMINI.md), because those
|
|
78
|
+
* files are auto-loaded by their CLIs on every subsequent launch — and
|
|
79
|
+
* a wrapper block surviving the coordinator session would silently
|
|
80
|
+
* inject the coordinator system prompt into ordinary non-coordinator
|
|
81
|
+
* sessions in the same workspace, which is exactly the bug we're
|
|
82
|
+
* fixing here.
|
|
83
|
+
*
|
|
84
|
+
* Stripping rules:
|
|
85
|
+
* - Look for the start sentinel anywhere in the file; if absent,
|
|
86
|
+
* leave the file alone (user-authored content).
|
|
87
|
+
* - If the wrapper block was the only thing in the file, delete the
|
|
88
|
+
* file outright so we don't leave behind an empty AGENTS.md.
|
|
89
|
+
* - Otherwise drop only the block between the sentinels and trim a
|
|
90
|
+
* leading/trailing blank line so we don't leave dangling separators.
|
|
91
|
+
*/
|
|
61
92
|
export function unregisterMeshCoordinator(sessionId: string): void {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
const entry = _registry.get(sessionId);
|
|
94
|
+
if (!_registry.delete(sessionId)) return;
|
|
95
|
+
saveRegistry();
|
|
96
|
+
if (!entry) return;
|
|
97
|
+
const workspace = entry.workspace;
|
|
98
|
+
const target = entry.injection?.mode === 'context_file' && typeof entry.injection.target === 'string'
|
|
99
|
+
? entry.injection.target
|
|
100
|
+
: '';
|
|
101
|
+
if (!workspace || !target) return;
|
|
102
|
+
// injection.target is workspace-relative (AGENTS.md / GEMINI.md); we don't
|
|
103
|
+
// resolve absolute paths here because we never wrote one — applyMesh-
|
|
104
|
+
// CoordinatorSystemPromptInjection joins via path.join(workspace, …).
|
|
105
|
+
const filePath = `${workspace.replace(/\/$/, '')}/${target}`;
|
|
106
|
+
try {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
108
|
+
const { stripCoordinatorWrapperFile } = require('../commands/mesh-coordinator.js');
|
|
109
|
+
stripCoordinatorWrapperFile(filePath);
|
|
110
|
+
} catch { /* best-effort cleanup; never throw out of unregister */ }
|
|
65
111
|
}
|
|
66
112
|
|
|
67
113
|
/** Look up a coordinator entry by session ID. Returns undefined if not registered. */
|
|
@@ -15,6 +15,7 @@ import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport }
|
|
|
15
15
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
16
16
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
17
17
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
18
|
+
import { createCliAdapter } from './spec/route.js';
|
|
18
19
|
import type { PtyRuntimeMetadata, PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
19
20
|
import { StatusMonitor } from './status-monitor.js';
|
|
20
21
|
import { ChatHistoryWriter, isNativeSourceCanonicalHistory, materializeProviderNativeHistory, readChatHistory, readProviderChatHistory } from '../config/chat-history.js';
|
|
@@ -389,7 +390,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
389
390
|
this.providerSessionId = options?.providerSessionId;
|
|
390
391
|
this.launchMode = options?.launchMode || 'new';
|
|
391
392
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
392
|
-
this.adapter =
|
|
393
|
+
this.adapter = createCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory) as ProviderCliAdapter;
|
|
393
394
|
if (this.providerSessionId) {
|
|
394
395
|
this.adapter.updateRuntimeMeta({ providerSessionId: this.providerSessionId });
|
|
395
396
|
}
|
|
@@ -727,13 +728,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
727
728
|
};
|
|
728
729
|
}
|
|
729
730
|
|
|
730
|
-
getSessionModalState(): SessionModalState {
|
|
731
|
+
getSessionModalState(sessionId?: string): SessionModalState {
|
|
731
732
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
732
733
|
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
733
734
|
const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
|
|
734
735
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
735
736
|
return {
|
|
736
|
-
|
|
737
|
+
// Honor the caller-supplied sessionId — InstanceMgr rejects the
|
|
738
|
+
// projection when projected.id !== requested sessionId, and
|
|
739
|
+
// this.instanceId is the manager's internal key, not the public
|
|
740
|
+
// sessionId the dashboard subscribes by.
|
|
741
|
+
id: sessionId ?? this.instanceId,
|
|
737
742
|
status: visibleStatus,
|
|
738
743
|
title: dirName,
|
|
739
744
|
activeModal: autoApproveActive ? null : adapterStatus.activeModal,
|
|
@@ -393,8 +393,61 @@ export interface ProviderMeshCoordinatorConfig {
|
|
|
393
393
|
/** Copyable setup template. Supports {{meshId}}, {{adhdevMcpCommand}}, {{workspace}}, {{serverName}}. */
|
|
394
394
|
template?: string;
|
|
395
395
|
};
|
|
396
|
+
/**
|
|
397
|
+
* How the coordinator system prompt reaches the launched CLI. Replaces the
|
|
398
|
+
* old hard-coded `if (cliType === 'claude-cli') push --append-system-prompt`
|
|
399
|
+
* branches in router.ts: a new CLI now ships its injection rule in its
|
|
400
|
+
* provider.v1.json, no daemon code change needed. Users can override the
|
|
401
|
+
* rendered prompt or the injection mechanism per-provider; if omitted, no
|
|
402
|
+
* system prompt is injected (safe default — won't crash spawn with a flag
|
|
403
|
+
* the CLI doesn't recognize).
|
|
404
|
+
*/
|
|
405
|
+
systemPromptInjection?: MeshCoordinatorSystemPromptInjection;
|
|
396
406
|
}
|
|
397
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Declarative description of how a CLI accepts a session-scoped system prompt.
|
|
410
|
+
*
|
|
411
|
+
* Modes:
|
|
412
|
+
* - cli_arg → push `flag` + prompt onto spawn args (Claude)
|
|
413
|
+
* - config_override → push `flag` + a templated key=value config override (Codex)
|
|
414
|
+
* - context_file → write prompt into a workspace markdown the CLI
|
|
415
|
+
* auto-loads as project context (Gemini, Antigravity)
|
|
416
|
+
* - env_var → expose prompt to the spawned process as $name (Hermes)
|
|
417
|
+
*
|
|
418
|
+
* The prompt text is templated with `{prompt}` (raw) or `{prompt_json}`
|
|
419
|
+
* (JSON-encoded for embedding inside config-override strings).
|
|
420
|
+
*/
|
|
421
|
+
export type MeshCoordinatorSystemPromptInjection =
|
|
422
|
+
| {
|
|
423
|
+
mode: 'cli_arg';
|
|
424
|
+
/** Spawn-args flag, e.g. '--append-system-prompt'. The prompt becomes the next argv. */
|
|
425
|
+
flag: string;
|
|
426
|
+
}
|
|
427
|
+
| {
|
|
428
|
+
mode: 'config_override';
|
|
429
|
+
/** Spawn-args flag, e.g. '-c'. Followed by `template` with placeholders rendered. */
|
|
430
|
+
flag: string;
|
|
431
|
+
/** Template using {prompt} or {prompt_json}, e.g. 'developer_instructions={prompt_json}'. */
|
|
432
|
+
template: string;
|
|
433
|
+
}
|
|
434
|
+
| {
|
|
435
|
+
mode: 'context_file';
|
|
436
|
+
/** Workspace-relative file path the CLI auto-loads, e.g. 'AGENTS.md' or 'GEMINI.md'. */
|
|
437
|
+
path: string;
|
|
438
|
+
/**
|
|
439
|
+
* Optional wrapper around the prompt. Use `{prompt}` placeholder. Existing
|
|
440
|
+
* wrapper-delimited blocks are replaced rather than duplicated, so re-launching
|
|
441
|
+
* a coordinator doesn't pile up copies. If omitted, the prompt is appended raw.
|
|
442
|
+
*/
|
|
443
|
+
wrapper?: string;
|
|
444
|
+
}
|
|
445
|
+
| {
|
|
446
|
+
mode: 'env_var';
|
|
447
|
+
/** Env-var name, e.g. 'HERMES_EPHEMERAL_SYSTEM_PROMPT'. */
|
|
448
|
+
name: string;
|
|
449
|
+
};
|
|
450
|
+
|
|
398
451
|
export interface ProviderCompatibilityEntry {
|
|
399
452
|
ideVersion: string;
|
|
400
453
|
scriptDir: string;
|
|
@@ -397,11 +397,11 @@ function parsePbFile(
|
|
|
397
397
|
*
|
|
398
398
|
* Returns `null` when the file is missing, empty, or yields no parseable messages.
|
|
399
399
|
*/
|
|
400
|
-
export
|
|
400
|
+
export function readSession(
|
|
401
401
|
sessionPath: string,
|
|
402
402
|
sessionId?: string,
|
|
403
403
|
workspace?: string,
|
|
404
|
-
):
|
|
404
|
+
): NativeHistorySession | null {
|
|
405
405
|
if (!sessionPath || !path.isAbsolute(sessionPath)) return null;
|
|
406
406
|
if (!fs.existsSync(sessionPath)) return null;
|
|
407
407
|
|
|
@@ -296,7 +296,7 @@ function parseTranscriptFile(
|
|
|
296
296
|
* `sessionPath` is the absolute path to a `<uuid>.jsonl` file.
|
|
297
297
|
* Returns `null` when the file is missing, empty, or yields no parseable messages.
|
|
298
298
|
*/
|
|
299
|
-
export
|
|
299
|
+
export function readSession(sessionPath: string): NativeHistorySession | null {
|
|
300
300
|
if (!sessionPath || !path.isAbsolute(sessionPath)) return null;
|
|
301
301
|
|
|
302
302
|
const basename = path.basename(sessionPath, '.jsonl');
|
|
@@ -302,7 +302,7 @@ function parseSessionFile(
|
|
|
302
302
|
* under ~/.codex/sessions/.
|
|
303
303
|
* Returns `null` when the file is missing, empty, or yields no parseable messages.
|
|
304
304
|
*/
|
|
305
|
-
export
|
|
305
|
+
export function readSession(sessionPath: string): NativeHistorySession | null {
|
|
306
306
|
if (!sessionPath || !path.isAbsolute(sessionPath)) return null;
|
|
307
307
|
if (!fs.existsSync(sessionPath)) return null;
|
|
308
308
|
|