@adhdev/daemon-core 0.9.82-rc.480 → 0.9.82-rc.482
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 +5 -28
- package/dist/index.d.ts +2 -2
- package/dist/index.js +243 -246
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +243 -241
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/contracts.d.ts +8 -5
- 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 +7 -13
- package/dist/types.d.ts +15 -6
- package/package.json +3 -3
- package/src/commands/chat-commands-read.ts +81 -60
- package/src/commands/med-family/mesh-crud.ts +8 -62
- package/src/config/mesh-config.ts +9 -140
- package/src/index.ts +1 -2
- package/src/mesh/contracts.ts +17 -11
- package/src/mesh/coordinator-prompt.ts +3 -6
- package/src/mesh/mesh-event-forwarding.ts +38 -102
- 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 +7 -13
- package/src/types.ts +21 -6
package/dist/mesh/contracts.d.ts
CHANGED
|
@@ -172,11 +172,14 @@ export declare function assertPendingMeshCoordinatorEventV2(raw: unknown, path?:
|
|
|
172
172
|
export declare function shouldDeliverPendingEventToCoordinator(event: PendingMeshCoordinatorEventV2, drainer: CoordinatorIdentity): boolean;
|
|
173
173
|
/**
|
|
174
174
|
* 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
|
-
*
|
|
175
|
+
* §3). Terminal task events and coordinator-addressed alerts → unicast (routed
|
|
176
|
+
* to the originating coordinator). Everything else — node lifecycle and
|
|
177
|
+
* progress signals — → broadcast, which also matches v1's implicit "deliver to
|
|
178
|
+
* any coordinator" behaviour, so an unstamped v1 event and a v2-stamped-as-
|
|
179
|
+
* broadcast event route identically during rollout. No event currently defaults
|
|
180
|
+
* to 'system'; the scope remains in MESH_EVENT_SCOPES for wire compatibility
|
|
181
|
+
* (an already-queued or version-skewed 'system' event still routes away from
|
|
182
|
+
* coordinators).
|
|
180
183
|
*/
|
|
181
184
|
export declare function defaultScopeForEvent(eventName: string): MeshEventScope;
|
|
182
185
|
/**
|
|
@@ -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;
|
|
@@ -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.482",
|
|
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.482",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.482",
|
|
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,
|
|
@@ -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 {
|
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
RepoMeshHostMetadata,
|
|
23
23
|
RepoMeshDaemonRole,
|
|
24
24
|
} from '../repo-mesh-types.js';
|
|
25
|
-
import type {
|
|
25
|
+
import type { MagiKindPanelMap, MagiSlot, MagiTaskKind } from '@adhdev/mesh-shared';
|
|
26
26
|
import { mergeAndNormalizePolicy } from '../repo-mesh-types.js';
|
|
27
27
|
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
28
28
|
|
|
@@ -626,8 +626,10 @@ export function updateNode(
|
|
|
626
626
|
|
|
627
627
|
// ─── MAGI Panels (machine-local cross-verification quorums) ──
|
|
628
628
|
|
|
629
|
-
|
|
630
|
-
|
|
629
|
+
// NOTE: the named-panel model (normalizeMagiPanel / list / get / upsert / remove,
|
|
630
|
+
// stored under meshes.json `magiPanels`) was REMOVED. MAGI now resolves its fan-out
|
|
631
|
+
// slots SOLELY from the per-task_kind `magiKindPanels` binding below. `normalizeMagiSlots`
|
|
632
|
+
// is the sole slot normalizer.
|
|
631
633
|
|
|
632
634
|
function normalizeReplicaCount(value: unknown): number | undefined {
|
|
633
635
|
if (typeof value !== 'number' || !Number.isFinite(value)) return undefined;
|
|
@@ -635,138 +637,6 @@ function normalizeReplicaCount(value: unknown): number | undefined {
|
|
|
635
637
|
return n >= 1 ? n : undefined;
|
|
636
638
|
}
|
|
637
639
|
|
|
638
|
-
/**
|
|
639
|
-
* Normalize a panel `defaultKind` (the non-binding default output kind). Returns
|
|
640
|
-
* undefined (drop, don't throw) for any absent / unknown value so a stray field
|
|
641
|
-
* never blocks a panel write. 'freeform' is explicitly DROPPED with a warning: a
|
|
642
|
-
* panel is a cross-verification tool and freeform contributes no structured claims
|
|
643
|
-
* (claims:[]), so defaulting to it would silently zero out the very thing the panel
|
|
644
|
-
* exists for. Only the evidence-bearing kinds (claim_audit / rca / design) survive.
|
|
645
|
-
*/
|
|
646
|
-
function normalizeMagiPanelDefaultKind(raw: unknown): MagiPanelDefaultKind | undefined {
|
|
647
|
-
if (raw == null) return undefined;
|
|
648
|
-
const s = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
649
|
-
if (s === 'claim_audit' || s === 'rca' || s === 'design') return s;
|
|
650
|
-
if (s === 'freeform') {
|
|
651
|
-
// eslint-disable-next-line no-console
|
|
652
|
-
console.warn(
|
|
653
|
-
"[magi] panel defaultKind='freeform' rejected — freeform contributes no structured claims to cross-verification; dropping (use claim_audit / rca / design, or omit).",
|
|
654
|
-
);
|
|
655
|
-
return undefined;
|
|
656
|
-
}
|
|
657
|
-
// Any other value (typo / unsupported kind): drop silently — the panel still
|
|
658
|
-
// resolves to the claim_audit fallback at review time.
|
|
659
|
-
return undefined;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Validate + normalize a panel config before persisting. Mirrors the node-config
|
|
664
|
-
* normalization style (mesh-config addNode/updateNode): trims strings, drops
|
|
665
|
-
* empties, requires a provider per member, clamps replica counts. Throws on
|
|
666
|
-
* structurally invalid input so the calling tool returns a clear error rather than
|
|
667
|
-
* writing a malformed panel.
|
|
668
|
-
*/
|
|
669
|
-
export function normalizeMagiPanel(config: unknown): MagiPanel {
|
|
670
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
671
|
-
throw new Error('invalid_magi_panel: config must be an object');
|
|
672
|
-
}
|
|
673
|
-
const raw = config as Record<string, unknown>;
|
|
674
|
-
const rawMembers = raw.members;
|
|
675
|
-
if (!Array.isArray(rawMembers) || rawMembers.length === 0) {
|
|
676
|
-
throw new Error('invalid_magi_panel: members must be a non-empty array');
|
|
677
|
-
}
|
|
678
|
-
if (rawMembers.length > MAX_MAGI_PANEL_MEMBERS) {
|
|
679
|
-
throw new Error(`invalid_magi_panel: too many members (max ${MAX_MAGI_PANEL_MEMBERS})`);
|
|
680
|
-
}
|
|
681
|
-
const members: MagiPanelMember[] = rawMembers.map((entry, idx) => {
|
|
682
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
683
|
-
throw new Error(`invalid_magi_panel: member[${idx}] must be an object`);
|
|
684
|
-
}
|
|
685
|
-
const m = entry as Record<string, unknown>;
|
|
686
|
-
const provider = typeof m.provider === 'string' ? m.provider.trim() : '';
|
|
687
|
-
if (!provider) {
|
|
688
|
-
throw new Error(`invalid_magi_panel: member[${idx}].provider is required`);
|
|
689
|
-
}
|
|
690
|
-
const nodeId = typeof m.nodeId === 'string' && m.nodeId.trim() ? m.nodeId.trim() : undefined;
|
|
691
|
-
const model = typeof m.model === 'string' && m.model.trim() ? m.model.trim() : undefined;
|
|
692
|
-
const capabilityTags = normalizeCapabilityTags(m.capabilityTags);
|
|
693
|
-
const n = normalizeReplicaCount(m.n);
|
|
694
|
-
return {
|
|
695
|
-
provider,
|
|
696
|
-
...(nodeId ? { nodeId } : {}),
|
|
697
|
-
...(model ? { model } : {}),
|
|
698
|
-
...(capabilityTags ? { capabilityTags } : {}),
|
|
699
|
-
...(n !== undefined ? { n } : {}),
|
|
700
|
-
};
|
|
701
|
-
});
|
|
702
|
-
const description = typeof raw.description === 'string' && raw.description.trim()
|
|
703
|
-
? raw.description.trim().slice(0, 200)
|
|
704
|
-
: undefined;
|
|
705
|
-
const defaultN = normalizeReplicaCount(raw.defaultN);
|
|
706
|
-
const defaultKind = normalizeMagiPanelDefaultKind(raw.defaultKind);
|
|
707
|
-
return {
|
|
708
|
-
...(description ? { description } : {}),
|
|
709
|
-
members,
|
|
710
|
-
...(defaultN !== undefined ? { defaultN } : {}),
|
|
711
|
-
...(defaultKind !== undefined ? { defaultKind } : {}),
|
|
712
|
-
// dedupExempt is always meaningful for a MAGI panel (intentional same-prompt
|
|
713
|
-
// fan-out). Persist it true unless the caller explicitly disables it.
|
|
714
|
-
dedupExempt: raw.dedupExempt === false ? false : true,
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
function normalizePanelName(name: unknown): string {
|
|
719
|
-
const trimmed = typeof name === 'string' ? name.trim() : '';
|
|
720
|
-
if (!trimmed) throw new Error('invalid_magi_panel: panel name is required');
|
|
721
|
-
return trimmed.slice(0, 100);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/** All configured MAGI panels (machine-local), keyed by name. Empty when none. */
|
|
725
|
-
export function listMagiPanels(): Record<string, MagiPanel> {
|
|
726
|
-
return loadMeshConfig().magiPanels ?? {};
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/** A single panel by name, or undefined when not configured. */
|
|
730
|
-
export function getMagiPanel(name: string): MagiPanel | undefined {
|
|
731
|
-
const key = typeof name === 'string' ? name.trim() : '';
|
|
732
|
-
if (!key) return undefined;
|
|
733
|
-
return loadMeshConfig().magiPanels?.[key];
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Upsert a named panel into meshes.json. Defaults to refusing to clobber an
|
|
738
|
-
* existing panel (overwrite=false) — mirrors the mesh_init write/overwrite
|
|
739
|
-
* precedent. Returns the normalized, persisted panel.
|
|
740
|
-
*/
|
|
741
|
-
export function upsertMagiPanel(
|
|
742
|
-
name: string,
|
|
743
|
-
config: unknown,
|
|
744
|
-
opts: { overwrite?: boolean } = {},
|
|
745
|
-
): MagiPanel {
|
|
746
|
-
const key = normalizePanelName(name);
|
|
747
|
-
const panel = normalizeMagiPanel(config);
|
|
748
|
-
const stored = loadMeshConfig();
|
|
749
|
-
const panels = stored.magiPanels ?? {};
|
|
750
|
-
if (panels[key] && opts.overwrite !== true) {
|
|
751
|
-
throw new Error(`magi_panel_exists: panel '${key}' already exists — pass overwrite=true to replace it`);
|
|
752
|
-
}
|
|
753
|
-
panels[key] = panel;
|
|
754
|
-
stored.magiPanels = panels;
|
|
755
|
-
saveMeshConfig(stored);
|
|
756
|
-
return panel;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/** Remove a named panel. Returns true when a panel was removed. */
|
|
760
|
-
export function removeMagiPanel(name: string): boolean {
|
|
761
|
-
const key = typeof name === 'string' ? name.trim() : '';
|
|
762
|
-
if (!key) return false;
|
|
763
|
-
const stored = loadMeshConfig();
|
|
764
|
-
if (!stored.magiPanels || !stored.magiPanels[key]) return false;
|
|
765
|
-
delete stored.magiPanels[key];
|
|
766
|
-
saveMeshConfig(stored);
|
|
767
|
-
return true;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
640
|
// ─── MAGI kind → panel bindings (MAGI-KIND-PANEL) ─────────
|
|
771
641
|
//
|
|
772
642
|
// Per-task_kind slot lists (machine-local, meshes.json `magiKindPanels`). A bare
|
|
@@ -788,11 +658,10 @@ function normalizeMagiTaskKindKey(raw: unknown): MagiTaskKind {
|
|
|
788
658
|
}
|
|
789
659
|
|
|
790
660
|
/**
|
|
791
|
-
* Validate + normalize a kind-panel's slots
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
* error. Returns the normalized slot array.
|
|
661
|
+
* Validate + normalize a kind-panel's slots (the SOLE MAGI slot normalizer): provider
|
|
662
|
+
* required per slot, trims strings, drops empties, clamps replica counts, and carries
|
|
663
|
+
* an optional per-slot `model`. Throws on structurally invalid input (empty list / no
|
|
664
|
+
* provider) so the write returns a clear error. Returns the normalized slot array.
|
|
796
665
|
*/
|
|
797
666
|
export function normalizeMagiSlots(slots: unknown): MagiSlot[] {
|
|
798
667
|
if (!Array.isArray(slots) || slots.length === 0) {
|