@adhdev/daemon-core 0.9.82-rc.31 → 0.9.82-rc.310
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/daemon-lifecycle.d.ts +8 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +23 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +38 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +14 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +210 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +9 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +9 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/index.d.ts +51 -12
- package/dist/index.js +38320 -16024
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38009 -15817
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +108 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +138 -0
- package/dist/mesh/mesh-events-pending.d.ts +45 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +47 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-init.d.ts +86 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +116 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +7 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +377 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
- package/dist/mesh/mesh-work-queue.d.ts +181 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +215 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
- package/dist/providers/acp-provider-instance.d.ts +2 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +61 -3
- package/dist/providers/contracts.d.ts +130 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +13 -0
- package/dist/providers/provider-instance.d.ts +13 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +59 -0
- package/dist/providers/spec/cli-adapter.d.ts +186 -0
- package/dist/providers/spec/evaluator.d.ts +23 -0
- package/dist/providers/spec/fsm-driver.d.ts +278 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +72 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +128 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +144 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/repo-mesh-types.d.ts +203 -13
- package/dist/sessions/registry.d.ts +6 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +42 -1
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +55 -12
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1215 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +674 -1139
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +68 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +45 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands.ts +1813 -60
- package/src/commands/cli-manager.ts +286 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +5911 -595
- package/src/commands/upgrade-helper.ts +29 -0
- package/src/config/chat-history.ts +474 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +303 -3
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +42 -12
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +302 -18
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +131 -11
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +234 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +469 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +1786 -0
- package/src/mesh/mesh-events-pending.ts +508 -0
- package/src/mesh/mesh-events-stale.ts +290 -0
- package/src/mesh/mesh-events-utils.ts +254 -0
- package/src/mesh/mesh-events.ts +30 -1035
- package/src/mesh/mesh-fast-forward.ts +839 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-init.ts +260 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +589 -102
- package/src/mesh/mesh-missions.ts +248 -0
- package/src/mesh/mesh-reconcile-loop.ts +466 -0
- package/src/mesh/mesh-refine-batch.ts +197 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +1513 -0
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
- package/src/mesh/mesh-work-queue.ts +604 -145
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +403 -0
- package/src/mesh/worktree-bootstrap-config.ts +326 -0
- package/src/providers/acp-provider-instance.ts +15 -1
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +953 -87
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/extension-provider-instance.ts +5 -1
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +25 -6
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +584 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +155 -0
- package/src/providers/spec/cli-adapter.ts +838 -0
- package/src/providers/spec/evaluator.ts +345 -0
- package/src/providers/spec/fsm-driver.ts +875 -0
- package/src/providers/spec/fsm-evaluator.ts +255 -0
- package/src/providers/spec/fsm-loader.ts +112 -0
- package/src/providers/spec/fsm-types.ts +222 -0
- package/src/providers/spec/native-history-executor.ts +943 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +189 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +257 -13
- package/src/sessions/registry.ts +6 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +26 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
package/src/launch.ts
CHANGED
|
@@ -16,7 +16,16 @@
|
|
|
16
16
|
* adhdev launch --workspace /path — Open specific workspace
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { exec, spawn, spawnSync } from 'child_process';
|
|
20
|
+
|
|
21
|
+
async function execQuiet(command: string, options: any = {}): Promise<string> {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
exec(command, options, (error, stdout) => {
|
|
24
|
+
if (error) return resolve('');
|
|
25
|
+
resolve(stdout.toString());
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
20
29
|
import * as net from 'net';
|
|
21
30
|
import * as os from 'os';
|
|
22
31
|
import * as path from 'path';
|
|
@@ -76,11 +85,11 @@ function getIdePathCandidates(ideId: string): string[] {
|
|
|
76
85
|
return getProviderLoader().getIdePathCandidates(ideId);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
function getMacAppProcessPids(ideId: string): number[] {
|
|
88
|
+
async function getMacAppProcessPids(ideId: string): Promise<number[]> {
|
|
80
89
|
const appPaths = getIdePathCandidates(ideId);
|
|
81
90
|
if (appPaths.length === 0) return [];
|
|
82
91
|
try {
|
|
83
|
-
const output =
|
|
92
|
+
const output = await execQuiet('ps axww -o pid=,args=', {
|
|
84
93
|
encoding: 'utf-8',
|
|
85
94
|
timeout: 3000,
|
|
86
95
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -91,8 +100,8 @@ function getMacAppProcessPids(ideId: string): number[] {
|
|
|
91
100
|
}
|
|
92
101
|
}
|
|
93
102
|
|
|
94
|
-
function killMacAppPathProcesses(ideId: string, signal: NodeJS.Signals): boolean {
|
|
95
|
-
const pids = getMacAppProcessPids(ideId);
|
|
103
|
+
async function killMacAppPathProcesses(ideId: string, signal: NodeJS.Signals): Promise<boolean> {
|
|
104
|
+
const pids = (await getMacAppProcessPids(ideId));
|
|
96
105
|
let signalled = false;
|
|
97
106
|
for (const pid of pids) {
|
|
98
107
|
try {
|
|
@@ -163,49 +172,49 @@ export async function killIdeProcess(ideId: string): Promise<boolean> {
|
|
|
163
172
|
if (plat === 'darwin' && appName) {
|
|
164
173
|
// macOS: graceful quit via osascript
|
|
165
174
|
try {
|
|
166
|
-
|
|
175
|
+
await execQuiet(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
|
|
167
176
|
timeout: 5000,
|
|
168
177
|
});
|
|
169
178
|
} catch {
|
|
170
|
-
try {
|
|
179
|
+
try { await execQuiet(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5000 }); } catch { }
|
|
171
180
|
}
|
|
172
|
-
killMacAppPathProcesses(ideId, 'SIGTERM');
|
|
181
|
+
await killMacAppPathProcesses(ideId, 'SIGTERM');
|
|
173
182
|
} else if (plat === 'win32' && winProcesses) {
|
|
174
183
|
// Windows: taskkill for each process name
|
|
175
184
|
for (const proc of winProcesses) {
|
|
176
185
|
try {
|
|
177
|
-
|
|
186
|
+
await execQuiet(`taskkill /IM "${proc}" /F 2>nul`, { timeout: 5000 });
|
|
178
187
|
} catch { }
|
|
179
188
|
}
|
|
180
189
|
// Process name may differ, so also try via WMIC
|
|
181
190
|
try {
|
|
182
191
|
const exeName = winProcesses[0].replace('.exe', '');
|
|
183
|
-
|
|
192
|
+
await execQuiet(`powershell -Command "Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue | Stop-Process -Force"`, {
|
|
184
193
|
timeout: 10000,
|
|
185
194
|
});
|
|
186
195
|
} catch { }
|
|
187
196
|
} else {
|
|
188
|
-
try {
|
|
197
|
+
try { await execQuiet(`pkill -f "${ideId}" 2>/dev/null`); } catch { }
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
// Wait for process kill (max 15 seconds)
|
|
192
201
|
for (let i = 0; i < 30; i++) {
|
|
193
202
|
await new Promise(r => setTimeout(r, 500));
|
|
194
|
-
if (!isIdeRunning(ideId)) return true;
|
|
203
|
+
if (!(await isIdeRunning(ideId))) return true;
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
// Force terminate retry
|
|
198
207
|
if (plat === 'darwin' && appName) {
|
|
199
|
-
try {
|
|
200
|
-
killMacAppPathProcesses(ideId, 'SIGKILL');
|
|
208
|
+
try { await execQuiet(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5000 }); } catch { }
|
|
209
|
+
await killMacAppPathProcesses(ideId, 'SIGKILL');
|
|
201
210
|
} else if (plat === 'win32' && winProcesses) {
|
|
202
211
|
for (const proc of winProcesses) {
|
|
203
|
-
try {
|
|
212
|
+
try { await execQuiet(`taskkill /IM "${proc}" /F 2>nul`); } catch { }
|
|
204
213
|
}
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
await new Promise(r => setTimeout(r, 2000));
|
|
208
|
-
return !isIdeRunning(ideId);
|
|
217
|
+
return !(await isIdeRunning(ideId));
|
|
209
218
|
|
|
210
219
|
} catch {
|
|
211
220
|
return false;
|
|
@@ -213,15 +222,15 @@ export async function killIdeProcess(ideId: string): Promise<boolean> {
|
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
/** Check if IDE process is running */
|
|
216
|
-
export function isIdeRunning(ideId: string): boolean {
|
|
225
|
+
export async function isIdeRunning(ideId: string): Promise<boolean> {
|
|
217
226
|
const plat = os.platform();
|
|
218
227
|
|
|
219
228
|
try {
|
|
220
229
|
if (plat === 'darwin') {
|
|
221
230
|
const appName = getMacAppIdentifiers()[ideId];
|
|
222
|
-
if (!appName) return getMacAppProcessPids(ideId).length > 0;
|
|
231
|
+
if (!appName) return (await getMacAppProcessPids(ideId)).length > 0;
|
|
223
232
|
try {
|
|
224
|
-
const result =
|
|
233
|
+
const result = await execQuiet(`pgrep -x "${appName}" 2>/dev/null`, {
|
|
225
234
|
encoding: 'utf-8',
|
|
226
235
|
timeout: 3000,
|
|
227
236
|
});
|
|
@@ -229,7 +238,7 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
229
238
|
} catch { }
|
|
230
239
|
|
|
231
240
|
try {
|
|
232
|
-
const result =
|
|
241
|
+
const result = await execQuiet(
|
|
233
242
|
`osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
|
|
234
243
|
{
|
|
235
244
|
encoding: 'utf-8',
|
|
@@ -240,21 +249,21 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
240
249
|
if (Number.parseInt(result.trim() || '0', 10) > 0) return true;
|
|
241
250
|
} catch { }
|
|
242
251
|
|
|
243
|
-
return getMacAppProcessPids(ideId).length > 0;
|
|
252
|
+
return (await getMacAppProcessPids(ideId)).length > 0;
|
|
244
253
|
} else if (plat === 'win32') {
|
|
245
254
|
const winProcesses = getWinProcessNames()[ideId];
|
|
246
255
|
if (!winProcesses) return false;
|
|
247
256
|
// Check each process name
|
|
248
257
|
for (const proc of winProcesses) {
|
|
249
258
|
try {
|
|
250
|
-
const result =
|
|
259
|
+
const result = await execQuiet(`tasklist /FI "IMAGENAME eq ${proc}" /NH 2>nul`, { encoding: 'utf-8' });
|
|
251
260
|
if (result.includes(proc)) return true;
|
|
252
261
|
} catch { }
|
|
253
262
|
}
|
|
254
263
|
// Also check via PowerShell (when tasklist cannot find)
|
|
255
264
|
try {
|
|
256
265
|
const exeName = winProcesses[0].replace('.exe', '');
|
|
257
|
-
const result =
|
|
266
|
+
const result = await execQuiet(
|
|
258
267
|
`powershell -Command "(Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue).Count"`,
|
|
259
268
|
{ encoding: 'utf-8', timeout: 5000 }
|
|
260
269
|
);
|
|
@@ -262,7 +271,7 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
262
271
|
} catch { }
|
|
263
272
|
return false;
|
|
264
273
|
} else {
|
|
265
|
-
const result =
|
|
274
|
+
const result = await execQuiet(`pgrep -f "${ideId}" 2>/dev/null`, { encoding: 'utf-8' });
|
|
266
275
|
return result.trim().length > 0;
|
|
267
276
|
}
|
|
268
277
|
} catch {
|
|
@@ -271,14 +280,14 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
271
280
|
}
|
|
272
281
|
|
|
273
282
|
/** Detect currently open workspace path */
|
|
274
|
-
function detectCurrentWorkspace(ideId: string): string | undefined {
|
|
283
|
+
async function detectCurrentWorkspace(ideId: string): Promise<string | undefined> {
|
|
275
284
|
const plat = os.platform();
|
|
276
285
|
|
|
277
286
|
if (plat === 'darwin') {
|
|
278
287
|
try {
|
|
279
288
|
const appName = getMacAppIdentifiers()[ideId];
|
|
280
289
|
if (!appName) return undefined;
|
|
281
|
-
const result =
|
|
290
|
+
const result = await execQuiet(
|
|
282
291
|
`lsof -c "${appName}" 2>/dev/null | grep cwd | head -1 | awk '{print $NF}'`,
|
|
283
292
|
{ encoding: 'utf-8', timeout: 3000 }
|
|
284
293
|
);
|
|
@@ -392,8 +401,8 @@ export async function launchWithCdp(options: LaunchOptions = {}): Promise<Launch
|
|
|
392
401
|
}
|
|
393
402
|
|
|
394
403
|
// 4. Check if IDE is currently running
|
|
395
|
-
const alreadyRunning = isIdeRunning(targetIde.id);
|
|
396
|
-
const workspace = options.workspace || (alreadyRunning ? detectCurrentWorkspace(targetIde.id) : undefined);
|
|
404
|
+
const alreadyRunning = await isIdeRunning(targetIde.id);
|
|
405
|
+
const workspace = options.workspace || (alreadyRunning ? await detectCurrentWorkspace(targetIde.id) : undefined);
|
|
397
406
|
|
|
398
407
|
// 5. If IDE is running, terminate it
|
|
399
408
|
if (alreadyRunning) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
export class AsyncBatchWriter {
|
|
4
|
+
// Maps filePath -> string buffer
|
|
5
|
+
private static buffers: Map<string, string[]> = new Map();
|
|
6
|
+
private static writePromises: Map<string, Promise<void>> = new Map();
|
|
7
|
+
private static flushTimer: NodeJS.Timeout | null = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Queues data to be written to a file asynchronously in a batch.
|
|
11
|
+
*/
|
|
12
|
+
public static write(filePath: string, data: string) {
|
|
13
|
+
let buf = this.buffers.get(filePath);
|
|
14
|
+
if (!buf) {
|
|
15
|
+
buf = [];
|
|
16
|
+
this.buffers.set(filePath, buf);
|
|
17
|
+
}
|
|
18
|
+
buf.push(data);
|
|
19
|
+
|
|
20
|
+
if (!this.flushTimer) {
|
|
21
|
+
this.flushTimer = setTimeout(() => {
|
|
22
|
+
this.flushTimer = null;
|
|
23
|
+
this.flushAll();
|
|
24
|
+
}, 50);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static async flushAll() {
|
|
29
|
+
const entries = Array.from(this.buffers.entries());
|
|
30
|
+
this.buffers.clear();
|
|
31
|
+
|
|
32
|
+
for (const [filePath, buffer] of entries) {
|
|
33
|
+
const dataToWrite = buffer.join('');
|
|
34
|
+
|
|
35
|
+
const doWrite = async () => {
|
|
36
|
+
try {
|
|
37
|
+
const prevPromise = this.writePromises.get(filePath);
|
|
38
|
+
if (prevPromise) await prevPromise;
|
|
39
|
+
await fs.promises.appendFile(filePath, dataToWrite, { encoding: 'utf-8', mode: 0o600 });
|
|
40
|
+
} catch {
|
|
41
|
+
// Logging must never create secondary failures or late console noise.
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const writePromise = doWrite();
|
|
46
|
+
this.writePromises.set(filePath, writePromise);
|
|
47
|
+
|
|
48
|
+
writePromise.finally(() => {
|
|
49
|
+
if (this.writePromises.get(filePath) === writePromise) {
|
|
50
|
+
this.writePromises.delete(filePath);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/logging/logger.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import * as os from 'os';
|
|
23
|
+
import { AsyncBatchWriter } from './async-batch-writer.js';
|
|
23
24
|
|
|
24
25
|
// ─── Log Level ──────────────────────────────
|
|
25
26
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
@@ -123,7 +124,7 @@ function writeToFile(line: string): void {
|
|
|
123
124
|
checkDateRotation();
|
|
124
125
|
rotateSizeIfNeeded();
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
+
AsyncBatchWriter.write(currentLogFile, line + '\n');
|
|
127
128
|
} catch { }
|
|
128
129
|
}
|
|
129
130
|
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mesh contract v2 — first-class identity, scopes, and protocol version.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the implicit conventions that grew across mesh-events.ts,
|
|
5
|
+
* mesh-work-queue.ts, mesh-ledger.ts, and mesh-tools.ts where the same
|
|
6
|
+
* concept (a coordinator, a session, a task status) appeared in different
|
|
7
|
+
* shapes per file. The audit found:
|
|
8
|
+
*
|
|
9
|
+
* - PendingMeshCoordinatorEvent had no targetCoordinatorDaemonId. All
|
|
10
|
+
* events were broadcast to every coordinator that drained them. Two
|
|
11
|
+
* coordinators sharing a mesh would each receive every event,
|
|
12
|
+
* leading to duplicate completion handling and missed targeting.
|
|
13
|
+
* - drainPendingMeshCoordinatorEvents accepted only meshId. The caller's
|
|
14
|
+
* coordinator identity was never available, so per-coordinator
|
|
15
|
+
* routing was impossible by construction.
|
|
16
|
+
* - Session identifier keys diverged across stores: targetSessionId /
|
|
17
|
+
* assignedSessionId / instanceId / runtimeSessionId / providerSessionId.
|
|
18
|
+
* resolveEventSessionId() tried four fallbacks per call.
|
|
19
|
+
* - No protocol version on the JSONL ledger or MeshRuntimeStore. Schema
|
|
20
|
+
* evolutions had no guard rail.
|
|
21
|
+
* - mesh_reconcile_ledger existed as a routine recovery tool, not as
|
|
22
|
+
* an incident-response escape hatch. That itself signals the routing
|
|
23
|
+
* layer cannot be trusted.
|
|
24
|
+
*
|
|
25
|
+
* This module introduces the types; the actual wiring lands in B2 (data
|
|
26
|
+
* model + 3-way transactional store), B3 (MCP layer enforcement), and B4
|
|
27
|
+
* (frontend consumption). B1 is intentionally non-breaking — it adds the
|
|
28
|
+
* types and leaves runtime behaviour unchanged.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** Provider type identifier (e.g. 'claude-cli', 'codex-cli', 'roo-code').
|
|
32
|
+
* Free-form string; no shared enum exists in daemon-core yet. */
|
|
33
|
+
type ProviderType = string;
|
|
34
|
+
|
|
35
|
+
// ─── Protocol version ────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export const MESH_PROTOCOL_VERSION_V1 = '1.0' as const;
|
|
38
|
+
export const MESH_PROTOCOL_VERSION_V2 = '2.0' as const;
|
|
39
|
+
|
|
40
|
+
export type MeshProtocolVersion =
|
|
41
|
+
| typeof MESH_PROTOCOL_VERSION_V1
|
|
42
|
+
| typeof MESH_PROTOCOL_VERSION_V2;
|
|
43
|
+
|
|
44
|
+
export const SUPPORTED_MESH_PROTOCOL_VERSIONS: readonly MeshProtocolVersion[] = [
|
|
45
|
+
MESH_PROTOCOL_VERSION_V1,
|
|
46
|
+
MESH_PROTOCOL_VERSION_V2,
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export function isSupportedMeshProtocolVersion(value: unknown): value is MeshProtocolVersion {
|
|
50
|
+
return typeof value === 'string'
|
|
51
|
+
&& (SUPPORTED_MESH_PROTOCOL_VERSIONS as readonly string[]).includes(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Coordinator identity ────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Identity of a mesh coordinator. Required (non-optional) on every dispatch
|
|
58
|
+
* and drain operation in v2 — opaque/missing identity is the audit's
|
|
59
|
+
* smoking gun for routing failures and is structurally banned by the v2
|
|
60
|
+
* types.
|
|
61
|
+
*
|
|
62
|
+
* - daemonId: the machineId of the daemon hosting the coordinator. Stable
|
|
63
|
+
* across coordinator restarts on the same machine.
|
|
64
|
+
* - coordinatorRunId: a UUID generated when the coordinator process starts.
|
|
65
|
+
* Stable for the coordinator's lifetime, fresh on restart. Required so
|
|
66
|
+
* two coordinators on the same daemon (one CLI, one MCP) can be told
|
|
67
|
+
* apart without conflating their drains.
|
|
68
|
+
* - sessionId: optional CLI coordinator session identifier (instanceId).
|
|
69
|
+
* Present when the coordinator is itself a CLI session, absent for
|
|
70
|
+
* pure MCP coordinators.
|
|
71
|
+
*/
|
|
72
|
+
export interface CoordinatorIdentity {
|
|
73
|
+
readonly daemonId: string;
|
|
74
|
+
readonly coordinatorRunId: string;
|
|
75
|
+
readonly sessionId?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function coordinatorIdentityEquals(a: CoordinatorIdentity, b: CoordinatorIdentity): boolean {
|
|
79
|
+
return a.daemonId === b.daemonId
|
|
80
|
+
&& a.coordinatorRunId === b.coordinatorRunId
|
|
81
|
+
&& (a.sessionId ?? '') === (b.sessionId ?? '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function coordinatorIdentityKey(identity: CoordinatorIdentity): string {
|
|
85
|
+
// Stable string form for map keys and ledger payloads. Avoids the
|
|
86
|
+
// {a:b, c:d} JSON.stringify ordering trap by enforcing field order.
|
|
87
|
+
return `${identity.daemonId}|${identity.coordinatorRunId}|${identity.sessionId ?? ''}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Session handle ──────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Unified mesh session identifier. v1 referred to "the session" with five
|
|
94
|
+
* different field names depending on which store you were reading; v2
|
|
95
|
+
* collapses them into one explicit handle that carries every disambiguator
|
|
96
|
+
* a consumer might need.
|
|
97
|
+
*
|
|
98
|
+
* - nodeId: the mesh node this session belongs to (FK into mesh node table).
|
|
99
|
+
* - sessionId: provider-instance identifier (the runtime session id).
|
|
100
|
+
* - providerType: which provider category/type the session runs (e.g.
|
|
101
|
+
* 'claude-cli', 'codex-cli', 'roo-code').
|
|
102
|
+
* - coordinatorDaemonId: which coordinator dispatched the work that
|
|
103
|
+
* spawned this session. Used for completion event routing.
|
|
104
|
+
* - assignedAt: ms epoch when the session was bound to its current task.
|
|
105
|
+
*/
|
|
106
|
+
export interface MeshSessionHandle {
|
|
107
|
+
readonly nodeId: string;
|
|
108
|
+
readonly sessionId: string;
|
|
109
|
+
readonly providerType: ProviderType;
|
|
110
|
+
readonly coordinatorDaemonId: string;
|
|
111
|
+
readonly assignedAt: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function meshSessionHandleKey(handle: MeshSessionHandle): string {
|
|
115
|
+
return `${handle.nodeId}|${handle.sessionId}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Task status (canonical enum) ────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Single canonical task status enum. v1 had at least three overlapping
|
|
122
|
+
* sets (queue / ledger / direct-dispatch). v2 consumers import from here.
|
|
123
|
+
*/
|
|
124
|
+
export const MESH_TASK_STATUSES = [
|
|
125
|
+
'pending',
|
|
126
|
+
'assigned',
|
|
127
|
+
'in_progress',
|
|
128
|
+
'completed',
|
|
129
|
+
'failed',
|
|
130
|
+
'cancelled',
|
|
131
|
+
] as const;
|
|
132
|
+
export type MeshTaskStatus = typeof MESH_TASK_STATUSES[number];
|
|
133
|
+
|
|
134
|
+
export function isMeshTaskStatus(value: unknown): value is MeshTaskStatus {
|
|
135
|
+
return typeof value === 'string'
|
|
136
|
+
&& (MESH_TASK_STATUSES as readonly string[]).includes(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Event scope ─────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Explicit scope for pending coordinator events. v1 had no scope: every
|
|
143
|
+
* event was broadcast to every drainer. v2 forces producers to declare
|
|
144
|
+
* intent.
|
|
145
|
+
*
|
|
146
|
+
* - 'unicast': delivered to exactly one coordinator (intendedFor). Other
|
|
147
|
+
* coordinators' drains skip it.
|
|
148
|
+
* - 'broadcast': delivered to every coordinator on the mesh. Used for
|
|
149
|
+
* system-wide signals (e.g. mesh-wide policy changes). The v1 default
|
|
150
|
+
* becomes explicit here so audit tools can flag unintended broadcasts.
|
|
151
|
+
* - 'system': delivered to the daemon-level handler, not coordinators.
|
|
152
|
+
* Reserved for infrastructure events that no coordinator should see
|
|
153
|
+
* (e.g. ledger reconciliation outcomes).
|
|
154
|
+
*/
|
|
155
|
+
export const MESH_EVENT_SCOPES = ['unicast', 'broadcast', 'system'] as const;
|
|
156
|
+
export type MeshEventScope = typeof MESH_EVENT_SCOPES[number];
|
|
157
|
+
|
|
158
|
+
export function isMeshEventScope(value: unknown): value is MeshEventScope {
|
|
159
|
+
return typeof value === 'string'
|
|
160
|
+
&& (MESH_EVENT_SCOPES as readonly string[]).includes(value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Pending coordinator event v2 ────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* v2 shape of a pending coordinator event. Strict supersets of the v1
|
|
167
|
+
* shape — every v1 field is preserved so existing readers do not break;
|
|
168
|
+
* v2 fields (scope, dispatchedBy, intendedFor, protocolVersion) are
|
|
169
|
+
* additive and consulted by v2-aware drainers only.
|
|
170
|
+
*
|
|
171
|
+
* Mixed v1/v2 events coexist during the rollout window. B2 fully cuts
|
|
172
|
+
* over once every coordinator drain is v2-aware.
|
|
173
|
+
*/
|
|
174
|
+
export interface PendingMeshCoordinatorEventV2 {
|
|
175
|
+
// v1 fields (preserved exactly)
|
|
176
|
+
readonly event: string;
|
|
177
|
+
readonly meshId: string;
|
|
178
|
+
readonly nodeLabel: string;
|
|
179
|
+
readonly nodeId?: string;
|
|
180
|
+
readonly workspace?: string;
|
|
181
|
+
readonly metadataEvent: Record<string, unknown>;
|
|
182
|
+
readonly coordinatorMessage?: string;
|
|
183
|
+
readonly queuedAt: number;
|
|
184
|
+
|
|
185
|
+
// v2 additions
|
|
186
|
+
readonly protocolVersion: MeshProtocolVersion;
|
|
187
|
+
readonly scope: MeshEventScope;
|
|
188
|
+
readonly dispatchedBy: CoordinatorIdentity;
|
|
189
|
+
/**
|
|
190
|
+
* Required when scope === 'unicast', forbidden otherwise. Drainers MUST
|
|
191
|
+
* skip unicast events whose intendedFor does not equal their own identity.
|
|
192
|
+
*/
|
|
193
|
+
readonly intendedFor?: CoordinatorIdentity;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Ledger entry v2 ─────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* v2 ledger entry additions. The originatingCoordinator field is the
|
|
200
|
+
* source of truth that lets completion events route back to the
|
|
201
|
+
* coordinator that dispatched the task. v1's "worker settings carried it
|
|
202
|
+
* if it happened to be set" approach is replaced.
|
|
203
|
+
*/
|
|
204
|
+
export interface MeshLedgerOriginatingCoordinatorV2 {
|
|
205
|
+
readonly originatingCoordinator: CoordinatorIdentity;
|
|
206
|
+
readonly protocolVersion: MeshProtocolVersion;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── Validation primitives ───────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
export class MeshContractViolationError extends Error {
|
|
212
|
+
readonly violationPath: string;
|
|
213
|
+
readonly protocolVersion: MeshProtocolVersion;
|
|
214
|
+
constructor(
|
|
215
|
+
protocolVersion: MeshProtocolVersion,
|
|
216
|
+
violationPath: string,
|
|
217
|
+
detail: string,
|
|
218
|
+
) {
|
|
219
|
+
super(`mesh contract ${protocolVersion} violation at ${violationPath}: ${detail}`);
|
|
220
|
+
this.name = 'MeshContractViolationError';
|
|
221
|
+
this.violationPath = violationPath;
|
|
222
|
+
this.protocolVersion = protocolVersion;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
227
|
+
return typeof value === 'string' && value.length > 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function assertCoordinatorIdentity(raw: unknown, path: string): CoordinatorIdentity {
|
|
231
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
232
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, path, 'must be an object');
|
|
233
|
+
}
|
|
234
|
+
const obj = raw as Record<string, unknown>;
|
|
235
|
+
if (!isNonEmptyString(obj.daemonId)) {
|
|
236
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.daemonId`, 'must be a non-empty string');
|
|
237
|
+
}
|
|
238
|
+
if (!isNonEmptyString(obj.coordinatorRunId)) {
|
|
239
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.coordinatorRunId`, 'must be a non-empty string');
|
|
240
|
+
}
|
|
241
|
+
const sessionId = obj.sessionId;
|
|
242
|
+
if (sessionId !== undefined && !isNonEmptyString(sessionId)) {
|
|
243
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.sessionId`, 'must be a non-empty string when provided');
|
|
244
|
+
}
|
|
245
|
+
return sessionId !== undefined
|
|
246
|
+
? { daemonId: obj.daemonId, coordinatorRunId: obj.coordinatorRunId, sessionId }
|
|
247
|
+
: { daemonId: obj.daemonId, coordinatorRunId: obj.coordinatorRunId };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function assertPendingMeshCoordinatorEventV2(raw: unknown, path = '$'): PendingMeshCoordinatorEventV2 {
|
|
251
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
252
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, path, 'must be an object');
|
|
253
|
+
}
|
|
254
|
+
const obj = raw as Record<string, unknown>;
|
|
255
|
+
if (!isSupportedMeshProtocolVersion(obj.protocolVersion)) {
|
|
256
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.protocolVersion`, `must be one of ${SUPPORTED_MESH_PROTOCOL_VERSIONS.join(', ')}`);
|
|
257
|
+
}
|
|
258
|
+
if (!isMeshEventScope(obj.scope)) {
|
|
259
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.scope`, `must be one of ${MESH_EVENT_SCOPES.join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
if (!isNonEmptyString(obj.event)) {
|
|
262
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.event`, 'must be a non-empty string');
|
|
263
|
+
}
|
|
264
|
+
if (!isNonEmptyString(obj.meshId)) {
|
|
265
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.meshId`, 'must be a non-empty string');
|
|
266
|
+
}
|
|
267
|
+
const dispatchedBy = assertCoordinatorIdentity(obj.dispatchedBy, `${path}.dispatchedBy`);
|
|
268
|
+
|
|
269
|
+
if (obj.scope === 'unicast') {
|
|
270
|
+
if (!obj.intendedFor) {
|
|
271
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.intendedFor`, 'unicast scope requires intendedFor');
|
|
272
|
+
}
|
|
273
|
+
} else if (obj.intendedFor !== undefined) {
|
|
274
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.intendedFor`, 'only unicast scope may set intendedFor');
|
|
275
|
+
}
|
|
276
|
+
const intendedFor = obj.intendedFor
|
|
277
|
+
? assertCoordinatorIdentity(obj.intendedFor, `${path}.intendedFor`)
|
|
278
|
+
: undefined;
|
|
279
|
+
|
|
280
|
+
const metadata = obj.metadataEvent && typeof obj.metadataEvent === 'object' && !Array.isArray(obj.metadataEvent)
|
|
281
|
+
? (obj.metadataEvent as Record<string, unknown>)
|
|
282
|
+
: null;
|
|
283
|
+
if (!metadata) {
|
|
284
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.metadataEvent`, 'must be an object');
|
|
285
|
+
}
|
|
286
|
+
const queuedAt = typeof obj.queuedAt === 'number' && Number.isFinite(obj.queuedAt) ? obj.queuedAt : null;
|
|
287
|
+
if (queuedAt === null) {
|
|
288
|
+
throw new MeshContractViolationError(MESH_PROTOCOL_VERSION_V2, `${path}.queuedAt`, 'must be a finite number');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
event: obj.event,
|
|
293
|
+
meshId: obj.meshId,
|
|
294
|
+
nodeLabel: isNonEmptyString(obj.nodeLabel) ? obj.nodeLabel : '',
|
|
295
|
+
nodeId: typeof obj.nodeId === 'string' ? obj.nodeId : undefined,
|
|
296
|
+
workspace: typeof obj.workspace === 'string' ? obj.workspace : undefined,
|
|
297
|
+
metadataEvent: metadata,
|
|
298
|
+
coordinatorMessage: typeof obj.coordinatorMessage === 'string' ? obj.coordinatorMessage : undefined,
|
|
299
|
+
queuedAt,
|
|
300
|
+
protocolVersion: obj.protocolVersion,
|
|
301
|
+
scope: obj.scope,
|
|
302
|
+
dispatchedBy,
|
|
303
|
+
...(intendedFor ? { intendedFor } : {}),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─── Routing helper ──────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Decide whether a v2 pending event should be delivered to the given drainer.
|
|
311
|
+
* Centralised so every drain implementation uses the same rule.
|
|
312
|
+
*
|
|
313
|
+
* - 'broadcast': always delivered.
|
|
314
|
+
* - 'system': never delivered to coordinators (system handler only).
|
|
315
|
+
* - 'unicast': delivered iff intendedFor matches drainer identity.
|
|
316
|
+
*
|
|
317
|
+
* v1 events (no scope / no protocolVersion) are treated as broadcast for
|
|
318
|
+
* backward compatibility during rollout; B3 tightens this so v1 events
|
|
319
|
+
* are quarantined to a dedicated drain endpoint instead.
|
|
320
|
+
*/
|
|
321
|
+
export function shouldDeliverPendingEventToCoordinator(
|
|
322
|
+
event: PendingMeshCoordinatorEventV2,
|
|
323
|
+
drainer: CoordinatorIdentity,
|
|
324
|
+
): boolean {
|
|
325
|
+
if (event.scope === 'system') return false;
|
|
326
|
+
if (event.scope === 'broadcast') return true;
|
|
327
|
+
if (!event.intendedFor) return false;
|
|
328
|
+
return coordinatorIdentityEquals(event.intendedFor, drainer);
|
|
329
|
+
}
|