@adhdev/daemon-core 0.9.82-rc.481 → 0.9.82-rc.483
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/mesh-config.d.ts +10 -28
- package/dist/index.d.ts +2 -2
- package/dist/index.js +337 -249
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +337 -244
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/contracts.d.ts +35 -9
- package/dist/mesh/mesh-reconcile-loop.d.ts +4 -0
- package/dist/mesh/mesh-runtime-store.d.ts +47 -2
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +4 -0
- package/dist/repo-mesh-types.d.ts +8 -14
- package/dist/types.d.ts +15 -6
- package/package.json +3 -3
- package/src/commands/chat-commands-read.ts +81 -60
- package/src/commands/low-family/coordinator-prompt.ts +53 -0
- package/src/commands/med-family/mesh-crud.ts +19 -62
- package/src/config/mesh-config.ts +21 -140
- package/src/index.ts +1 -2
- package/src/mesh/contracts.ts +59 -18
- package/src/mesh/coordinator-prompt.ts +22 -6
- package/src/mesh/mesh-event-forwarding.ts +38 -102
- package/src/mesh/mesh-events-pending.ts +20 -0
- package/src/mesh/mesh-reconcile-loop.ts +157 -2
- package/src/mesh/mesh-runtime-store.ts +118 -3
- package/src/mesh/mesh-unresolved-forward-outbox.ts +30 -0
- package/src/repo-mesh-types.ts +8 -14
- package/src/types.ts +21 -6
package/dist/mesh/contracts.d.ts
CHANGED
|
@@ -161,7 +161,12 @@ export declare function assertPendingMeshCoordinatorEventV2(raw: unknown, path?:
|
|
|
161
161
|
* Decide whether a v2 pending event should be delivered to the given drainer.
|
|
162
162
|
* Centralised so every drain implementation uses the same rule.
|
|
163
163
|
*
|
|
164
|
-
* - 'broadcast': always delivered.
|
|
164
|
+
* - 'broadcast': always delivered. NOTE: a terminal task event that reached the
|
|
165
|
+
* queue as broadcast is an ownership leak (it belongs to its dispatching
|
|
166
|
+
* coordinator). This pure helper does not have the drain-window
|
|
167
|
+
* daemon-form/session matching semantics, so the terminal+broadcast
|
|
168
|
+
* dispatchedBy filter is applied one layer up in the drainer (see
|
|
169
|
+
* mesh-events-pending routeV2EventsForDrainer) where those semantics live.
|
|
165
170
|
* - 'system': never delivered to coordinators (system handler only).
|
|
166
171
|
* - 'unicast': delivered iff intendedFor matches drainer identity.
|
|
167
172
|
*
|
|
@@ -170,13 +175,25 @@ export declare function assertPendingMeshCoordinatorEventV2(raw: unknown, path?:
|
|
|
170
175
|
* are quarantined to a dedicated drain endpoint instead.
|
|
171
176
|
*/
|
|
172
177
|
export declare function shouldDeliverPendingEventToCoordinator(event: PendingMeshCoordinatorEventV2, drainer: CoordinatorIdentity): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* True for a terminal task event (completion / stop / refine outcome). A terminal
|
|
180
|
+
* event belongs to exactly the coordinator that dispatched the task, so it must
|
|
181
|
+
* never fan out to sibling coordinators that did not dispatch it. Used by the
|
|
182
|
+
* emit-side stamp (to avoid downgrading an unaddressed terminal event to full
|
|
183
|
+
* broadcast) and by the drain-side filter (defense-in-depth for any terminal
|
|
184
|
+
* event that already reached the queue as broadcast).
|
|
185
|
+
*/
|
|
186
|
+
export declare function isTerminalTaskEvent(eventName: string): boolean;
|
|
173
187
|
/**
|
|
174
188
|
* Default the v2 scope for an event by its producer event name (design decision
|
|
175
|
-
* §3). Terminal task events → unicast (routed
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
189
|
+
* §3). Terminal task events and coordinator-addressed alerts → unicast (routed
|
|
190
|
+
* to the originating coordinator). Everything else — node lifecycle and
|
|
191
|
+
* progress signals — → broadcast, which also matches v1's implicit "deliver to
|
|
192
|
+
* any coordinator" behaviour, so an unstamped v1 event and a v2-stamped-as-
|
|
193
|
+
* broadcast event route identically during rollout. No event currently defaults
|
|
194
|
+
* to 'system'; the scope remains in MESH_EVENT_SCOPES for wire compatibility
|
|
195
|
+
* (an already-queued or version-skewed 'system' event still routes away from
|
|
196
|
+
* coordinators).
|
|
180
197
|
*/
|
|
181
198
|
export declare function defaultScopeForEvent(eventName: string): MeshEventScope;
|
|
182
199
|
/**
|
|
@@ -216,9 +233,18 @@ export declare function coordinatorIdentityFromEmitFields(fields: {
|
|
|
216
233
|
* returns undefined: the event stays a v1 (unstamped) event and is broadcast-
|
|
217
234
|
* treated during rollout, exactly as before — no regression, no fabricated
|
|
218
235
|
* identity. When the resolved scope is 'unicast' but no `intendedFor` is
|
|
219
|
-
* available, the
|
|
220
|
-
*
|
|
221
|
-
*
|
|
236
|
+
* available, the fallback depends on the event class:
|
|
237
|
+
*
|
|
238
|
+
* - Terminal task events (completion / stop / refine outcome) MUST NOT be
|
|
239
|
+
* broadcast to every coordinator — a completion belongs to the coordinator
|
|
240
|
+
* that dispatched the task, and broadcasting it makes non-owner coordinators
|
|
241
|
+
* (e.g. sibling MAGI coordinators that never dispatched this replica's task)
|
|
242
|
+
* act on a completion that is not theirs (MAGI-REPLICA-COMPLETION-EVENT-LEAK).
|
|
243
|
+
* For these we address the event to `dispatchedBy` (the dispatching
|
|
244
|
+
* coordinator) and KEEP it unicast, so the stamp stays contract-valid and the
|
|
245
|
+
* event reaches only its originating coordinator.
|
|
246
|
+
* - Any other unicast event with no addressable target falls back to broadcast
|
|
247
|
+
* (contract-valid, still delivered, never dropped) — unchanged.
|
|
222
248
|
*/
|
|
223
249
|
export declare function buildPendingEventEmitStamp(opts: {
|
|
224
250
|
eventName: string;
|
|
@@ -72,6 +72,10 @@ export declare function shouldHoldPendingDrainForBusyLocalCoordinator(components
|
|
|
72
72
|
statusInstanceId?: string;
|
|
73
73
|
}, meshId: string, requestedCoordinatorDaemonId?: string | null, callerIsSelfCoordinatorInboxRead?: boolean): boolean;
|
|
74
74
|
export declare function __resetReclaimUnknownStreakForTests(): void;
|
|
75
|
+
export declare function reconcileZombieAssignedTasks(components: DaemonComponents, mesh: {
|
|
76
|
+
id: string;
|
|
77
|
+
nodes?: unknown[];
|
|
78
|
+
}, selfIds: string[]): void;
|
|
75
79
|
export declare function runMeshReconcileTick(components: DaemonComponents): Promise<void>;
|
|
76
80
|
export declare function __resetUnresolvedForwardRejectionCountsForTests(): void;
|
|
77
81
|
interface ReconcileLoopHandle {
|
|
@@ -262,9 +262,39 @@ export declare class MeshRuntimeStore {
|
|
|
262
262
|
};
|
|
263
263
|
/**
|
|
264
264
|
* Prune tool call log entries older than the given age in ms.
|
|
265
|
-
*
|
|
265
|
+
* Returns the number of rows deleted. Also used by the periodic retention
|
|
266
|
+
* sweep (pruneMeshRuntimeRetention) — the in-write sweep in recordMeshToolCall
|
|
267
|
+
* only fires every 200 calls and only covers the rate-limit window, so a
|
|
268
|
+
* quiet mesh otherwise accumulates rows indefinitely.
|
|
266
269
|
*/
|
|
267
|
-
pruneToolCallLog(olderThanMs: number):
|
|
270
|
+
pruneToolCallLog(olderThanMs: number): number;
|
|
271
|
+
/**
|
|
272
|
+
* Retention prune for mesh_event_ledger (SoT 1-11 (b)). The ledger is append-only
|
|
273
|
+
* with NO lifecycle GC of its own, so lifecycle events accumulate without bound
|
|
274
|
+
* (the dominant mesh-runtime.db growth). Every production reader is bounded to a
|
|
275
|
+
* recent window (readLedgerEntries tail/limit ≤ a few hundred; task-stats /
|
|
276
|
+
* terminal-evidence scans look at recent tasks), so rows past a generous age only
|
|
277
|
+
* cost space. Excluded from deletion — retained forever:
|
|
278
|
+
* - coordinator_operating_note / _tombstone: runtime-accumulated lessons whose
|
|
279
|
+
* whole point is surviving restarts; a tombstone must also outlive the notes
|
|
280
|
+
* it retracts.
|
|
281
|
+
* Timestamps are ISO-8601 TEXT, so the lexicographic `<` cutoff is a correct time
|
|
282
|
+
* comparison; a malformed timestamp compares greater than any ISO date and is
|
|
283
|
+
* conservatively retained. Returns rows deleted.
|
|
284
|
+
*/
|
|
285
|
+
pruneEventLedger(olderThanMs: number): number;
|
|
286
|
+
/**
|
|
287
|
+
* Retention prune for TERMINAL (completed/cancelled/failed) mesh_queue rows
|
|
288
|
+
* (SoT 1-11 (b)). Terminal rows are kept as recent history (mesh_task_history,
|
|
289
|
+
* completion-dedup taskId lookups) but nothing ever deletes them, so the queue
|
|
290
|
+
* table grows monotonically. Rows past the retention window serve no reader —
|
|
291
|
+
* every dedup/attribution path operates on recent tasks — EXCEPT as a dependency
|
|
292
|
+
* anchor: taskDependenciesSatisfied resolves dependsOn by id and treats a MISSING
|
|
293
|
+
* row as not-completed, so deleting a completed row that a still-live
|
|
294
|
+
* (pending/assigned) row depends on would permanently strand the dependent.
|
|
295
|
+
* Those ids are collected first and excluded. Returns rows deleted.
|
|
296
|
+
*/
|
|
297
|
+
pruneTerminalQueueEntries(olderThanMs: number): number;
|
|
268
298
|
appendLedgerEntry(entry: {
|
|
269
299
|
id: string;
|
|
270
300
|
meshId: string;
|
|
@@ -506,3 +536,18 @@ export declare class MeshRuntimeStore {
|
|
|
506
536
|
undrainedOlderThanMs: number;
|
|
507
537
|
}): number;
|
|
508
538
|
}
|
|
539
|
+
export declare const MESH_EVENT_LEDGER_RETENTION_MS: number;
|
|
540
|
+
export declare const MESH_TOOL_CALL_LOG_RETENTION_MS: number;
|
|
541
|
+
export declare const MESH_TERMINAL_QUEUE_RETENTION_MS: number;
|
|
542
|
+
/**
|
|
543
|
+
* Periodic retention sweep for the mesh-runtime.db tables that previously had no
|
|
544
|
+
* lifecycle GC (event ledger, tool-call log, terminal queue rows). Runs on the SAME
|
|
545
|
+
* cadence as the pending-events retention prune (the hourly mesh-event maintenance
|
|
546
|
+
* sweep in mesh-event-forwarding.ts). Best-effort and idempotent: a store failure
|
|
547
|
+
* degrades to a no-op with one warn; an empty table costs three cheap DELETEs.
|
|
548
|
+
*/
|
|
549
|
+
export declare function pruneMeshRuntimeRetention(): {
|
|
550
|
+
ledger: number;
|
|
551
|
+
toolCalls: number;
|
|
552
|
+
terminalQueue: number;
|
|
553
|
+
};
|
|
@@ -9,6 +9,10 @@ export interface UnresolvedForwardEntry {
|
|
|
9
9
|
/** When the entry was first enqueued (epoch ms) — used for age-based expiry. */
|
|
10
10
|
queuedAt: number;
|
|
11
11
|
}
|
|
12
|
+
/** Register (or clear, with undefined) the PHASE 0 retry nudge handler. */
|
|
13
|
+
export declare function registerUnresolvedForwardRetryNudge(handler?: () => void): void;
|
|
14
|
+
/** Fire-and-forget nudge: ask the reconcile loop to run the outbox retry soon. */
|
|
15
|
+
export declare function nudgeUnresolvedForwardRetry(): void;
|
|
12
16
|
/**
|
|
13
17
|
* Durably enqueue an unresolved-delegate forward for a coordinator daemon. The
|
|
14
18
|
* `forwardPayload` is the flat shape handleMeshForwardEvent reads on the coordinator.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
|
|
14
14
|
import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
|
|
15
15
|
import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
|
|
16
|
-
import type {
|
|
16
|
+
import type { MagiKindPanelMap } from '@adhdev/mesh-shared';
|
|
17
17
|
export interface RepoMesh {
|
|
18
18
|
id: string;
|
|
19
19
|
name: string;
|
|
@@ -372,7 +372,7 @@ export declare function magiAutoLaunchedSessionCleanupDecision(args: {
|
|
|
372
372
|
};
|
|
373
373
|
/** Min/max bounds for the global write-task parallel cap. */
|
|
374
374
|
export declare const MESH_MAX_PARALLEL_TASKS_MIN = 1;
|
|
375
|
-
export declare const MESH_MAX_PARALLEL_TASKS_MAX =
|
|
375
|
+
export declare const MESH_MAX_PARALLEL_TASKS_MAX = 64;
|
|
376
376
|
/**
|
|
377
377
|
* Default multiplier applied to the write cap to derive the read-only diagnosis
|
|
378
378
|
* cap. Read-only (live_debug_readonly) tasks carry no isolation/merge cost so they
|
|
@@ -547,18 +547,12 @@ export interface RepoMeshCoordinatorConfig {
|
|
|
547
547
|
export interface LocalMeshConfig {
|
|
548
548
|
meshes: LocalMeshEntry[];
|
|
549
549
|
/**
|
|
550
|
-
* MAGI
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* MAGI-KIND-PANEL: per-task_kind panel bindings (machine-local). Keyed by
|
|
558
|
-
* task_kind (rca / design / claim_audit / freeform); each maps to ≥1
|
|
559
|
-
* `(node × provider × model?)` slot. A `mesh_magi_review` invoked with a bare
|
|
560
|
-
* `task_kind` resolves its panel from here — an unconfigured kind is a hard
|
|
561
|
-
* error, never a synthesized fallback. Optional; absent on pre-feature configs.
|
|
550
|
+
* MAGI-KIND-PANEL: per-task_kind panel bindings (machine-local), the SOLE MAGI
|
|
551
|
+
* panel-resolution surface (the former named-panel `magiPanels` map was removed).
|
|
552
|
+
* Keyed by task_kind (rca / design / claim_audit / freeform); each maps to ≥1
|
|
553
|
+
* `(node × provider × model?)` slot. A `mesh_magi_review` resolves its panel from
|
|
554
|
+
* here — an unconfigured kind is a hard error, never a synthesized fallback.
|
|
555
|
+
* Optional; absent on pre-feature configs.
|
|
562
556
|
*/
|
|
563
557
|
magiKindPanels?: MagiKindPanelMap;
|
|
564
558
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* When modifying this file, also update interface contracts in AGENT_PROTOCOL.md.
|
|
6
6
|
*/
|
|
7
7
|
import type { StatusReportPayload, AvailableProviderInfo } from './shared-types.js';
|
|
8
|
-
import type { ChatMessageKind } from './providers/chat-message-normalization.js';
|
|
8
|
+
import type { ChatMessageKind, ChatMessageVisibility, ChatMessageTranscriptVisibility, ChatMessageAudience, ChatMessageSource } from './providers/chat-message-normalization.js';
|
|
9
9
|
/** Full status response from /api/v1/status and WS events */
|
|
10
10
|
export interface StatusResponse extends StatusReportPayload {
|
|
11
11
|
/** For standalone API compat */
|
|
@@ -47,11 +47,20 @@ export interface ChatMessage {
|
|
|
47
47
|
/** Optional: fiber metadata */
|
|
48
48
|
_type?: string;
|
|
49
49
|
_sub?: string;
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Transcript visibility/audience contract for separating chat-visible content
|
|
52
|
+
* from internal/debug runtime rows. These reference the canonical named unions
|
|
53
|
+
* declared alongside the classifier (chat-message-normalization.ts) so the known
|
|
54
|
+
* values have one source of truth instead of a hand-inlined copy that drifts.
|
|
55
|
+
* Each alias keeps the `| (string & {})` escape hatch: the read-chat contract
|
|
56
|
+
* (read-chat-contract.ts) preserves ANY producer-supplied string verbatim, so
|
|
57
|
+
* the type must stay open — it documents the known values without forbidding
|
|
58
|
+
* provider-specific extensions.
|
|
59
|
+
*/
|
|
60
|
+
visibility?: ChatMessageVisibility;
|
|
61
|
+
transcriptVisibility?: ChatMessageTranscriptVisibility;
|
|
62
|
+
audience?: ChatMessageAudience;
|
|
63
|
+
source?: ChatMessageSource;
|
|
55
64
|
userFacing?: boolean;
|
|
56
65
|
internal?: boolean;
|
|
57
66
|
isInternal?: boolean;
|
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.483",
|
|
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",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"author": "vilmire",
|
|
48
48
|
"license": "AGPL-3.0-or-later",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
51
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "0.9.82-rc.483",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.483",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -195,6 +195,57 @@ function isRuntimeFallbackHistorySessionId(
|
|
|
195
195
|
const candidate = typeof candidateHistorySessionId === 'string' ? candidateHistorySessionId.trim() : '';
|
|
196
196
|
return candidate === target;
|
|
197
197
|
}
|
|
198
|
+
|
|
199
|
+
interface ResolvedNativeHistoryReadSession {
|
|
200
|
+
/**
|
|
201
|
+
* True when the candidate history id is the daemon runtime session id (==
|
|
202
|
+
* targetSessionId) standing in for a real provider-native conv uuid — reached
|
|
203
|
+
* either via getHistorySessionId's internal fallback (empty args) or because
|
|
204
|
+
* the browser explicitly echoed targetSessionId back as historySessionId (the
|
|
205
|
+
* poisoned agy-coordinator read). See isRuntimeFallbackHistorySessionId.
|
|
206
|
+
*/
|
|
207
|
+
isRuntimeFallback: boolean;
|
|
208
|
+
/** Owner-confirmed pin recorded by a prior bound read for this mesh session, if any. */
|
|
209
|
+
pinnedProviderSessionId: string | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* The id to key the native read on: the pin (or undefined) when the candidate
|
|
212
|
+
* is a runtime fallback so pin / workspace-latest resolution engages, else the
|
|
213
|
+
* candidate unchanged (a real DISTINCT provider uuid still exact-binds).
|
|
214
|
+
*/
|
|
215
|
+
effectiveHistorySessionId: string | undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Resolve the runtime-fallback → pin substitution shared by every native-history
|
|
220
|
+
* read path (handleChatHistory, the CLI-adapter main read, and the history-only
|
|
221
|
+
* read). Each site previously inlined this same four-step computation verbatim:
|
|
222
|
+
* detect the runtime fallback (candidate === targetSessionId AND no distinct
|
|
223
|
+
* explicit id), look up the owner-confirmed pin, and drop the runtime id in favor
|
|
224
|
+
* of the pin (or undefined) so readCliProviderNativeHistory's pin / workspace-
|
|
225
|
+
* latest paths can engage instead of fail-closing to pty-parser. Extracted to a
|
|
226
|
+
* single helper so the D9 historySessionId-poison guard has one definition.
|
|
227
|
+
* Behavior is identical to the inlined blocks — same target (args.targetSessionId),
|
|
228
|
+
* same explicit-id source, same pin key (getBoundProviderSessionIdPin trims).
|
|
229
|
+
*/
|
|
230
|
+
function resolveNativeHistoryReadSession(
|
|
231
|
+
args: any,
|
|
232
|
+
candidateHistorySessionId: string | undefined,
|
|
233
|
+
): ResolvedNativeHistoryReadSession {
|
|
234
|
+
const targetSid = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
235
|
+
const explicitHistorySessionId = getExplicitHistorySessionId(args);
|
|
236
|
+
const isRuntimeFallback = Boolean(
|
|
237
|
+
targetSid
|
|
238
|
+
&& isRuntimeFallbackHistorySessionId(candidateHistorySessionId, targetSid)
|
|
239
|
+
&& (!explicitHistorySessionId
|
|
240
|
+
|| isRuntimeFallbackHistorySessionId(explicitHistorySessionId, targetSid)),
|
|
241
|
+
);
|
|
242
|
+
const pinnedProviderSessionId = getBoundProviderSessionIdPin(args?.targetSessionId);
|
|
243
|
+
const effectiveHistorySessionId = isRuntimeFallback
|
|
244
|
+
? (pinnedProviderSessionId || undefined)
|
|
245
|
+
: candidateHistorySessionId;
|
|
246
|
+
return { isRuntimeFallback, pinnedProviderSessionId, effectiveHistorySessionId };
|
|
247
|
+
}
|
|
248
|
+
|
|
198
249
|
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
199
250
|
const explicit = getExplicitHistorySessionId(args);
|
|
200
251
|
if (explicit) return explicit;
|
|
@@ -1653,24 +1704,18 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
1653
1704
|
? (h.currentSession as any).workspace
|
|
1654
1705
|
: undefined;
|
|
1655
1706
|
// Same runtime-fallback poison guard as the subscribe / history-only
|
|
1656
|
-
// paths: getHistorySessionId falls
|
|
1657
|
-
// id) for an agy coordinator, and the
|
|
1658
|
-
//
|
|
1659
|
-
// (it is not the on-disk conv uuid). Drop
|
|
1660
|
-
// workspace-latest / owner-confirmed resolution
|
|
1661
|
-
// fail-closing to pty-parser. A real DISTINCT provider
|
|
1662
|
-
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|| isRuntimeFallbackHistorySessionId(explicitHistorySessionIdForHistory, targetSidForHistory)),
|
|
1669
|
-
);
|
|
1670
|
-
const pinnedProviderSessionIdForHistory = getBoundProviderSessionIdPin(args?.targetSessionId);
|
|
1671
|
-
const effectiveHistorySessionId = historySessionIdIsRuntimeFallback
|
|
1672
|
-
? (pinnedProviderSessionIdForHistory || undefined)
|
|
1673
|
-
: historySessionId;
|
|
1707
|
+
// paths (see resolveNativeHistoryReadSession): getHistorySessionId falls
|
|
1708
|
+
// back to targetSessionId (the ADHDev id) for an agy coordinator, and the
|
|
1709
|
+
// browser may also send that id back explicitly. Reading native history
|
|
1710
|
+
// keyed on it can never exact-bind (it is not the on-disk conv uuid). Drop
|
|
1711
|
+
// it here too so the pin / workspace-latest / owner-confirmed resolution
|
|
1712
|
+
// engages instead of fail-closing to pty-parser. A real DISTINCT provider
|
|
1713
|
+
// uuid is preserved.
|
|
1714
|
+
const {
|
|
1715
|
+
isRuntimeFallback: historySessionIdIsRuntimeFallback,
|
|
1716
|
+
pinnedProviderSessionId: pinnedProviderSessionIdForHistory,
|
|
1717
|
+
effectiveHistorySessionId,
|
|
1718
|
+
} = resolveNativeHistoryReadSession(args, historySessionId);
|
|
1674
1719
|
const exactNativeHistoryScope = Boolean(
|
|
1675
1720
|
(typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
|
|
1676
1721
|
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim() && !historySessionIdIsRuntimeFallback)
|
|
@@ -1921,7 +1966,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1921
1966
|
let nativeHistory: any | null = null;
|
|
1922
1967
|
let nativeHistoryError: unknown | undefined;
|
|
1923
1968
|
if (supportsNative) {
|
|
1924
|
-
// Runtime-fallback → pin substitution
|
|
1969
|
+
// Runtime-fallback → pin substitution (see
|
|
1970
|
+
// resolveNativeHistoryReadSession): nativeHistoryReadSessionId is
|
|
1925
1971
|
// the bare runtime/session id when no explicit provider handle was
|
|
1926
1972
|
// supplied and none was parsed (antigravity takes no --session-id, so
|
|
1927
1973
|
// its this.providerSessionId stays empty and getHistorySessionId falls
|
|
@@ -1933,23 +1979,10 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1933
1979
|
// persisted across restart) over the runtime id, else drop the runtime
|
|
1934
1980
|
// id so readCliProviderNativeHistory's pin / workspace-latest paths can
|
|
1935
1981
|
// engage. Mirrors the handleChatHistory path's established handling.
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
// (the poisoned agy-coordinator read). Both must drop the
|
|
1941
|
-
// runtime id so pin / live-bind resolution engages; only a real
|
|
1942
|
-
// DISTINCT provider uuid stays as an exact-bind id.
|
|
1943
|
-
const explicitHistorySessionIdForRead = getExplicitHistorySessionId(args);
|
|
1944
|
-
const nativeReadSessionIdIsRuntimeFallback = Boolean(
|
|
1945
|
-
targetSessionId
|
|
1946
|
-
&& isRuntimeFallbackHistorySessionId(nativeHistoryReadSessionId, targetSessionId)
|
|
1947
|
-
&& (!explicitHistorySessionIdForRead
|
|
1948
|
-
|| isRuntimeFallbackHistorySessionId(explicitHistorySessionIdForRead, targetSessionId)),
|
|
1949
|
-
);
|
|
1950
|
-
const effectiveNativeReadSessionId = nativeReadSessionIdIsRuntimeFallback
|
|
1951
|
-
? (pinnedProviderSessionIdForRead || undefined)
|
|
1952
|
-
: nativeHistoryReadSessionId;
|
|
1982
|
+
const {
|
|
1983
|
+
pinnedProviderSessionId: pinnedProviderSessionIdForRead,
|
|
1984
|
+
effectiveHistorySessionId: effectiveNativeReadSessionId,
|
|
1985
|
+
} = resolveNativeHistoryReadSession(args, nativeHistoryReadSessionId);
|
|
1953
1986
|
try {
|
|
1954
1987
|
nativeHistory = readCliProviderNativeHistory(agentStr, {
|
|
1955
1988
|
canonicalHistory: provider?.nativeHistory,
|
|
@@ -2353,30 +2386,18 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
2353
2386
|
// zero rows) even though the transcript is present in state.db. When
|
|
2354
2387
|
// this is that runtime fallback (historySessionId === targetSid and no
|
|
2355
2388
|
// explicit id was passed) and we hold a pin from an earlier bound
|
|
2356
|
-
// read, prefer the pin so the query hits the real session.
|
|
2357
|
-
|
|
2358
|
-
//
|
|
2359
|
-
//
|
|
2360
|
-
//
|
|
2361
|
-
//
|
|
2362
|
-
//
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
&& isRuntimeFallbackHistorySessionId(historySessionId, targetSid)
|
|
2369
|
-
&& (!explicitHistorySessionId
|
|
2370
|
-
|| isRuntimeFallbackHistorySessionId(explicitHistorySessionId, targetSid)),
|
|
2371
|
-
);
|
|
2372
|
-
// When this is the runtime fallback (not a real provider id): prefer
|
|
2373
|
-
// the pin if we have one, else drop the runtime id entirely so the
|
|
2374
|
-
// pin-reuse / workspace-latest logic inside readCliProviderNativeHistory
|
|
2375
|
-
// can engage (passing the runtime id as historySessionId would pin the
|
|
2376
|
-
// query to a non-existent session and never reach those paths).
|
|
2377
|
-
const effectiveHistorySessionIdForRead = historySessionIdIsRuntimeFallback
|
|
2378
|
-
? (pinnedProviderSessionIdForHistory || undefined)
|
|
2379
|
-
: historySessionId;
|
|
2389
|
+
// read, prefer the pin so the query hits the real session. Detects the
|
|
2390
|
+
// fallback whether historySessionId reached targetSid via
|
|
2391
|
+
// getHistorySessionId's internal fallback (empty args) or the browser
|
|
2392
|
+
// explicitly echoed targetSid back (poisoned agy-coordinator
|
|
2393
|
+
// subscription / D8 refreshAuthoritativeTail read); a real DISTINCT
|
|
2394
|
+
// provider uuid still exact-binds unchanged. See
|
|
2395
|
+
// resolveNativeHistoryReadSession.
|
|
2396
|
+
const {
|
|
2397
|
+
isRuntimeFallback: historySessionIdIsRuntimeFallback,
|
|
2398
|
+
pinnedProviderSessionId: pinnedProviderSessionIdForHistory,
|
|
2399
|
+
effectiveHistorySessionId: effectiveHistorySessionIdForRead,
|
|
2400
|
+
} = resolveNativeHistoryReadSession(args, historySessionId);
|
|
2380
2401
|
const history = supportsNative
|
|
2381
2402
|
? readCliProviderNativeHistory(agentStr, {
|
|
2382
2403
|
canonicalHistory: provider?.nativeHistory,
|
|
@@ -8,6 +8,59 @@
|
|
|
8
8
|
import type { LowFamilyContext, LowFamilyHandler } from './types.js';
|
|
9
9
|
|
|
10
10
|
export const coordinatorPromptHandlers: Record<string, LowFamilyHandler> = {
|
|
11
|
+
/**
|
|
12
|
+
* Render the coordinator system prompt for a mesh + CLI type, so the
|
|
13
|
+
* dashboard can show the operator exactly what a coordinator session
|
|
14
|
+
* receives by default. This resolves the mesh, applies its repo-mesh
|
|
15
|
+
* config, and runs the SAME buildCoordinatorSystemPrompt the launch path
|
|
16
|
+
* uses — minus the runtime-only best-effort sections (mission / recent
|
|
17
|
+
* activity / operating notes), which are launch-scope and not part of the
|
|
18
|
+
* static "default base" an operator is trying to preview here.
|
|
19
|
+
*
|
|
20
|
+
* It respects mesh-level and user-file override/append layering, so the
|
|
21
|
+
* preview reflects the effective prompt: with no overrides configured it
|
|
22
|
+
* shows the pure daemon default; with an override set it shows that.
|
|
23
|
+
*/
|
|
24
|
+
coordinator_prompt_preview: async (ctx: LowFamilyContext, args: any) => {
|
|
25
|
+
const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
|
|
26
|
+
const cliType = typeof args?.cliType === 'string' && args.cliType.trim() ? args.cliType.trim() : 'claude-cli';
|
|
27
|
+
if (!meshId) return { success: false, error: 'meshId required' };
|
|
28
|
+
try {
|
|
29
|
+
// Prefer the router-bound resolver (inline cache aware); fall back to
|
|
30
|
+
// local config when a bare context is used (e.g. unit tests).
|
|
31
|
+
let mesh: any = null;
|
|
32
|
+
if (ctx.getMeshForCommand) {
|
|
33
|
+
const resolved = await ctx.getMeshForCommand(meshId);
|
|
34
|
+
mesh = resolved?.mesh ?? null;
|
|
35
|
+
}
|
|
36
|
+
if (!mesh) {
|
|
37
|
+
const { getMesh } = await import('../../config/mesh-config.js');
|
|
38
|
+
mesh = getMesh(meshId);
|
|
39
|
+
}
|
|
40
|
+
if (!mesh) return { success: false, error: `mesh not found: ${meshId}` };
|
|
41
|
+
|
|
42
|
+
// Apply the on-disk repo-mesh config overlay exactly as launch does,
|
|
43
|
+
// so policy/nodes reflect the effective mesh.
|
|
44
|
+
let effectiveMesh = mesh;
|
|
45
|
+
try {
|
|
46
|
+
const { loadRepoMeshJsonConfig, applyRepoMeshConfig } = await import('../../config/mesh-json-config.js');
|
|
47
|
+
const workspace = typeof mesh?.workspace === 'string' ? mesh.workspace : undefined;
|
|
48
|
+
if (workspace) {
|
|
49
|
+
const loaded = loadRepoMeshJsonConfig(workspace);
|
|
50
|
+
if (loaded?.sourceType !== 'invalid') {
|
|
51
|
+
effectiveMesh = applyRepoMeshConfig(mesh, loaded?.config);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch { /* overlay is best-effort — fall back to the raw mesh */ }
|
|
55
|
+
|
|
56
|
+
const { buildCoordinatorSystemPrompt } = await import('../../mesh/coordinator-prompt.js');
|
|
57
|
+
const prompt = buildCoordinatorSystemPrompt({ mesh: effectiveMesh, coordinatorCliType: cliType });
|
|
58
|
+
return { success: true, prompt, cliType, meshId, bytes: Buffer.byteLength(prompt, 'utf8') };
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
return { success: false, error: error?.message || String(error) };
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
11
64
|
list_coordinator_prompts: async (_ctx: LowFamilyContext, _args: any) => {
|
|
12
65
|
const fs = await import('node:fs');
|
|
13
66
|
const path = await import('node:path');
|
|
@@ -411,69 +411,15 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
411
411
|
}
|
|
412
412
|
},
|
|
413
413
|
|
|
414
|
-
// ─── MAGI panels (machine-local config, sibling to meshes) ───────────────
|
|
415
|
-
// Panels live in ~/.adhdev/meshes.json `magiPanels` and are pure local config
|
|
416
|
-
// (no mesh ownership). These three handlers mirror list_meshes/create_mesh/
|
|
417
|
-
// update_mesh: dynamic-import the already-exported mesh-config accessors and
|
|
418
|
-
// surface normalizeMagiPanel's structured error codes (invalid_magi_panel,
|
|
419
|
-
// magi_panel_exists) verbatim so the dashboard can render them.
|
|
420
|
-
//
|
|
421
|
-
// Permission: magi_panel_set / magi_panel_remove are WRITE commands. They are
|
|
422
|
-
// intentionally NOT listed in canPeerUsePrivilegedShareCommand (daemon-cloud
|
|
423
|
-
// data-channel-router), so a peer holding ANY share permission hits its
|
|
424
|
-
// `default → false` branch — identical owner-only gating to create_mesh /
|
|
425
|
-
// update_mesh / list_meshes (none of which are listed there either). A trusted
|
|
426
|
-
// peer (no permission = the owner) passes the top `!permission → true` guard.
|
|
427
|
-
// Mirror, don't invent: do not add a new policy tier here.
|
|
428
|
-
//
|
|
429
|
-
// Resolvability (coupling / stale / available) is deliberately NOT computed
|
|
430
|
-
// here: buildMagiFanoutPlan lives in mcp-server, unreachable from daemon-core.
|
|
431
|
-
// magi_panel_list returns the raw definitions only; the dashboard derives
|
|
432
|
-
// member resolvability client-side (web-core MagiPanelManager, reusing the
|
|
433
|
-
// MagiGroupRow coupling logic) against live mesh_status.
|
|
434
|
-
magi_panel_list: async (_ctx: MedFamilyContext, _args: any) => {
|
|
435
|
-
try {
|
|
436
|
-
const { listMagiPanels } = await import('../../config/mesh-config.js');
|
|
437
|
-
return { success: true, panels: listMagiPanels() };
|
|
438
|
-
} catch (e: any) {
|
|
439
|
-
return { success: false, error: e.message };
|
|
440
|
-
}
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
magi_panel_set: async (_ctx: MedFamilyContext, args: any) => {
|
|
444
|
-
const name = typeof args?.name === 'string' ? args.name.trim() : '';
|
|
445
|
-
if (!name) return { success: false, error: 'invalid_magi_panel: panel name is required' };
|
|
446
|
-
try {
|
|
447
|
-
const { upsertMagiPanel } = await import('../../config/mesh-config.js');
|
|
448
|
-
// normalizeMagiPanel (invoked inside upsertMagiPanel) validates members,
|
|
449
|
-
// enforces MAX_MAGI_PANEL_MEMBERS, and clamps replica counts. Its
|
|
450
|
-
// invalid_magi_panel / magi_panel_exists messages flow back as `error`.
|
|
451
|
-
const panel = upsertMagiPanel(name, args?.panel, { overwrite: args?.overwrite === true });
|
|
452
|
-
return { success: true, name, panel };
|
|
453
|
-
} catch (e: any) {
|
|
454
|
-
// Surface the structured code (invalid_magi_panel: … / magi_panel_exists: …)
|
|
455
|
-
// verbatim so the editor can map it to a field-level message.
|
|
456
|
-
return { success: false, error: e.message };
|
|
457
|
-
}
|
|
458
|
-
},
|
|
459
|
-
|
|
460
|
-
magi_panel_remove: async (_ctx: MedFamilyContext, args: any) => {
|
|
461
|
-
const name = typeof args?.name === 'string' ? args.name.trim() : '';
|
|
462
|
-
if (!name) return { success: false, error: 'invalid_magi_panel: panel name is required' };
|
|
463
|
-
try {
|
|
464
|
-
const { removeMagiPanel } = await import('../../config/mesh-config.js');
|
|
465
|
-
const removed = removeMagiPanel(name);
|
|
466
|
-
return { success: true, removed };
|
|
467
|
-
} catch (e: any) {
|
|
468
|
-
return { success: false, error: e.message };
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
|
|
472
414
|
// ─── MAGI kind → panel bindings (MAGI-KIND-PANEL, machine-local config) ───
|
|
473
|
-
// Per-task_kind slot lists in ~/.adhdev/meshes.json `magiKindPanels
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
415
|
+
// Per-task_kind slot lists in ~/.adhdev/meshes.json `magiKindPanels` — the SOLE
|
|
416
|
+
// MAGI panel-resolution surface (the former named-panel magi_panel_* handlers were
|
|
417
|
+
// removed). Owner-only gating: intentionally NOT listed in
|
|
418
|
+
// canPeerUsePrivilegedShareCommand (daemon-cloud data-channel-router), so a peer
|
|
419
|
+
// holding ANY share permission hits its `default → false` branch — identical
|
|
420
|
+
// owner-only gating to create_mesh / update_mesh / list_meshes. A trusted peer (no
|
|
421
|
+
// permission = the owner) passes the top `!permission → true` guard. set/remove are
|
|
422
|
+
// WRITE commands; list is read-only. normalizeMagiSlots (inside setMagiKindPanel)
|
|
477
423
|
// surfaces invalid_magi_kind_panel: … messages verbatim for the editor.
|
|
478
424
|
magi_kind_panel_list: async (_ctx: MedFamilyContext, _args: any) => {
|
|
479
425
|
try {
|
|
@@ -534,6 +480,9 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
534
480
|
const daemonId = typeof args?.daemonId === 'string' && args.daemonId.trim() ? args.daemonId.trim() : undefined;
|
|
535
481
|
const machineId = typeof args?.machineId === 'string' && args.machineId.trim() ? args.machineId.trim() : undefined;
|
|
536
482
|
const repoRoot = typeof args?.repoRoot === 'string' && args.repoRoot.trim() ? args.repoRoot.trim() : undefined;
|
|
483
|
+
const capabilities = Array.isArray(args?.capabilities)
|
|
484
|
+
? args.capabilities.map((t: any) => typeof t === 'string' ? t.trim() : '').filter(Boolean)
|
|
485
|
+
: undefined;
|
|
537
486
|
const node = addNode(meshId, {
|
|
538
487
|
workspace,
|
|
539
488
|
...(repoRoot ? { repoRoot } : {}),
|
|
@@ -541,6 +490,7 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
541
490
|
...(machineId ? { machineId } : {}),
|
|
542
491
|
...(policy ? { policy } : {}),
|
|
543
492
|
...(role ? { role } : {}),
|
|
493
|
+
...(capabilities && capabilities.length ? { capabilities } : {}),
|
|
544
494
|
});
|
|
545
495
|
if (!node) return { success: false, error: 'Mesh not found' };
|
|
546
496
|
// mesh_status hands back a coordinator-memory aggregate
|
|
@@ -597,6 +547,13 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
|
|
|
597
547
|
} else if (args?.systemPrompt === null) {
|
|
598
548
|
patch.systemPrompt = undefined;
|
|
599
549
|
}
|
|
550
|
+
// Operator custom capability tags. An explicit (possibly empty) array
|
|
551
|
+
// replaces them; omitting the arg leaves existing tags untouched.
|
|
552
|
+
if (Array.isArray(args?.capabilities)) {
|
|
553
|
+
patch.capabilities = args.capabilities
|
|
554
|
+
.map((t: any) => typeof t === 'string' ? t.trim() : '')
|
|
555
|
+
.filter(Boolean);
|
|
556
|
+
}
|
|
600
557
|
const node = updateNode(meshId, nodeId, patch as any);
|
|
601
558
|
if (!node) return { success: false, error: 'Mesh node not found' };
|
|
602
559
|
// Provider priority / systemPrompt changes don't touch
|