@adhdev/daemon-core 0.9.82-rc.17 → 0.9.82-rc.171
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/boot/process-hardening.d.ts +50 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +22 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +72 -1
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +7 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +71 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +35 -6
- package/dist/index.js +18634 -3760
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18538 -3732
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +30 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +96 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +9 -3
- package/dist/providers/contracts.d.ts +98 -5
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- 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 +13 -0
- package/dist/providers/provider-instance.d.ts +3 -0
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +56 -0
- package/dist/providers/spec/cli-adapter.d.ts +95 -0
- package/dist/providers/spec/driver.d.ts +149 -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 +511 -0
- package/dist/providers/spec/types.d.ts +212 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +100 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types.d.ts +42 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -2
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +17 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +21 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1086 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1597 -60
- package/src/commands/cli-manager.ts +214 -3
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +231 -127
- package/src/commands/router.ts +3574 -502
- package/src/config/chat-history.ts +87 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +264 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +113 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +204 -30
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +879 -61
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +456 -54
- package/src/providers/contracts.ts +105 -5
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- 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 +30 -0
- package/src/providers/provider-instance.ts +3 -0
- package/src/providers/provider-loader.ts +585 -50
- package/src/providers/provider-schema.ts +31 -13
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +458 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +210 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +168 -0
- package/src/providers/spec/cli-adapter.ts +430 -0
- package/src/providers/spec/driver.ts +502 -0
- package/src/providers/spec/evaluator.ts +298 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +623 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +511 -0
- package/src/providers/spec/schema.json +211 -0
- package/src/providers/spec/types.ts +231 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +297 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +110 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mesh contract v2 — first-class identity, scopes, and protocol version.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the implicit conventions that grew across mesh-events.ts,
|
|
5
|
+
* mesh-work-queue.ts, mesh-ledger.ts, and mesh-tools.ts where the same
|
|
6
|
+
* concept (a coordinator, a session, a task status) appeared in different
|
|
7
|
+
* shapes per file. The audit found:
|
|
8
|
+
*
|
|
9
|
+
* - PendingMeshCoordinatorEvent had no targetCoordinatorDaemonId. All
|
|
10
|
+
* events were broadcast to every coordinator that drained them. Two
|
|
11
|
+
* coordinators sharing a mesh would each receive every event,
|
|
12
|
+
* leading to duplicate completion handling and missed targeting.
|
|
13
|
+
* - drainPendingMeshCoordinatorEvents accepted only meshId. The caller's
|
|
14
|
+
* coordinator identity was never available, so per-coordinator
|
|
15
|
+
* routing was impossible by construction.
|
|
16
|
+
* - Session identifier keys diverged across stores: targetSessionId /
|
|
17
|
+
* assignedSessionId / instanceId / runtimeSessionId / providerSessionId.
|
|
18
|
+
* resolveEventSessionId() tried four fallbacks per call.
|
|
19
|
+
* - No protocol version on the JSONL ledger or BeadsDB. Schema
|
|
20
|
+
* evolutions had no guard rail.
|
|
21
|
+
* - mesh_reconcile_ledger existed as a routine recovery tool, not as
|
|
22
|
+
* an incident-response escape hatch. That itself signals the routing
|
|
23
|
+
* layer cannot be trusted.
|
|
24
|
+
*
|
|
25
|
+
* This module introduces the types; the actual wiring lands in B2 (data
|
|
26
|
+
* model + 3-way transactional store), B3 (MCP layer enforcement), and B4
|
|
27
|
+
* (frontend consumption). B1 is intentionally non-breaking — it adds the
|
|
28
|
+
* types and leaves runtime behaviour unchanged.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** Provider type identifier (e.g. 'claude-cli', 'codex-cli', 'roo-code').
|
|
32
|
+
* Free-form string; no shared enum exists in daemon-core yet. */
|
|
33
|
+
type ProviderType = string;
|
|
34
|
+
|
|
35
|
+
// ─── Protocol version ────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export const MESH_PROTOCOL_VERSION_V1 = '1.0' as const;
|
|
38
|
+
export const MESH_PROTOCOL_VERSION_V2 = '2.0' as const;
|
|
39
|
+
|
|
40
|
+
export type MeshProtocolVersion =
|
|
41
|
+
| typeof MESH_PROTOCOL_VERSION_V1
|
|
42
|
+
| typeof MESH_PROTOCOL_VERSION_V2;
|
|
43
|
+
|
|
44
|
+
export const SUPPORTED_MESH_PROTOCOL_VERSIONS: readonly MeshProtocolVersion[] = [
|
|
45
|
+
MESH_PROTOCOL_VERSION_V1,
|
|
46
|
+
MESH_PROTOCOL_VERSION_V2,
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export function isSupportedMeshProtocolVersion(value: unknown): value is MeshProtocolVersion {
|
|
50
|
+
return typeof value === 'string'
|
|
51
|
+
&& (SUPPORTED_MESH_PROTOCOL_VERSIONS as readonly string[]).includes(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Coordinator identity ────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Identity of a mesh coordinator. Required (non-optional) on every dispatch
|
|
58
|
+
* and drain operation in v2 — opaque/missing identity is the audit's
|
|
59
|
+
* smoking gun for routing failures and is structurally banned by the v2
|
|
60
|
+
* types.
|
|
61
|
+
*
|
|
62
|
+
* - daemonId: the machineId of the daemon hosting the coordinator. Stable
|
|
63
|
+
* across coordinator restarts on the same machine.
|
|
64
|
+
* - coordinatorRunId: a UUID generated when the coordinator process starts.
|
|
65
|
+
* Stable for the coordinator's lifetime, fresh on restart. Required so
|
|
66
|
+
* two coordinators on the same daemon (one CLI, one MCP) can be told
|
|
67
|
+
* apart without conflating their drains.
|
|
68
|
+
* - sessionId: optional CLI coordinator session identifier (instanceId).
|
|
69
|
+
* Present when the coordinator is itself a CLI session, absent for
|
|
70
|
+
* pure MCP coordinators.
|
|
71
|
+
*/
|
|
72
|
+
export interface CoordinatorIdentity {
|
|
73
|
+
readonly daemonId: string;
|
|
74
|
+
readonly coordinatorRunId: string;
|
|
75
|
+
readonly sessionId?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function coordinatorIdentityEquals(a: CoordinatorIdentity, b: CoordinatorIdentity): boolean {
|
|
79
|
+
return a.daemonId === b.daemonId
|
|
80
|
+
&& a.coordinatorRunId === b.coordinatorRunId
|
|
81
|
+
&& (a.sessionId ?? '') === (b.sessionId ?? '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function coordinatorIdentityKey(identity: CoordinatorIdentity): string {
|
|
85
|
+
// Stable string form for map keys and ledger payloads. Avoids the
|
|
86
|
+
// {a:b, c:d} JSON.stringify ordering trap by enforcing field order.
|
|
87
|
+
return `${identity.daemonId}|${identity.coordinatorRunId}|${identity.sessionId ?? ''}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Session handle ──────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Unified mesh session identifier. v1 referred to "the session" with five
|
|
94
|
+
* different field names depending on which store you were reading; v2
|
|
95
|
+
* collapses them into one explicit handle that carries every disambiguator
|
|
96
|
+
* a consumer might need.
|
|
97
|
+
*
|
|
98
|
+
* - nodeId: the mesh node this session belongs to (FK into mesh node table).
|
|
99
|
+
* - sessionId: provider-instance identifier (the runtime session id).
|
|
100
|
+
* - providerType: which provider category/type the session runs (e.g.
|
|
101
|
+
* 'claude-cli', 'codex-cli', 'roo-code').
|
|
102
|
+
* - coordinatorDaemonId: which coordinator dispatched the work that
|
|
103
|
+
* spawned this session. Used for completion event routing.
|
|
104
|
+
* - assignedAt: ms epoch when the session was bound to its current task.
|
|
105
|
+
*/
|
|
106
|
+
export interface MeshSessionHandle {
|
|
107
|
+
readonly nodeId: string;
|
|
108
|
+
readonly sessionId: string;
|
|
109
|
+
readonly providerType: ProviderType;
|
|
110
|
+
readonly coordinatorDaemonId: string;
|
|
111
|
+
readonly assignedAt: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function meshSessionHandleKey(handle: MeshSessionHandle): string {
|
|
115
|
+
return `${handle.nodeId}|${handle.sessionId}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Task status (canonical enum) ────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Single canonical task status enum. v1 had at least three overlapping
|
|
122
|
+
* sets (queue / ledger / direct-dispatch). v2 consumers import from here.
|
|
123
|
+
*/
|
|
124
|
+
export const MESH_TASK_STATUSES = [
|
|
125
|
+
'pending',
|
|
126
|
+
'assigned',
|
|
127
|
+
'in_progress',
|
|
128
|
+
'completed',
|
|
129
|
+
'failed',
|
|
130
|
+
'cancelled',
|
|
131
|
+
] as const;
|
|
132
|
+
export type MeshTaskStatus = typeof MESH_TASK_STATUSES[number];
|
|
133
|
+
|
|
134
|
+
export function isMeshTaskStatus(value: unknown): value is MeshTaskStatus {
|
|
135
|
+
return typeof value === 'string'
|
|
136
|
+
&& (MESH_TASK_STATUSES as readonly string[]).includes(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Event scope ─────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Explicit scope for pending coordinator events. v1 had no scope: every
|
|
143
|
+
* event was broadcast to every drainer. v2 forces producers to declare
|
|
144
|
+
* intent.
|
|
145
|
+
*
|
|
146
|
+
* - 'unicast': delivered to exactly one coordinator (intendedFor). Other
|
|
147
|
+
* coordinators' drains skip it.
|
|
148
|
+
* - 'broadcast': delivered to every coordinator on the mesh. Used for
|
|
149
|
+
* system-wide signals (e.g. mesh-wide policy changes). The v1 default
|
|
150
|
+
* becomes explicit here so audit tools can flag unintended broadcasts.
|
|
151
|
+
* - 'system': delivered to the daemon-level handler, not coordinators.
|
|
152
|
+
* Reserved for infrastructure events that no coordinator should see
|
|
153
|
+
* (e.g. ledger reconciliation outcomes).
|
|
154
|
+
*/
|
|
155
|
+
export const MESH_EVENT_SCOPES = ['unicast', 'broadcast', 'system'] as const;
|
|
156
|
+
export type MeshEventScope = typeof MESH_EVENT_SCOPES[number];
|
|
157
|
+
|
|
158
|
+
export function isMeshEventScope(value: unknown): value is MeshEventScope {
|
|
159
|
+
return typeof value === 'string'
|
|
160
|
+
&& (MESH_EVENT_SCOPES as readonly string[]).includes(value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Pending coordinator event v2 ────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* v2 shape of a pending coordinator event. Strict supersets of the v1
|
|
167
|
+
* shape — every v1 field is preserved so existing readers do not break;
|
|
168
|
+
* v2 fields (scope, dispatchedBy, intendedFor, protocolVersion) are
|
|
169
|
+
* additive and consulted by v2-aware drainers only.
|
|
170
|
+
*
|
|
171
|
+
* Mixed v1/v2 events coexist during the rollout window. B2 fully cuts
|
|
172
|
+
* over once every coordinator drain is v2-aware.
|
|
173
|
+
*/
|
|
174
|
+
export interface PendingMeshCoordinatorEventV2 {
|
|
175
|
+
// v1 fields (preserved exactly)
|
|
176
|
+
readonly event: string;
|
|
177
|
+
readonly meshId: string;
|
|
178
|
+
readonly nodeLabel: string;
|
|
179
|
+
readonly nodeId?: string;
|
|
180
|
+
readonly workspace?: string;
|
|
181
|
+
readonly metadataEvent: Record<string, unknown>;
|
|
182
|
+
readonly coordinatorMessage?: string;
|
|
183
|
+
readonly queuedAt: number;
|
|
184
|
+
|
|
185
|
+
// v2 additions
|
|
186
|
+
readonly protocolVersion: MeshProtocolVersion;
|
|
187
|
+
readonly scope: MeshEventScope;
|
|
188
|
+
readonly dispatchedBy: CoordinatorIdentity;
|
|
189
|
+
/**
|
|
190
|
+
* Required when scope === 'unicast', forbidden otherwise. Drainers MUST
|
|
191
|
+
* skip unicast events whose intendedFor does not equal their own identity.
|
|
192
|
+
*/
|
|
193
|
+
readonly intendedFor?: CoordinatorIdentity;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Ledger entry v2 ─────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* v2 ledger entry additions. The originatingCoordinator field is the
|
|
200
|
+
* source of truth that lets completion events route back to the
|
|
201
|
+
* coordinator that dispatched the task. v1's "worker settings carried it
|
|
202
|
+
* if it happened to be set" approach is replaced.
|
|
203
|
+
*/
|
|
204
|
+
export interface MeshLedgerOriginatingCoordinatorV2 {
|
|
205
|
+
readonly originatingCoordinator: CoordinatorIdentity;
|
|
206
|
+
readonly protocolVersion: MeshProtocolVersion;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── Validation primitives ───────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
export class MeshContractViolationError extends Error {
|
|
212
|
+
readonly violationPath: string;
|
|
213
|
+
readonly protocolVersion: MeshProtocolVersion;
|
|
214
|
+
constructor(
|
|
215
|
+
protocolVersion: MeshProtocolVersion,
|
|
216
|
+
violationPath: string,
|
|
217
|
+
detail: string,
|
|
218
|
+
) {
|
|
219
|
+
super(`mesh contract ${protocolVersion} violation at ${violationPath}: ${detail}`);
|
|
220
|
+
this.name = 'MeshContractViolationError';
|
|
221
|
+
this.violationPath = violationPath;
|
|
222
|
+
this.protocolVersion = protocolVersion;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
227
|
+
return typeof value === 'string' && value.length > 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function assertCoordinatorIdentity(raw: unknown, path: string): CoordinatorIdentity {
|
|
231
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
232
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, path, 'must be an object');
|
|
233
|
+
}
|
|
234
|
+
const obj = raw as Record<string, unknown>;
|
|
235
|
+
if (!isNonEmptyString(obj.daemonId)) {
|
|
236
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.daemonId`, 'must be a non-empty string');
|
|
237
|
+
}
|
|
238
|
+
if (!isNonEmptyString(obj.coordinatorRunId)) {
|
|
239
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.coordinatorRunId`, 'must be a non-empty string');
|
|
240
|
+
}
|
|
241
|
+
const sessionId = obj.sessionId;
|
|
242
|
+
if (sessionId !== undefined && !isNonEmptyString(sessionId)) {
|
|
243
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.sessionId`, 'must be a non-empty string when provided');
|
|
244
|
+
}
|
|
245
|
+
return sessionId !== undefined
|
|
246
|
+
? { daemonId: obj.daemonId, coordinatorRunId: obj.coordinatorRunId, sessionId }
|
|
247
|
+
: { daemonId: obj.daemonId, coordinatorRunId: obj.coordinatorRunId };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function assertPendingMeshCoordinatorEventV2(raw: unknown, path = '$'): PendingMeshCoordinatorEventV2 {
|
|
251
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
252
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, path, 'must be an object');
|
|
253
|
+
}
|
|
254
|
+
const obj = raw as Record<string, unknown>;
|
|
255
|
+
if (!isSupportedMeshProtocolVersion(obj.protocolVersion)) {
|
|
256
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.protocolVersion`, `must be one of ${SUPPORTED_MESH_PROTOCOL_VERSIONS.join(', ')}`);
|
|
257
|
+
}
|
|
258
|
+
if (!isMeshEventScope(obj.scope)) {
|
|
259
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.scope`, `must be one of ${MESH_EVENT_SCOPES.join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
if (!isNonEmptyString(obj.event)) {
|
|
262
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.event`, 'must be a non-empty string');
|
|
263
|
+
}
|
|
264
|
+
if (!isNonEmptyString(obj.meshId)) {
|
|
265
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.meshId`, 'must be a non-empty string');
|
|
266
|
+
}
|
|
267
|
+
const dispatchedBy = assertCoordinatorIdentity(obj.dispatchedBy, `${path}.dispatchedBy`);
|
|
268
|
+
|
|
269
|
+
if (obj.scope === 'unicast') {
|
|
270
|
+
if (!obj.intendedFor) {
|
|
271
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.intendedFor`, 'unicast scope requires intendedFor');
|
|
272
|
+
}
|
|
273
|
+
} else if (obj.intendedFor !== undefined) {
|
|
274
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.intendedFor`, 'only unicast scope may set intendedFor');
|
|
275
|
+
}
|
|
276
|
+
const intendedFor = obj.intendedFor
|
|
277
|
+
? assertCoordinatorIdentity(obj.intendedFor, `${path}.intendedFor`)
|
|
278
|
+
: undefined;
|
|
279
|
+
|
|
280
|
+
const metadata = obj.metadataEvent && typeof obj.metadataEvent === 'object' && !Array.isArray(obj.metadataEvent)
|
|
281
|
+
? (obj.metadataEvent as Record<string, unknown>)
|
|
282
|
+
: null;
|
|
283
|
+
if (!metadata) {
|
|
284
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.metadataEvent`, 'must be an object');
|
|
285
|
+
}
|
|
286
|
+
const queuedAt = typeof obj.queuedAt === 'number' && Number.isFinite(obj.queuedAt) ? obj.queuedAt : null;
|
|
287
|
+
if (queuedAt === null) {
|
|
288
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.queuedAt`, 'must be a finite number');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
event: obj.event,
|
|
293
|
+
meshId: obj.meshId,
|
|
294
|
+
nodeLabel: isNonEmptyString(obj.nodeLabel) ? obj.nodeLabel : '',
|
|
295
|
+
nodeId: typeof obj.nodeId === 'string' ? obj.nodeId : undefined,
|
|
296
|
+
workspace: typeof obj.workspace === 'string' ? obj.workspace : undefined,
|
|
297
|
+
metadataEvent: metadata,
|
|
298
|
+
coordinatorMessage: typeof obj.coordinatorMessage === 'string' ? obj.coordinatorMessage : undefined,
|
|
299
|
+
queuedAt,
|
|
300
|
+
protocolVersion: obj.protocolVersion,
|
|
301
|
+
scope: obj.scope,
|
|
302
|
+
dispatchedBy,
|
|
303
|
+
...(intendedFor ? { intendedFor } : {}),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─── Routing helper ──────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Decide whether a v2 pending event should be delivered to the given drainer.
|
|
311
|
+
* Centralised so every drain implementation uses the same rule.
|
|
312
|
+
*
|
|
313
|
+
* - 'broadcast': always delivered.
|
|
314
|
+
* - 'system': never delivered to coordinators (system handler only).
|
|
315
|
+
* - 'unicast': delivered iff intendedFor matches drainer identity.
|
|
316
|
+
*
|
|
317
|
+
* v1 events (no scope / no protocolVersion) are treated as broadcast for
|
|
318
|
+
* backward compatibility during rollout; B3 tightens this so v1 events
|
|
319
|
+
* are quarantined to a dedicated drain endpoint instead.
|
|
320
|
+
*/
|
|
321
|
+
export function shouldDeliverPendingEventToCoordinator(
|
|
322
|
+
event: PendingMeshCoordinatorEventV2,
|
|
323
|
+
drainer: CoordinatorIdentity,
|
|
324
|
+
): boolean {
|
|
325
|
+
if (event.scope === 'system') return false;
|
|
326
|
+
if (event.scope === 'broadcast') return true;
|
|
327
|
+
if (!event.intendedFor) return false;
|
|
328
|
+
return coordinatorIdentityEquals(event.intendedFor, drainer);
|
|
329
|
+
}
|
|
@@ -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,22 +136,88 @@ 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 ───────────────────────────
|
|
77
213
|
|
|
78
214
|
function buildNodeStatusSection(nodes: RepoMeshNodeStatus[]): string {
|
|
79
|
-
const lines = [
|
|
215
|
+
const lines = [
|
|
216
|
+
'## Current Node Status',
|
|
217
|
+
'',
|
|
218
|
+
'Node labels are display context, not aliases. Use exact `nodeId` values in mesh tool calls; do not invent shorthand names such as M1/M2 unless they are explicitly configured labels.',
|
|
219
|
+
'',
|
|
220
|
+
];
|
|
80
221
|
for (const n of nodes) {
|
|
81
222
|
const healthIcon = n.health === 'online' ? '🟢' :
|
|
82
223
|
n.health === 'dirty' ? '🟡' :
|
|
@@ -85,31 +226,65 @@ function buildNodeStatusSection(nodes: RepoMeshNodeStatus[]): string {
|
|
|
85
226
|
? `sessions: ${n.activeSessions.join(', ')}`
|
|
86
227
|
: 'no active sessions';
|
|
87
228
|
const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : '';
|
|
88
|
-
|
|
89
|
-
|
|
229
|
+
const context = [
|
|
230
|
+
n.daemonId ? `daemon: \`${n.daemonId}\`` : '',
|
|
231
|
+
n.providers?.length ? `providers: ${n.providers.join(', ')}` : '',
|
|
232
|
+
].filter(Boolean).join(' | ');
|
|
233
|
+
lines.push(`- ${healthIcon} **${n.machineLabel}** (nodeId: \`${n.nodeId}\`)`);
|
|
234
|
+
lines.push(` workspace: \`${n.workspace}\`${context ? ` | ${context}` : ''} | ${branch} | ${sessions}`);
|
|
90
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
|
+
}
|
|
91
240
|
}
|
|
92
241
|
return lines.join('\n');
|
|
93
242
|
}
|
|
94
243
|
|
|
95
244
|
function buildNodeConfigSection(mesh: LocalMeshEntry): string {
|
|
96
|
-
const lines = [
|
|
245
|
+
const lines = [
|
|
246
|
+
'## Configured Nodes',
|
|
247
|
+
'',
|
|
248
|
+
'Node labels are display context, not aliases. Use exact `nodeId` values in mesh tool calls; do not invent shorthand names such as M1/M2 unless they are explicitly configured labels.',
|
|
249
|
+
'',
|
|
250
|
+
];
|
|
97
251
|
for (const n of mesh.nodes) {
|
|
98
252
|
const labels: string[] = [];
|
|
99
253
|
if (n.isLocalWorktree) labels.push('worktree');
|
|
100
254
|
if (n.policy?.readOnly) labels.push('read-only');
|
|
101
255
|
const suffix = labels.length ? ` [${labels.join(', ')}]` : '';
|
|
102
|
-
|
|
256
|
+
const explicitMachineLabel = typeof (n as any).machineLabel === 'string' ? (n as any).machineLabel : '';
|
|
257
|
+
const explicitLabel = explicitMachineLabel ? ` label: **${explicitMachineLabel}** |` : '';
|
|
258
|
+
const providerPriority = n.policy?.providerPriority?.length ? ` | providers: ${n.policy.providerPriority.join(', ')}` : '';
|
|
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
|
+
}
|
|
103
264
|
}
|
|
104
265
|
lines.push('', '_Use `mesh_status` to probe live health before delegating work._');
|
|
105
266
|
return lines.join('\n');
|
|
106
267
|
}
|
|
107
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
|
+
|
|
108
280
|
function buildPolicySection(policy: RepoMeshPolicy): string {
|
|
109
281
|
const rules: string[] = [];
|
|
110
282
|
if (policy.requirePreTaskCheckpoint) rules.push('- Create a git checkpoint **before** starting each task');
|
|
111
283
|
if (policy.requirePostTaskCheckpoint) rules.push('- Create a git checkpoint **after** each task completes');
|
|
112
284
|
if (policy.requireApprovalForPush) rules.push('- **Ask for user approval** before pushing to remote');
|
|
285
|
+
if (policy.allowAutoPublishSubmoduleMainCommits) {
|
|
286
|
+
rules.push('- Refinery may auto-publish unreachable submodule gitlink commits to submodule origin/main with non-force pushes after validation and patch-equivalence pass');
|
|
287
|
+
}
|
|
113
288
|
if (policy.requireApprovalForDestructiveGit) rules.push('- **Ask for user approval** before destructive git operations (force push, reset, etc.)');
|
|
114
289
|
|
|
115
290
|
const dirtyBehavior = {
|
|
@@ -140,6 +315,7 @@ const TOOLS_SECTION = `## Available Tools
|
|
|
140
315
|
| \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
|
|
141
316
|
| \`mesh_task_history\` | Read the task ledger — dispatches, completions, failures. Use to understand what has been done before deciding next steps |
|
|
142
317
|
| \`mesh_git_status\` | Check git status on a specific node |
|
|
318
|
+
| \`mesh_fast_forward_node\` | Safely dry-run or explicitly execute an obvious clean fast-forward without launching an agent session |
|
|
143
319
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
144
320
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
145
321
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
@@ -161,10 +337,10 @@ const WORKFLOW_SECTION = `## Orchestration Workflow
|
|
|
161
337
|
c. **Targeted Tasks**: Use \`mesh_send_task\` only when you need to bypass the queue and force a specific node to execute a task immediately.
|
|
162
338
|
d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
|
|
163
339
|
e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.
|
|
164
|
-
4. **Monitor** — Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly.
|
|
340
|
+
4. **Monitor** — Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Do **not** repeatedly call \`mesh_status\` or \`mesh_view_queue\` just to wait for assigned/generating work. After dispatching a direct or queued task, send one progress update with the task/session handle, then stop. Wait for \`pendingCoordinatorEvents\` or another completion/approval/status signal, an explicit user status request, or a real timeout/stall signal before reading status/chat/queue again. Use at most one compact \`mesh_read_chat\` check after a terminal signal. Handle approvals via \`mesh_approve\`.
|
|
165
341
|
5. **Verify** — When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
166
342
|
6. **Checkpoint** — Call \`mesh_checkpoint\` to save the work.
|
|
167
|
-
7. **Converge branches** — Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary and \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
343
|
+
7. **Converge branches** — Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from the configured submodule remote main branch, not merely present on a feature ref or local checkout. If \`mesh_refine_node\` returns \`submodule_reachability_failed\` or publish-required evidence, keep the public convergence bucket as \`blocked_review\`; unless \`allowAutoPublishSubmoduleMainCommits\` is explicitly enabled and Refinery reports successful non-force publish plus post-publish verification, ask the user for explicit approval to push/publish the unreachable submodule commit(s) to submodule main, then rerun \`mesh_refine_node\`. Do not merge the root branch until the submodule commit(s) are reachable from submodule origin/main. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
168
344
|
8. **Clean up** — Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
169
345
|
9. **Report** — Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
170
346
|
|
|
@@ -188,19 +364,17 @@ function buildRulesSection(coordinatorCliType?: string): string {
|
|
|
188
364
|
|
|
189
365
|
return `## Rules
|
|
190
366
|
|
|
191
|
-
- **
|
|
192
|
-
- **
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
196
|
-
- **
|
|
197
|
-
- **
|
|
198
|
-
- **
|
|
199
|
-
- **
|
|
200
|
-
- **
|
|
201
|
-
- **
|
|
202
|
-
- **Never fabricate tool results.** Always call the actual tool
|
|
203
|
-
- **
|
|
204
|
-
- **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
|
|
205
|
-
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
367
|
+
- **Route, don't implement.** Delegate all code reading, analysis, and execution to node agents. Never read source files or run commands in the coordinator — keep context lean.
|
|
368
|
+
- **Front-load task messages.** Include everything the agent needs (files, problem, expected fix) in \`mesh_enqueue_task\` / \`mesh_send_task\`. Append a structured result request at the end: ask the worker to conclude with a JSON block containing \`status\`, \`changedFiles\`, \`gitStatus\`, \`validationResults\`, \`errors\`, \`nextAction\`. The daemon parses this automatically; you can read it from \`mesh_task_history\`.
|
|
369
|
+
- **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start fresh only for independent work, provider mismatch, transcript contamination, or required worktree isolation.
|
|
370
|
+
- **Respect explicit provider requests.** Map: Hermes → \`hermes-cli\`, Claude/Claude Code → \`claude-cli\`, Codex → \`codex-cli\`, Gemini → \`gemini-cli\`, Antigravity → \`antigravity-cli\`. Never substitute the coordinator's own runtime.
|
|
371
|
+
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
372
|
+
- **Limit parallelism.** Start with 1–2 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing.
|
|
373
|
+
- **Check history first.** Call \`mesh_task_history\` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.
|
|
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\`.
|
|
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.
|
|
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.
|
|
378
|
+
- **Never fabricate tool results.** Always call the actual tool.
|
|
379
|
+
- **Keep the user informed.** One or two sentences after each delegation round.${coordinatorNote}`;
|
|
206
380
|
}
|