@adhdev/daemon-core 0.9.82-rc.366 → 0.9.82-rc.368
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/commands/chat-commands.d.ts +27 -0
- package/dist/commands/router.d.ts +34 -0
- package/dist/index.js +220 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +220 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/commands/chat-commands.ts +96 -2
- package/src/commands/cli-manager.ts +22 -0
- package/src/commands/router.ts +135 -8
- package/src/mesh/mesh-events-coordinator.ts +144 -32
- package/src/mesh/mesh-reconcile-loop.ts +38 -0
|
@@ -6,6 +6,33 @@ import type { CommandResult, CommandHelpers } from './handler.js';
|
|
|
6
6
|
import { type InputEnvelope } from '../providers/contracts.js';
|
|
7
7
|
export declare const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25000;
|
|
8
8
|
export declare function buildSendInputSignature(input: InputEnvelope): string;
|
|
9
|
+
/**
|
|
10
|
+
* read_chat node scope verdict. One physical daemon hosts a base node plus several
|
|
11
|
+
* worktree nodes; mesh_read_chat always dispatches read_chat with the requested
|
|
12
|
+
* node's workspace (`args.workspace`). When the resolved target session actually
|
|
13
|
+
* lives in a DIFFERENT worktree, returning its transcript — or worse, letting the
|
|
14
|
+
* native-history-by-workspace fallback splice sibling worktree turns into the
|
|
15
|
+
* reply — makes the coordinator believe one session received every worktree's
|
|
16
|
+
* work. This guard refuses a CONFIRMED cross-workspace read instead of mixing.
|
|
17
|
+
*
|
|
18
|
+
* Conservative by design (mirrors the WTCLAIM fix-B "unknown → allow" rule): only
|
|
19
|
+
* a session id that resolves to a known workspace which is unequal to a known
|
|
20
|
+
* intended workspace blocks. When either side is unknown — no targetSessionId, no
|
|
21
|
+
* args.workspace, an unregistered session, the coordinator self-session, or a
|
|
22
|
+
* plain dashboard read that never passes a node workspace — the read proceeds
|
|
23
|
+
* untouched, so base-node and same-daemon coordinator reads never regress.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateReadChatNodeWorkspaceScope(args: {
|
|
26
|
+
targetSessionId?: string;
|
|
27
|
+
intendedWorkspace?: string;
|
|
28
|
+
sessionWorkspace?: string;
|
|
29
|
+
}): {
|
|
30
|
+
scoped: false;
|
|
31
|
+
} | {
|
|
32
|
+
scoped: true;
|
|
33
|
+
intended: string;
|
|
34
|
+
actual: string;
|
|
35
|
+
};
|
|
9
36
|
interface DebugSanitizeOptions {
|
|
10
37
|
maxDepth?: number;
|
|
11
38
|
maxArrayLength?: number;
|
|
@@ -86,6 +86,7 @@ export declare function finalizeMeshNodeStatus(args: {
|
|
|
86
86
|
}): void;
|
|
87
87
|
export declare const MESH_DIRECT_PROBE_TIMEOUT_MS: number;
|
|
88
88
|
export declare const MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS: number;
|
|
89
|
+
export declare const MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS: number;
|
|
89
90
|
/**
|
|
90
91
|
* De-duplicates and rate-limits per-peer git_status probes so a single mesh
|
|
91
92
|
* refresh — or a burst of refreshes from the dashboard auto-retry loop — cannot
|
|
@@ -116,6 +117,37 @@ export declare class MeshGitProbeCache {
|
|
|
116
117
|
*/
|
|
117
118
|
probe(daemonId: string, workspace: string, probe: () => Promise<Record<string, unknown> | null>): Promise<Record<string, unknown> | null>;
|
|
118
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Await `work` under a warmup-aware deadline so a cold-open DataChannel handshake
|
|
122
|
+
* is NOT charged against the command response budget — the root cause of the
|
|
123
|
+
* "first mesh probe to a cold peer false-times-out, the warm retry succeeds"
|
|
124
|
+
* signature. Two budgets, switched by the live peer connection state:
|
|
125
|
+
*
|
|
126
|
+
* - While `isConnected()` returns false the peer's channel is still opening; the
|
|
127
|
+
* cold-open `connectTimeoutMs` budget applies. This phase is deliberately
|
|
128
|
+
* generous because a TURN-relayed cross-machine handshake legitimately needs
|
|
129
|
+
* many seconds — but a genuine connect *failure* is surfaced by `work`
|
|
130
|
+
* rejecting on its own (the mesh manager fails the peer the instant its
|
|
131
|
+
* PeerConnection state goes terminal), so a real failure is never masked for
|
|
132
|
+
* the whole window.
|
|
133
|
+
* - The first time `isConnected()` returns true the channel is warm; from that
|
|
134
|
+
* instant the tight `responseTimeoutMs` governs how long the handler may take.
|
|
135
|
+
* Warm-channel callers therefore see behavior identical to the old single
|
|
136
|
+
* `Promise.race(work, responseTimeoutMs)`.
|
|
137
|
+
*
|
|
138
|
+
* Rejects with `Error('timeout')` when either budget is exhausted, mirroring the
|
|
139
|
+
* previous single-race contract. Pure except for timers + the injected
|
|
140
|
+
* `isConnected` probe, so it is unit-testable under fake timers without any real
|
|
141
|
+
* WebRTC. When no connection getter is wired `isConnected` should be `() => true`
|
|
142
|
+
* (the caller's choice) so the response deadline governs from t0 — the legacy
|
|
143
|
+
* single-budget behavior, never a combined connect+response window.
|
|
144
|
+
*/
|
|
145
|
+
export declare function awaitWithWarmupDeadline<T>(work: Promise<T>, opts: {
|
|
146
|
+
isConnected: () => boolean;
|
|
147
|
+
connectTimeoutMs: number;
|
|
148
|
+
responseTimeoutMs: number;
|
|
149
|
+
pollIntervalMs?: number;
|
|
150
|
+
}): Promise<T>;
|
|
119
151
|
/**
|
|
120
152
|
* Probe a remote peer's git_status with a bounded retry budget, but only while
|
|
121
153
|
* the peer is reported `connected`. A single slow (often TURN-relayed) peer can
|
|
@@ -136,6 +168,8 @@ export declare function probeRemoteMeshGitStatusWithRetry(args: {
|
|
|
136
168
|
timeoutMs: number;
|
|
137
169
|
/** Per-attempt timeout for retries (attempts > 0); defaults to timeoutMs. */
|
|
138
170
|
retryTimeoutMs?: number;
|
|
171
|
+
/** Cold-open warmup budget per attempt; defaults to MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS. */
|
|
172
|
+
connectTimeoutMs?: number;
|
|
139
173
|
getConnection?: (daemonId: string) => Record<string, unknown> | null;
|
|
140
174
|
onConnection?: (connection: Record<string, unknown>) => void;
|
|
141
175
|
}): Promise<Record<string, unknown> | null>;
|
package/dist/index.js
CHANGED
|
@@ -316,10 +316,10 @@ function readInjected(value) {
|
|
|
316
316
|
}
|
|
317
317
|
function getDaemonBuildInfo() {
|
|
318
318
|
if (cached) return cached;
|
|
319
|
-
const commit = readInjected(true ? "
|
|
320
|
-
const commitShort = readInjected(true ? "
|
|
321
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
322
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
319
|
+
const commit = readInjected(true ? "8cb6cfc5399f55c6b625095bacc9a27e1741df17" : void 0) ?? "unknown";
|
|
320
|
+
const commitShort = readInjected(true ? "8cb6cfc5" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
321
|
+
const version = readInjected(true ? "0.9.82-rc.368" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
322
|
+
const builtAt = readInjected(true ? "2026-06-24T06:14:32.255Z" : void 0);
|
|
323
323
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
324
324
|
return cached;
|
|
325
325
|
}
|
|
@@ -2903,6 +2903,16 @@ function meshNodeIdMatches(node, candidateId) {
|
|
|
2903
2903
|
if (!trimmed) return false;
|
|
2904
2904
|
return normalizeMeshNodeId(node) === trimmed;
|
|
2905
2905
|
}
|
|
2906
|
+
function normalizeMeshWorkspaceForCompare(dir) {
|
|
2907
|
+
if (typeof dir !== "string") return "";
|
|
2908
|
+
return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
2909
|
+
}
|
|
2910
|
+
function meshWorkspacesEquivalent(a, b) {
|
|
2911
|
+
const left = normalizeMeshWorkspaceForCompare(a);
|
|
2912
|
+
const right = normalizeMeshWorkspaceForCompare(b);
|
|
2913
|
+
if (!left || !right) return false;
|
|
2914
|
+
return left === right;
|
|
2915
|
+
}
|
|
2906
2916
|
function machineCoreFromDaemonId(id) {
|
|
2907
2917
|
const trimmed = readString3(id);
|
|
2908
2918
|
if (!trimmed) return void 0;
|
|
@@ -12856,21 +12866,29 @@ function deliverTaskToSession(dispatchThunk, ctx) {
|
|
|
12856
12866
|
}
|
|
12857
12867
|
});
|
|
12858
12868
|
}
|
|
12859
|
-
function normalizeMeshWorkspaceForCompare(dir) {
|
|
12860
|
-
if (typeof dir !== "string") return "";
|
|
12861
|
-
return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
12862
|
-
}
|
|
12863
12869
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
12864
12870
|
const mesh = getMeshWithCache(components, meshId);
|
|
12865
12871
|
const node = mesh?.nodes.find((n) => readMeshNodeId(n) === nodeId);
|
|
12866
12872
|
const localClaimAdapter = components.cliManager?.adapters?.get(sessionId);
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12873
|
+
let claimInstanceWorkspace = "";
|
|
12874
|
+
let claimStampedNodeId = "";
|
|
12875
|
+
try {
|
|
12876
|
+
const claimState = components.instanceManager?.getInstance?.(sessionId)?.getState?.();
|
|
12877
|
+
claimInstanceWorkspace = readNonEmptyString2(claimState?.workspace);
|
|
12878
|
+
const claimSettings = claimState?.settings || {};
|
|
12879
|
+
claimStampedNodeId = readNonEmptyString2(claimSettings.meshNodeId);
|
|
12880
|
+
} catch {
|
|
12881
|
+
}
|
|
12882
|
+
const nodeWorkspaceRaw = readNonEmptyString2(node?.workspace);
|
|
12883
|
+
const sessionWorkspaceRaw = readNonEmptyString2(localClaimAdapter?.workingDir) || claimInstanceWorkspace;
|
|
12884
|
+
if (claimStampedNodeId && nodeId) {
|
|
12885
|
+
if (!meshNodeIdMatches({ id: claimStampedNodeId }, nodeId)) {
|
|
12886
|
+
LOG.info("MeshQueue", `WTDISPATCH: refusing claim for node ${nodeId} (${sessionId}) \u2014 session is bound to node "${claimStampedNodeId}" (cross-node claim blocked)`);
|
|
12872
12887
|
return false;
|
|
12873
12888
|
}
|
|
12889
|
+
} else if (sessionWorkspaceRaw && nodeWorkspaceRaw && !meshWorkspacesEquivalent(sessionWorkspaceRaw, nodeWorkspaceRaw)) {
|
|
12890
|
+
LOG.info("MeshQueue", `WTCLAIM: refusing claim for node ${nodeId} (${sessionId}) \u2014 session workspace "${normalizeMeshWorkspaceForCompare(sessionWorkspaceRaw)}" \u2260 node workspace "${normalizeMeshWorkspaceForCompare(nodeWorkspaceRaw)}" (cross-workspace dispatch blocked)`);
|
|
12891
|
+
return false;
|
|
12874
12892
|
}
|
|
12875
12893
|
const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
|
|
12876
12894
|
const providerMaxParallel = resolveProviderMaxParallel(node?.policy, providerType);
|
|
@@ -13711,7 +13729,10 @@ function injectMeshSystemMessage(components, args) {
|
|
|
13711
13729
|
if (terminal?.kind === "task_completed" && !sessionHasActiveAssignment(args.meshId, eventSessionId)) {
|
|
13712
13730
|
const newDispatchAfterTerminal = hasDispatchAfterTerminal(args.meshId, eventSessionId, terminal.id);
|
|
13713
13731
|
const supersedesWeakTerminal = isWeakTerminalLedgerPayload(terminal.payload) && isGenuineCompletionEvidence(args.metadataEvent);
|
|
13714
|
-
|
|
13732
|
+
const terminalTaskId = readNonEmptyString2(terminal.payload.taskId);
|
|
13733
|
+
const eventTaskId = readNonEmptyString2(args.metadataEvent.taskId);
|
|
13734
|
+
const distinctTaskCompletion = !!eventTaskId && !!terminalTaskId && eventTaskId !== terminalTaskId;
|
|
13735
|
+
if (!newDispatchAfterTerminal && !supersedesWeakTerminal && !distinctTaskCompletion) {
|
|
13715
13736
|
const terminalProviderSessionId = readNonEmptyString2(terminal.payload.providerSessionId);
|
|
13716
13737
|
const terminalFinalSummary = readNonEmptyString2(terminal.payload.finalSummary);
|
|
13717
13738
|
const eventProviderSessionId = readNonEmptyString2(args.metadataEvent.providerSessionId);
|
|
@@ -14145,6 +14166,25 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
14145
14166
|
metadataEvent: buildRelayMetadataEvent(payload)
|
|
14146
14167
|
});
|
|
14147
14168
|
}
|
|
14169
|
+
function enqueueCoordinatorForwardPush(coordinatorDaemonId, run) {
|
|
14170
|
+
let lane = coordinatorForwardLanes.get(coordinatorDaemonId);
|
|
14171
|
+
if (!lane) {
|
|
14172
|
+
lane = { tail: Promise.resolve(), depth: 0 };
|
|
14173
|
+
coordinatorForwardLanes.set(coordinatorDaemonId, lane);
|
|
14174
|
+
}
|
|
14175
|
+
const wasIdle = lane.depth === 0;
|
|
14176
|
+
lane.depth += 1;
|
|
14177
|
+
const dec = () => {
|
|
14178
|
+
lane.depth -= 1;
|
|
14179
|
+
};
|
|
14180
|
+
if (wasIdle) {
|
|
14181
|
+
lane.tail = Promise.resolve(run()).catch(() => {
|
|
14182
|
+
}).then(dec, dec);
|
|
14183
|
+
} else {
|
|
14184
|
+
lane.tail = lane.tail.then(() => run()).catch(() => {
|
|
14185
|
+
}).then(dec, dec);
|
|
14186
|
+
}
|
|
14187
|
+
}
|
|
14148
14188
|
function forwardUnresolvedDelegateEvent(components, routing, event) {
|
|
14149
14189
|
const coordinatorDaemonId = readNonEmptyString2(routing.coordinatorDaemonId);
|
|
14150
14190
|
if (!coordinatorDaemonId) return false;
|
|
@@ -14157,6 +14197,16 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
|
|
|
14157
14197
|
nodeId: readNonEmptyString2(routing.nodeId) || readNonEmptyString2(event.meshNodeId) || void 0,
|
|
14158
14198
|
workspace: readNonEmptyString2(routing.workspace) || readNonEmptyString2(event.workspace) || void 0
|
|
14159
14199
|
};
|
|
14200
|
+
const selfDaemonIds = resolveCoordinatorDrainDaemonIds(components);
|
|
14201
|
+
if (selfDaemonIds.some((self) => daemonIdsEquivalent(self, coordinatorDaemonId))) {
|
|
14202
|
+
try {
|
|
14203
|
+
handleMeshForwardEvent(components, payload);
|
|
14204
|
+
LOG.info("MeshEvents", `Self-addressed unresolved-delegate ${eventName} routed via local router (coordinator ${coordinatorDaemonId} is self) \u2014 outbox skipped`);
|
|
14205
|
+
} catch (e) {
|
|
14206
|
+
LOG.warn("MeshEvents", `Local route of self-addressed unresolved-delegate ${eventName} failed: ${e?.message || e}`);
|
|
14207
|
+
}
|
|
14208
|
+
return true;
|
|
14209
|
+
}
|
|
14160
14210
|
const persisted = enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, payload);
|
|
14161
14211
|
const fwdTraceCtx = {
|
|
14162
14212
|
taskId: payload.taskId,
|
|
@@ -14166,7 +14216,8 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
|
|
|
14166
14216
|
};
|
|
14167
14217
|
traceMeshEventStage("outbox_enqueue", fwdTraceCtx, `coordinatorDaemon=${coordinatorDaemonId} meshId=absent`);
|
|
14168
14218
|
traceMeshEventStage("forward_send", fwdTraceCtx, "immediate push");
|
|
14169
|
-
|
|
14219
|
+
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
14220
|
+
enqueueCoordinatorForwardPush(coordinatorDaemonId, () => Promise.resolve(dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
|
|
14170
14221
|
if (result && result.success === false) {
|
|
14171
14222
|
LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued for retry`);
|
|
14172
14223
|
traceMeshEventDrop("immediate_forward_rejected", fwdTraceCtx, readNonEmptyString2(result.error) || "no reason");
|
|
@@ -14175,7 +14226,7 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
|
|
|
14175
14226
|
if (persisted) ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload);
|
|
14176
14227
|
}).catch((e) => {
|
|
14177
14228
|
LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued for retry`);
|
|
14178
|
-
});
|
|
14229
|
+
}));
|
|
14179
14230
|
LOG.info("MeshEvents", `Durably forwarded ${eventName} for unresolved-mesh worker at ${routing.workspace || "(no workspace)"} to coordinator daemon ${coordinatorDaemonId}`);
|
|
14180
14231
|
return true;
|
|
14181
14232
|
}
|
|
@@ -14258,7 +14309,7 @@ function setupMeshEventForwarding(components) {
|
|
|
14258
14309
|
});
|
|
14259
14310
|
});
|
|
14260
14311
|
}
|
|
14261
|
-
var import_fs13, REMOTE_IDLE_SESSION_TTL_MS, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, DISPATCH_CONFIRM_TIMEOUT_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, MESH_FORCE_INJECT_EVENTS;
|
|
14312
|
+
var import_fs13, REMOTE_IDLE_SESSION_TTL_MS, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, DISPATCH_CONFIRM_TIMEOUT_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, MESH_FORCE_INJECT_EVENTS, coordinatorForwardLanes;
|
|
14262
14313
|
var init_mesh_events_coordinator = __esm({
|
|
14263
14314
|
"src/mesh/mesh-events-coordinator.ts"() {
|
|
14264
14315
|
"use strict";
|
|
@@ -14323,6 +14374,7 @@ var init_mesh_events_coordinator = __esm({
|
|
|
14323
14374
|
"worktree_bootstrap_complete",
|
|
14324
14375
|
"worktree_bootstrap_failed"
|
|
14325
14376
|
]);
|
|
14377
|
+
coordinatorForwardLanes = /* @__PURE__ */ new Map();
|
|
14326
14378
|
}
|
|
14327
14379
|
});
|
|
14328
14380
|
|
|
@@ -14665,6 +14717,8 @@ async function retryUnresolvedDelegateForwards(components) {
|
|
|
14665
14717
|
expireStaleUnresolvedDelegateForwards();
|
|
14666
14718
|
const entries = peekUnresolvedDelegateForwards();
|
|
14667
14719
|
if (entries.length === 0) return;
|
|
14720
|
+
const selfIds = resolveCoordinatorDaemonIds(components);
|
|
14721
|
+
const isSelfCoordinatorId = (id) => selfIds.some((self) => daemonIdsEquivalent(self, id));
|
|
14668
14722
|
for (const entry of entries) {
|
|
14669
14723
|
const entryTraceCtx = {
|
|
14670
14724
|
taskId: entry.payload.taskId,
|
|
@@ -14672,6 +14726,23 @@ async function retryUnresolvedDelegateForwards(components) {
|
|
|
14672
14726
|
nodeId: readNonEmptyString2(entry.payload.nodeId),
|
|
14673
14727
|
event: readNonEmptyString2(entry.payload.event)
|
|
14674
14728
|
};
|
|
14729
|
+
if (isSelfCoordinatorId(entry.coordinatorDaemonId)) {
|
|
14730
|
+
let localResult;
|
|
14731
|
+
try {
|
|
14732
|
+
traceMeshEventStage("forward_send", entryTraceCtx, `self \u2192 local router (${entry.coordinatorDaemonId})`);
|
|
14733
|
+
localResult = handleMeshForwardEvent(components, entry.payload);
|
|
14734
|
+
} catch (e) {
|
|
14735
|
+
LOG.warn("MeshReconcile", `Local route of self-addressed forward to ${entry.coordinatorDaemonId} threw: ${e?.message || e} \u2014 draining anyway to break the retry loop`);
|
|
14736
|
+
}
|
|
14737
|
+
ackUnresolvedDelegateForward(entry.id);
|
|
14738
|
+
if (localResult && localResult.success === false) {
|
|
14739
|
+
LOG.warn("MeshReconcile", `Self-addressed unresolved-delegate ${readNonEmptyString2(entry.payload.event)} rejected by local router (${readNonEmptyString2(localResult.error) || "no reason"}) \u2014 drained to break the self-forward retry loop`);
|
|
14740
|
+
traceMeshEventDrop("self_forward_local_rejected", entryTraceCtx, readNonEmptyString2(localResult.error) || "no reason");
|
|
14741
|
+
} else {
|
|
14742
|
+
LOG.info("MeshReconcile", `Self-addressed unresolved-delegate ${readNonEmptyString2(entry.payload.event)} routed via local router (coordinator ${entry.coordinatorDaemonId} is self) \u2014 drained`);
|
|
14743
|
+
}
|
|
14744
|
+
continue;
|
|
14745
|
+
}
|
|
14675
14746
|
let result;
|
|
14676
14747
|
try {
|
|
14677
14748
|
traceMeshEventStage("forward_send", entryTraceCtx, `retry \u2192 ${entry.coordinatorDaemonId}`);
|
|
@@ -28227,6 +28298,7 @@ var CHAT_SOURCE_REGISTRY = new ChatSourceRegistry();
|
|
|
28227
28298
|
|
|
28228
28299
|
// src/commands/chat-commands.ts
|
|
28229
28300
|
init_chat_message_normalization();
|
|
28301
|
+
init_dist();
|
|
28230
28302
|
var RECENT_SEND_WINDOW_MS = 1200;
|
|
28231
28303
|
var READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25e3;
|
|
28232
28304
|
var HOT_TAIL_MIN_LIMIT = 60;
|
|
@@ -28788,6 +28860,24 @@ function normalizeComparableWorkspace(value) {
|
|
|
28788
28860
|
if (!text) return "";
|
|
28789
28861
|
return path16.resolve(text);
|
|
28790
28862
|
}
|
|
28863
|
+
function evaluateReadChatNodeWorkspaceScope(args) {
|
|
28864
|
+
const targetSessionId = typeof args.targetSessionId === "string" ? args.targetSessionId.trim() : "";
|
|
28865
|
+
if (!targetSessionId) return { scoped: false };
|
|
28866
|
+
const intended = normalizeMeshWorkspaceForCompare(args.intendedWorkspace);
|
|
28867
|
+
const actual = normalizeMeshWorkspaceForCompare(args.sessionWorkspace);
|
|
28868
|
+
if (!intended || !actual) return { scoped: false };
|
|
28869
|
+
if (intended === actual) return { scoped: false };
|
|
28870
|
+
return { scoped: true, intended, actual };
|
|
28871
|
+
}
|
|
28872
|
+
function resolveTargetSessionActualWorkspace(h, targetSessionId) {
|
|
28873
|
+
const registryWorkspace = h.ctx?.sessionRegistry?.get?.(targetSessionId)?.workspace;
|
|
28874
|
+
if (typeof registryWorkspace === "string" && registryWorkspace.trim()) return registryWorkspace;
|
|
28875
|
+
const adapter = h.getCliAdapter?.(targetSessionId);
|
|
28876
|
+
if (adapter && typeof adapter.workingDir === "string" && adapter.workingDir.trim()) return adapter.workingDir;
|
|
28877
|
+
const instanceWorkspace = getTargetInstance(h, { targetSessionId })?.getState?.()?.workspace;
|
|
28878
|
+
if (typeof instanceWorkspace === "string" && instanceWorkspace.trim()) return instanceWorkspace;
|
|
28879
|
+
return "";
|
|
28880
|
+
}
|
|
28791
28881
|
function isCurrentRuntimePtySafelyAttributed(args) {
|
|
28792
28882
|
if (args.adapter.cliType !== "codex-cli") return false;
|
|
28793
28883
|
if (!Array.isArray(args.ptyMessages) || args.ptyMessages.length === 0) return false;
|
|
@@ -29602,6 +29692,24 @@ async function handleChatHistory(h, args) {
|
|
|
29602
29692
|
}
|
|
29603
29693
|
}
|
|
29604
29694
|
async function handleReadChat(h, args) {
|
|
29695
|
+
{
|
|
29696
|
+
const guardSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
|
|
29697
|
+
if (guardSessionId && typeof args?.workspace === "string" && args.workspace.trim()) {
|
|
29698
|
+
const verdict = evaluateReadChatNodeWorkspaceScope({
|
|
29699
|
+
targetSessionId: guardSessionId,
|
|
29700
|
+
intendedWorkspace: args.workspace,
|
|
29701
|
+
sessionWorkspace: resolveTargetSessionActualWorkspace(h, guardSessionId)
|
|
29702
|
+
});
|
|
29703
|
+
if (verdict.scoped) {
|
|
29704
|
+
LOG.info("Command", `[read_chat] node scope mismatch: session ${guardSessionId} workspace "${verdict.actual}" \u2260 requested node workspace "${verdict.intended}" \u2014 refusing cross-worktree transcript`);
|
|
29705
|
+
return {
|
|
29706
|
+
success: false,
|
|
29707
|
+
code: "read_chat_session_node_scope_mismatch",
|
|
29708
|
+
error: `Session ${guardSessionId} belongs to a different worktree (workspace "${verdict.actual}") than the requested node (workspace "${verdict.intended}"). Refusing to return a cross-worktree transcript \u2014 target the node that owns this session.`
|
|
29709
|
+
};
|
|
29710
|
+
}
|
|
29711
|
+
}
|
|
29712
|
+
}
|
|
29605
29713
|
let providerHint = args?.agentType || args?.providerType;
|
|
29606
29714
|
if (!providerHint) {
|
|
29607
29715
|
const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
|
|
@@ -29990,10 +30098,19 @@ async function handleReadChat(h, args) {
|
|
|
29990
30098
|
ptyStatusApprovalOnly: false
|
|
29991
30099
|
});
|
|
29992
30100
|
if (supportsNative && !decision.nativeSelected) {
|
|
30101
|
+
LOG.debug("Command", `[read_chat] soft pending: no live adapter and native history not safely mappable target=${String(args?.targetSessionId || "")} provider=${agentStr} reason=native_history_not_safely_available`);
|
|
29993
30102
|
return {
|
|
29994
|
-
success:
|
|
30103
|
+
success: true,
|
|
30104
|
+
pending: true,
|
|
30105
|
+
// Both signals are true here: we reached the history-only path
|
|
30106
|
+
// because no live adapter was found (`live_adapter_not_found`),
|
|
30107
|
+
// and native history is not safely mappable
|
|
30108
|
+
// (`native_history_not_safely_available`).
|
|
30109
|
+
reason: "native_history_not_safely_available",
|
|
30110
|
+
reasons: ["live_adapter_not_found", "native_history_not_safely_available"],
|
|
29995
30111
|
code: "native_history_not_safely_available",
|
|
29996
|
-
|
|
30112
|
+
messages: [],
|
|
30113
|
+
status: "idle",
|
|
29997
30114
|
providerSessionId: historyProviderSessionId,
|
|
29998
30115
|
messageSource: decision.messageSource,
|
|
29999
30116
|
transcriptProvenance: decision.messageSource
|
|
@@ -41453,6 +41570,18 @@ var DaemonCliManager = class {
|
|
|
41453
41570
|
throw new Error(`Failed to start ${provider.displayName || provider.name || cliType}: ${spawnErr?.message}`);
|
|
41454
41571
|
}
|
|
41455
41572
|
this.adapters.set(key, cliInstance.getAdapter());
|
|
41573
|
+
const launchMeshNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
41574
|
+
const launchMeshNodeFor = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
41575
|
+
if (launchMeshNodeId || launchMeshNodeFor) {
|
|
41576
|
+
try {
|
|
41577
|
+
cliInstance.getAdapter().updateRuntimeMeta?.({
|
|
41578
|
+
...launchMeshNodeId ? { meshNodeId: launchMeshNodeId } : {},
|
|
41579
|
+
...launchMeshNodeFor ? { meshNodeFor: launchMeshNodeFor } : {},
|
|
41580
|
+
...settings?.launchedByCoordinator === true ? { launchedByCoordinator: true } : {}
|
|
41581
|
+
});
|
|
41582
|
+
} catch {
|
|
41583
|
+
}
|
|
41584
|
+
}
|
|
41456
41585
|
this.startCliExitMonitor(key, cliType);
|
|
41457
41586
|
}
|
|
41458
41587
|
// ─── Session start/management ──────────────────────────────
|
|
@@ -49493,6 +49622,7 @@ function readMeshTimeoutEnvMs(name, defaultMs) {
|
|
|
49493
49622
|
}
|
|
49494
49623
|
var MESH_DIRECT_PROBE_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_TIMEOUT_MS", 25e3);
|
|
49495
49624
|
var MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS", 25e3);
|
|
49625
|
+
var MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS", 45e3);
|
|
49496
49626
|
var MESH_DIRECT_PROBE_REUSE_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_REUSE_MS", 12e3);
|
|
49497
49627
|
var MeshGitProbeCache = class {
|
|
49498
49628
|
constructor(reuseMs, now = Date.now) {
|
|
@@ -49530,12 +49660,73 @@ var MeshGitProbeCache = class {
|
|
|
49530
49660
|
}
|
|
49531
49661
|
}
|
|
49532
49662
|
};
|
|
49663
|
+
function awaitWithWarmupDeadline(work, opts) {
|
|
49664
|
+
const pollMs = Math.max(1, Math.min(opts.pollIntervalMs ?? 200, opts.connectTimeoutMs));
|
|
49665
|
+
return new Promise((resolve24, reject) => {
|
|
49666
|
+
let done = false;
|
|
49667
|
+
let poll;
|
|
49668
|
+
let responseTimer;
|
|
49669
|
+
const startedAt = Date.now();
|
|
49670
|
+
const cleanup = () => {
|
|
49671
|
+
if (poll) {
|
|
49672
|
+
clearInterval(poll);
|
|
49673
|
+
poll = void 0;
|
|
49674
|
+
}
|
|
49675
|
+
if (responseTimer) {
|
|
49676
|
+
clearTimeout(responseTimer);
|
|
49677
|
+
responseTimer = void 0;
|
|
49678
|
+
}
|
|
49679
|
+
};
|
|
49680
|
+
const settle = (fn) => {
|
|
49681
|
+
if (done) return;
|
|
49682
|
+
done = true;
|
|
49683
|
+
cleanup();
|
|
49684
|
+
fn();
|
|
49685
|
+
};
|
|
49686
|
+
const armResponse = () => {
|
|
49687
|
+
if (responseTimer || done) return;
|
|
49688
|
+
responseTimer = setTimeout(
|
|
49689
|
+
() => settle(() => reject(new Error("timeout"))),
|
|
49690
|
+
opts.responseTimeoutMs
|
|
49691
|
+
);
|
|
49692
|
+
if (typeof responseTimer.unref === "function") responseTimer.unref();
|
|
49693
|
+
};
|
|
49694
|
+
const onPoll = () => {
|
|
49695
|
+
if (done) return;
|
|
49696
|
+
if (opts.isConnected()) {
|
|
49697
|
+
if (poll) {
|
|
49698
|
+
clearInterval(poll);
|
|
49699
|
+
poll = void 0;
|
|
49700
|
+
}
|
|
49701
|
+
armResponse();
|
|
49702
|
+
return;
|
|
49703
|
+
}
|
|
49704
|
+
if (Date.now() - startedAt >= opts.connectTimeoutMs) {
|
|
49705
|
+
settle(() => reject(new Error("timeout")));
|
|
49706
|
+
}
|
|
49707
|
+
};
|
|
49708
|
+
if (opts.isConnected()) {
|
|
49709
|
+
armResponse();
|
|
49710
|
+
} else {
|
|
49711
|
+
poll = setInterval(onPoll, pollMs);
|
|
49712
|
+
if (typeof poll.unref === "function") poll.unref();
|
|
49713
|
+
}
|
|
49714
|
+
work.then(
|
|
49715
|
+
(val) => settle(() => resolve24(val)),
|
|
49716
|
+
(err) => settle(() => reject(err))
|
|
49717
|
+
);
|
|
49718
|
+
});
|
|
49719
|
+
}
|
|
49533
49720
|
async function probeRemoteMeshGitStatus(args) {
|
|
49534
49721
|
if (!args.dispatchMeshCommand) return null;
|
|
49535
|
-
const
|
|
49536
|
-
|
|
49537
|
-
|
|
49538
|
-
|
|
49722
|
+
const dispatch = args.dispatchMeshCommand(args.daemonId, "git_status", { workspace: args.workspace, refreshUpstream: true });
|
|
49723
|
+
const getConnection = args.getConnection;
|
|
49724
|
+
const isConnected = getConnection ? () => readMeshConnectionState(getConnection(args.daemonId)) === "connected" : () => true;
|
|
49725
|
+
const remoteResult = await awaitWithWarmupDeadline(dispatch, {
|
|
49726
|
+
isConnected,
|
|
49727
|
+
connectTimeoutMs: args.connectTimeoutMs,
|
|
49728
|
+
responseTimeoutMs: args.responseTimeoutMs
|
|
49729
|
+
});
|
|
49539
49730
|
const remoteGit = remoteResult?.status ?? remoteResult?.git ?? remoteResult;
|
|
49540
49731
|
if (!remoteGit || typeof remoteGit !== "object" || typeof remoteGit.isGitRepo !== "boolean") return null;
|
|
49541
49732
|
const reporterPlatform = readStringValue(remoteResult?.reporterPlatform);
|
|
@@ -49570,7 +49761,9 @@ async function probeRemoteMeshGitStatusWithRetry(args) {
|
|
|
49570
49761
|
dispatchMeshCommand: args.dispatchMeshCommand,
|
|
49571
49762
|
daemonId: args.daemonId,
|
|
49572
49763
|
workspace: args.workspace,
|
|
49573
|
-
|
|
49764
|
+
responseTimeoutMs: attempt === 0 ? args.timeoutMs : args.retryTimeoutMs ?? args.timeoutMs,
|
|
49765
|
+
connectTimeoutMs: args.connectTimeoutMs ?? MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS,
|
|
49766
|
+
getConnection: args.getConnection
|
|
49574
49767
|
});
|
|
49575
49768
|
if (remoteGit) return remoteGit;
|
|
49576
49769
|
} catch {
|
|
@@ -49653,6 +49846,7 @@ async function hydrateInlineMeshDirectTruth(args) {
|
|
|
49653
49846
|
workspace,
|
|
49654
49847
|
timeoutMs: MESH_DIRECT_PROBE_TIMEOUT_MS,
|
|
49655
49848
|
retryTimeoutMs: MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS,
|
|
49849
|
+
connectTimeoutMs: MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS,
|
|
49656
49850
|
getConnection: args.getMeshPeerConnectionStatus
|
|
49657
49851
|
});
|
|
49658
49852
|
const remoteGit = args.probeCache ? await args.probeCache.probe(daemonId, workspace, runProbe) : await runProbe();
|
|
@@ -49704,13 +49898,13 @@ function liveSessionRecordMatchesMeshNode(record, meshId, nodeId, nodeWorkspace
|
|
|
49704
49898
|
if (!recordNodeId || recordNodeId !== nodeId) return false;
|
|
49705
49899
|
if (nodeIsMissingLocalWorktree) return false;
|
|
49706
49900
|
const recordWorkspace = readStringValue(record?.workspace);
|
|
49707
|
-
if (nodeWorkspace && recordWorkspace && recordWorkspace
|
|
49901
|
+
if (nodeWorkspace && recordWorkspace && !meshWorkspacesEquivalent(recordWorkspace, nodeWorkspace)) return false;
|
|
49708
49902
|
const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
|
|
49709
49903
|
return !recordMeshId || recordMeshId === meshId;
|
|
49710
49904
|
}
|
|
49711
49905
|
function liveSessionRecordMatchesMeshWorkspace(record, meshId, workspace) {
|
|
49712
49906
|
const recordWorkspace = readStringValue(record?.workspace);
|
|
49713
|
-
if (!recordWorkspace || !workspace || recordWorkspace
|
|
49907
|
+
if (!recordWorkspace || !workspace || !meshWorkspacesEquivalent(recordWorkspace, workspace)) return false;
|
|
49714
49908
|
const recordMeshId = readStringValue(record?.meta?.meshNodeFor);
|
|
49715
49909
|
if (recordMeshId) return recordMeshId === meshId;
|
|
49716
49910
|
return record?.meta?.launchedByCoordinator === true || !!readStringValue(record?.meta?.meshNodeId);
|