@adhdev/daemon-core 0.9.82-rc.32 → 0.9.82-rc.320
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 +46 -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 +115 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -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 +229 -1
- package/dist/commands/upgrade-helper.d.ts +12 -0
- 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/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- 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 +36031 -12365
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40382 -16833
- 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/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +46 -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 +169 -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 +401 -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 +226 -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 +69 -0
- package/dist/providers/spec/cli-adapter.d.ts +217 -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 +180 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +365 -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 +47 -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 +710 -1140
- 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 +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +85 -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 +90 -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 +1822 -61
- 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 +6370 -704
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +81 -30
- package/src/config/chat-history.ts +483 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +391 -4
- 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/cli-detector.ts +28 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- 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 +408 -43
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +151 -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/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +220 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +235 -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 +2102 -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 +496 -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 +1578 -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 +780 -139
- 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 +19 -4
- 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 +958 -91
- 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 +662 -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 +168 -0
- package/src/providers/spec/cli-adapter.ts +986 -0
- package/src/providers/spec/evaluator.ts +345 -0
- package/src/providers/spec/fsm-driver.ts +909 -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 +1136 -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 +226 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +494 -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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
-
import { randomUUID } from 'crypto';
|
|
11
|
+
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
12
12
|
import { getConfigDir } from './config.js';
|
|
13
13
|
import type {
|
|
14
14
|
LocalMeshConfig,
|
|
@@ -18,8 +18,12 @@ import type {
|
|
|
18
18
|
RepoMeshNodePolicy,
|
|
19
19
|
RepoMeshNodeCapabilities,
|
|
20
20
|
RepoMeshCoordinatorConfig,
|
|
21
|
+
RepoMeshHostMetadata,
|
|
22
|
+
RepoMeshDaemonRole,
|
|
23
|
+
RepoMeshTaskAffinityPolicy,
|
|
21
24
|
} from '../repo-mesh-types.js';
|
|
22
|
-
import { DEFAULT_MESH_POLICY } from '../repo-mesh-types.js';
|
|
25
|
+
import { DEFAULT_MESH_POLICY, normalizeMeshSchedulingStrategy, STANDARD_MESH_ROLES } from '../repo-mesh-types.js';
|
|
26
|
+
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
23
27
|
|
|
24
28
|
// ─── Persistence ────────────────────────────────
|
|
25
29
|
|
|
@@ -39,6 +43,20 @@ function loadMeshConfig(): LocalMeshConfig {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
function normalizeCapabilityTags(value: unknown): string[] | undefined {
|
|
47
|
+
if (!Array.isArray(value)) return undefined;
|
|
48
|
+
const seen = new Set<string>();
|
|
49
|
+
const tags = value
|
|
50
|
+
.map(tag => typeof tag === 'string' ? tag.trim() : '')
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.filter(tag => {
|
|
53
|
+
if (seen.has(tag)) return false;
|
|
54
|
+
seen.add(tag);
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
return tags.length ? tags : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
function saveMeshConfig(config: LocalMeshConfig): void {
|
|
43
61
|
const path = getMeshConfigPath();
|
|
44
62
|
writeFileSync(path, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
@@ -78,21 +96,130 @@ const SESSION_CLEANUP_MODES = new Set(['preserve', 'stop', 'delete_stopped', 'st
|
|
|
78
96
|
const SPAWNED_SESSION_VISIBILITY_MODES = new Set(['visible', 'hidden']);
|
|
79
97
|
|
|
80
98
|
function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMeshPolicy> | undefined): RepoMeshPolicy {
|
|
81
|
-
const
|
|
99
|
+
const autoFastForward = normalizeAutoFastForwardPolicy({
|
|
100
|
+
...DEFAULT_MESH_POLICY.autoFastForward,
|
|
101
|
+
...((base?.autoFastForward && typeof base.autoFastForward === 'object') ? base.autoFastForward : {}),
|
|
102
|
+
...((patch?.autoFastForward && typeof patch.autoFastForward === 'object') ? patch.autoFastForward : {}),
|
|
103
|
+
});
|
|
104
|
+
const policy: RepoMeshPolicy = {
|
|
105
|
+
...DEFAULT_MESH_POLICY,
|
|
106
|
+
...(base || {}),
|
|
107
|
+
...(patch || {}),
|
|
108
|
+
autoFastForward,
|
|
109
|
+
};
|
|
82
110
|
if (!['block', 'warn', 'checkpoint_then_continue'].includes(policy.dirtyWorkspaceBehavior)) {
|
|
83
111
|
policy.dirtyWorkspaceBehavior = 'warn';
|
|
84
112
|
}
|
|
85
113
|
const maxParallelTasks = Number(policy.maxParallelTasks);
|
|
86
114
|
policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
|
|
115
|
+
policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
|
|
87
116
|
if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
|
|
88
117
|
policy.sessionCleanupOnNodeRemove = 'preserve';
|
|
89
118
|
}
|
|
90
119
|
if (!SPAWNED_SESSION_VISIBILITY_MODES.has(String(policy.spawnedSessionVisibility))) {
|
|
91
120
|
policy.spawnedSessionVisibility = 'visible';
|
|
92
121
|
}
|
|
122
|
+
// Load-balancing: normalize the scheduling strategy so an invalid/blank value
|
|
123
|
+
// falls back to 'first_eligible' (strict no-change). Only persist the field when
|
|
124
|
+
// it is explicitly a non-default value to keep existing meshes.json untouched.
|
|
125
|
+
const normalizedStrategy = normalizeMeshSchedulingStrategy(policy.schedulingStrategy);
|
|
126
|
+
if (normalizedStrategy === 'first_eligible') {
|
|
127
|
+
delete policy.schedulingStrategy;
|
|
128
|
+
} else {
|
|
129
|
+
policy.schedulingStrategy = normalizedStrategy;
|
|
130
|
+
}
|
|
131
|
+
// Convergence routing: strict opt-in (default false). Only persist when explicitly
|
|
132
|
+
// enabled so existing meshes.json stays byte-for-byte untouched.
|
|
133
|
+
if (policy.autoConvergeCodeChange === true) {
|
|
134
|
+
policy.autoConvergeCodeChange = true;
|
|
135
|
+
} else {
|
|
136
|
+
delete policy.autoConvergeCodeChange;
|
|
137
|
+
}
|
|
138
|
+
// Task-mode → role affinity: format-only validation (drop blanks). Unknown/custom
|
|
139
|
+
// roles are NOT blocked — only a warning is logged. Only persist when the block
|
|
140
|
+
// carries meaningful content so existing meshes.json stays untouched.
|
|
141
|
+
const normalizedAffinity = normalizeTaskAffinityPolicy(policy.taskAffinity);
|
|
142
|
+
if (normalizedAffinity) {
|
|
143
|
+
policy.taskAffinity = normalizedAffinity;
|
|
144
|
+
} else {
|
|
145
|
+
delete policy.taskAffinity;
|
|
146
|
+
}
|
|
93
147
|
return policy;
|
|
94
148
|
}
|
|
95
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Format-only normalization of a task-affinity policy. Trims/lowercases role strings,
|
|
152
|
+
* drops blank custom roles and blank byTaskMode keys, and warns (never blocks) when a
|
|
153
|
+
* declared role is not one of the standard four. Returns undefined when the result is
|
|
154
|
+
* empty/no-op so the field stays absent from persisted config.
|
|
155
|
+
*/
|
|
156
|
+
function normalizeTaskAffinityPolicy(
|
|
157
|
+
value: unknown,
|
|
158
|
+
): RepoMeshTaskAffinityPolicy | undefined {
|
|
159
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
160
|
+
const record = value as Record<string, unknown>;
|
|
161
|
+
const out: RepoMeshTaskAffinityPolicy = {};
|
|
162
|
+
let hasContent = false;
|
|
163
|
+
|
|
164
|
+
if (typeof record.enabled === 'boolean') {
|
|
165
|
+
out.enabled = record.enabled;
|
|
166
|
+
// `enabled:true` is the default — only persist the explicit `false`.
|
|
167
|
+
if (record.enabled === false) hasContent = true;
|
|
168
|
+
else delete out.enabled;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (record.byTaskMode && typeof record.byTaskMode === 'object' && !Array.isArray(record.byTaskMode)) {
|
|
172
|
+
const byTaskMode: Record<string, string> = {};
|
|
173
|
+
for (const [mode, raw] of Object.entries(record.byTaskMode as Record<string, unknown>)) {
|
|
174
|
+
const modeKey = mode.trim();
|
|
175
|
+
if (!modeKey) continue;
|
|
176
|
+
if (typeof raw !== 'string') continue;
|
|
177
|
+
// A blank value is a deliberate opt-out for that mode — preserve it as ''.
|
|
178
|
+
const role = raw.trim().toLowerCase();
|
|
179
|
+
byTaskMode[modeKey] = role;
|
|
180
|
+
if (role && !STANDARD_MESH_ROLES.includes(role as any)) {
|
|
181
|
+
try {
|
|
182
|
+
console.warn(`[mesh] task_affinity.byTaskMode.${modeKey}: custom role "${role}" is not a standard mesh role (${STANDARD_MESH_ROLES.join('/')}); routing still applies`);
|
|
183
|
+
} catch { /* best-effort */ }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (Object.keys(byTaskMode).length > 0) {
|
|
187
|
+
out.byTaskMode = byTaskMode;
|
|
188
|
+
hasContent = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (Array.isArray(record.customRoles)) {
|
|
193
|
+
const seen = new Set<string>();
|
|
194
|
+
const customRoles: string[] = [];
|
|
195
|
+
for (const raw of record.customRoles) {
|
|
196
|
+
if (typeof raw !== 'string') continue;
|
|
197
|
+
const role = raw.trim().toLowerCase();
|
|
198
|
+
if (!role || seen.has(role)) continue;
|
|
199
|
+
seen.add(role);
|
|
200
|
+
customRoles.push(role);
|
|
201
|
+
}
|
|
202
|
+
if (customRoles.length > 0) {
|
|
203
|
+
out.customRoles = customRoles;
|
|
204
|
+
hasContent = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return hasContent ? out : undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function normalizeAutoFastForwardPolicy(value: unknown): NonNullable<RepoMeshPolicy['autoFastForward']> {
|
|
212
|
+
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
213
|
+
? value as Record<string, unknown>
|
|
214
|
+
: {};
|
|
215
|
+
const maxBehind = Number(record.maxBehind);
|
|
216
|
+
return {
|
|
217
|
+
enabled: record.enabled !== false,
|
|
218
|
+
...(Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {}),
|
|
219
|
+
requireCleanSubmodules: record.requireCleanSubmodules !== false,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
96
223
|
export function listMeshes(): LocalMeshEntry[] {
|
|
97
224
|
return loadMeshConfig().meshes;
|
|
98
225
|
}
|
|
@@ -112,6 +239,7 @@ export interface CreateMeshOptions {
|
|
|
112
239
|
defaultBranch?: string;
|
|
113
240
|
policy?: Partial<RepoMeshPolicy>;
|
|
114
241
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
242
|
+
meshHost?: RepoMeshHostMetadata;
|
|
115
243
|
}
|
|
116
244
|
|
|
117
245
|
export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
@@ -133,6 +261,7 @@ export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
|
133
261
|
defaultBranch: opts.defaultBranch,
|
|
134
262
|
policy: mergeMeshPolicy(undefined, opts.policy),
|
|
135
263
|
coordinator: opts.coordinator || {},
|
|
264
|
+
meshHost: opts.meshHost || createDefaultMeshHostMetadata(),
|
|
136
265
|
nodes: [],
|
|
137
266
|
createdAt: now,
|
|
138
267
|
updatedAt: now,
|
|
@@ -148,6 +277,7 @@ export interface UpdateMeshOptions {
|
|
|
148
277
|
defaultBranch?: string;
|
|
149
278
|
policy?: Partial<RepoMeshPolicy>;
|
|
150
279
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
280
|
+
meshHost?: RepoMeshHostMetadata;
|
|
151
281
|
}
|
|
152
282
|
|
|
153
283
|
export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEntry | undefined {
|
|
@@ -159,6 +289,7 @@ export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEn
|
|
|
159
289
|
if (opts.defaultBranch !== undefined) mesh.defaultBranch = opts.defaultBranch;
|
|
160
290
|
if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
|
|
161
291
|
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
292
|
+
if (opts.meshHost) mesh.meshHost = opts.meshHost;
|
|
162
293
|
mesh.updatedAt = new Date().toISOString();
|
|
163
294
|
|
|
164
295
|
saveMeshConfig(config);
|
|
@@ -174,6 +305,240 @@ export function deleteMesh(meshId: string): boolean {
|
|
|
174
305
|
return true;
|
|
175
306
|
}
|
|
176
307
|
|
|
308
|
+
function normalizeManualHostAddress(hostAddress: string): string {
|
|
309
|
+
const normalized = hostAddress.trim().replace(/\/+$/, '');
|
|
310
|
+
if (!normalized) throw new Error('hostAddress required');
|
|
311
|
+
let parsed: URL;
|
|
312
|
+
try {
|
|
313
|
+
parsed = new URL(normalized);
|
|
314
|
+
} catch {
|
|
315
|
+
throw new Error('hostAddress must be a valid http(s) or ws(s) URL');
|
|
316
|
+
}
|
|
317
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
|
|
318
|
+
throw new Error('hostAddress must use http, https, ws, or wss');
|
|
319
|
+
}
|
|
320
|
+
return normalized;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function tokenIdForManualPairing(token: string): string {
|
|
324
|
+
return `tok_${createHash('sha256').update(token).digest('hex').slice(0, 16)}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function normalizeTokenExpiry(value: unknown): string | undefined {
|
|
328
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
329
|
+
const date = new Date(value);
|
|
330
|
+
if (Number.isNaN(date.getTime())) throw new Error('expiresAt must be a valid ISO date');
|
|
331
|
+
return date.toISOString();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function assertPairingTokenValid(pairing: RepoMeshHostMetadata['pairing'], rawToken: string, nowIso: string): { ok: true; tokenId: string } | { ok: false; reason: string; expectedTokenId?: string; presentedTokenId?: string } {
|
|
335
|
+
const token = rawToken.trim();
|
|
336
|
+
if (!token) return { ok: false, reason: 'token required' };
|
|
337
|
+
const presentedTokenId = tokenIdForManualPairing(token);
|
|
338
|
+
const expectedTokenId = pairing?.tokenId;
|
|
339
|
+
if (!expectedTokenId || pairing?.status === 'not_configured' || pairing?.status === 'revoked') {
|
|
340
|
+
return { ok: false, reason: 'host pairing token is not configured', presentedTokenId };
|
|
341
|
+
}
|
|
342
|
+
if (pairing.expiresAt && new Date(pairing.expiresAt).getTime() <= new Date(nowIso).getTime()) {
|
|
343
|
+
return { ok: false, reason: 'host pairing token expired', expectedTokenId, presentedTokenId };
|
|
344
|
+
}
|
|
345
|
+
if (presentedTokenId !== expectedTokenId) {
|
|
346
|
+
return { ok: false, reason: 'invalid pairing token', expectedTokenId, presentedTokenId };
|
|
347
|
+
}
|
|
348
|
+
return { ok: true, tokenId: presentedTokenId };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface ConfigureMeshHostPairingOptions {
|
|
352
|
+
hostAddress: string;
|
|
353
|
+
token: string;
|
|
354
|
+
now?: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function configureMeshHostPairing(
|
|
358
|
+
meshId: string,
|
|
359
|
+
opts: ConfigureMeshHostPairingOptions,
|
|
360
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; hostAddress: string } | undefined {
|
|
361
|
+
const hostAddress = normalizeManualHostAddress(opts.hostAddress);
|
|
362
|
+
const token = opts.token.trim();
|
|
363
|
+
if (!token) throw new Error('token required');
|
|
364
|
+
|
|
365
|
+
const config = loadMeshConfig();
|
|
366
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
367
|
+
if (!mesh) return undefined;
|
|
368
|
+
|
|
369
|
+
const now = opts.now || new Date().toISOString();
|
|
370
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
371
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
372
|
+
...previous,
|
|
373
|
+
role: 'member',
|
|
374
|
+
hostAddress,
|
|
375
|
+
pairing: {
|
|
376
|
+
status: 'pairing',
|
|
377
|
+
tokenId: tokenIdForManualPairing(token),
|
|
378
|
+
lastPairedAt: now,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
mesh.meshHost = meshHost;
|
|
383
|
+
mesh.updatedAt = now;
|
|
384
|
+
saveMeshConfig(config);
|
|
385
|
+
return { mesh, meshHost, hostAddress };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export interface CreateMeshHostPairingTokenOptions {
|
|
389
|
+
token?: string;
|
|
390
|
+
expiresAt?: string;
|
|
391
|
+
now?: string;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function createMeshHostPairingToken(
|
|
395
|
+
meshId: string,
|
|
396
|
+
opts: CreateMeshHostPairingTokenOptions = {},
|
|
397
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; token: string; tokenId: string; expiresAt?: string } | undefined {
|
|
398
|
+
const config = loadMeshConfig();
|
|
399
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
400
|
+
if (!mesh) return undefined;
|
|
401
|
+
const now = opts.now || new Date().toISOString();
|
|
402
|
+
const token = (opts.token || `mhj_${randomBytes(24).toString('base64url')}`).trim();
|
|
403
|
+
if (!token) throw new Error('token required');
|
|
404
|
+
const tokenId = tokenIdForManualPairing(token);
|
|
405
|
+
const expiresAt = normalizeTokenExpiry(opts.expiresAt);
|
|
406
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
407
|
+
if (previous.role === 'member') {
|
|
408
|
+
throw new Error('Mesh Host daemon required to create host pairing tokens; member daemons cannot mint host join tokens.');
|
|
409
|
+
}
|
|
410
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
411
|
+
...previous,
|
|
412
|
+
role: 'host',
|
|
413
|
+
pairing: {
|
|
414
|
+
status: 'pairing',
|
|
415
|
+
tokenId,
|
|
416
|
+
lastPairedAt: now,
|
|
417
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
mesh.meshHost = meshHost;
|
|
421
|
+
mesh.updatedAt = now;
|
|
422
|
+
saveMeshConfig(config);
|
|
423
|
+
return { mesh, meshHost, token, tokenId, ...(expiresAt ? { expiresAt } : {}) };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export interface MeshHostJoinMemberNodeInput {
|
|
427
|
+
id?: string;
|
|
428
|
+
workspace: string;
|
|
429
|
+
repoRoot?: string;
|
|
430
|
+
daemonId?: string;
|
|
431
|
+
machineId?: string;
|
|
432
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
433
|
+
policy?: RepoMeshNodePolicy;
|
|
434
|
+
role?: RepoMeshDaemonRole;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export interface ApplyMeshHostJoinOptions {
|
|
438
|
+
token: string;
|
|
439
|
+
memberNode: MeshHostJoinMemberNodeInput;
|
|
440
|
+
memberMeshId?: string;
|
|
441
|
+
now?: string;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function applyMeshHostJoinRequest(
|
|
445
|
+
meshId: string,
|
|
446
|
+
opts: ApplyMeshHostJoinOptions,
|
|
447
|
+
): { accepted: true; mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; node: LocalMeshNodeEntry; tokenId: string } | { accepted: false; mesh?: LocalMeshEntry; meshHost?: RepoMeshHostMetadata; tokenId?: string; reason: string } | undefined {
|
|
448
|
+
const config = loadMeshConfig();
|
|
449
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
450
|
+
if (!mesh) return undefined;
|
|
451
|
+
const now = opts.now || new Date().toISOString();
|
|
452
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
453
|
+
if (previous.role === 'member') {
|
|
454
|
+
return { accepted: false, mesh, meshHost: previous, reason: 'Mesh Host daemon required to accept join requests' };
|
|
455
|
+
}
|
|
456
|
+
const meshHost: RepoMeshHostMetadata = { ...previous, role: 'host' };
|
|
457
|
+
const validation = assertPairingTokenValid(meshHost.pairing, opts.token, now);
|
|
458
|
+
if (!validation.ok) {
|
|
459
|
+
mesh.meshHost = {
|
|
460
|
+
...meshHost,
|
|
461
|
+
pairing: {
|
|
462
|
+
...(meshHost.pairing || { status: 'not_configured' as const }),
|
|
463
|
+
status: 'rejected',
|
|
464
|
+
lastRejectedAt: now,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
mesh.updatedAt = now;
|
|
468
|
+
saveMeshConfig(config);
|
|
469
|
+
return { accepted: false, mesh, meshHost: mesh.meshHost, tokenId: validation.presentedTokenId, reason: validation.reason };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const workspace = opts.memberNode.workspace.trim();
|
|
473
|
+
if (!workspace) throw new Error('memberNode.workspace required');
|
|
474
|
+
const memberId = opts.memberNode.id?.trim();
|
|
475
|
+
let node = mesh.nodes.find(n => (memberId && n.id === memberId) || n.workspace === workspace);
|
|
476
|
+
if (node) {
|
|
477
|
+
node.workspace = workspace;
|
|
478
|
+
node.repoRoot = opts.memberNode.repoRoot;
|
|
479
|
+
node.daemonId = opts.memberNode.daemonId;
|
|
480
|
+
node.machineId = opts.memberNode.machineId;
|
|
481
|
+
node.userOverrides = opts.memberNode.userOverrides || node.userOverrides || {};
|
|
482
|
+
node.policy = { ...(node.policy || {}), ...(opts.memberNode.policy || {}) };
|
|
483
|
+
node.role = 'member';
|
|
484
|
+
} else {
|
|
485
|
+
if (mesh.nodes.length >= 10) throw new Error('Maximum 10 nodes per mesh');
|
|
486
|
+
node = {
|
|
487
|
+
id: memberId || `node_${randomUUID().replace(/-/g, '')}`,
|
|
488
|
+
workspace,
|
|
489
|
+
repoRoot: opts.memberNode.repoRoot,
|
|
490
|
+
daemonId: opts.memberNode.daemonId,
|
|
491
|
+
machineId: opts.memberNode.machineId,
|
|
492
|
+
userOverrides: opts.memberNode.userOverrides || {},
|
|
493
|
+
policy: opts.memberNode.policy || {},
|
|
494
|
+
role: 'member',
|
|
495
|
+
};
|
|
496
|
+
mesh.nodes.push(node);
|
|
497
|
+
}
|
|
498
|
+
mesh.meshHost = {
|
|
499
|
+
...meshHost,
|
|
500
|
+
pairing: {
|
|
501
|
+
...(meshHost.pairing || {}),
|
|
502
|
+
status: 'paired',
|
|
503
|
+
tokenId: validation.tokenId,
|
|
504
|
+
joinedAt: now,
|
|
505
|
+
lastPairedAt: meshHost.pairing?.lastPairedAt || now,
|
|
506
|
+
...(meshHost.pairing?.expiresAt ? { expiresAt: meshHost.pairing.expiresAt } : {}),
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
mesh.updatedAt = now;
|
|
510
|
+
saveMeshConfig(config);
|
|
511
|
+
return { accepted: true, mesh, meshHost: mesh.meshHost, node, tokenId: validation.tokenId };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function markMeshHostPairingJoined(
|
|
515
|
+
meshId: string,
|
|
516
|
+
opts: { hostDaemonId?: string; hostNodeId?: string; joinedAt?: string; token?: string; tokenId?: string },
|
|
517
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata } | undefined {
|
|
518
|
+
const config = loadMeshConfig();
|
|
519
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
520
|
+
if (!mesh) return undefined;
|
|
521
|
+
const now = opts.joinedAt || new Date().toISOString();
|
|
522
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
523
|
+
const tokenId = opts.tokenId || (opts.token ? tokenIdForManualPairing(opts.token) : previous.pairing?.tokenId);
|
|
524
|
+
mesh.meshHost = {
|
|
525
|
+
...previous,
|
|
526
|
+
role: 'member',
|
|
527
|
+
...(opts.hostDaemonId ? { hostDaemonId: opts.hostDaemonId } : {}),
|
|
528
|
+
...(opts.hostNodeId ? { hostNodeId: opts.hostNodeId } : {}),
|
|
529
|
+
pairing: {
|
|
530
|
+
...(previous.pairing || {}),
|
|
531
|
+
status: 'paired',
|
|
532
|
+
...(tokenId ? { tokenId } : {}),
|
|
533
|
+
joinedAt: now,
|
|
534
|
+
lastPairedAt: previous.pairing?.lastPairedAt || now,
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
mesh.updatedAt = now;
|
|
538
|
+
saveMeshConfig(config);
|
|
539
|
+
return { mesh, meshHost: mesh.meshHost };
|
|
540
|
+
}
|
|
541
|
+
|
|
177
542
|
// ─── Node Operations ────────────────────────────
|
|
178
543
|
|
|
179
544
|
export interface AddNodeOptions {
|
|
@@ -181,11 +546,14 @@ export interface AddNodeOptions {
|
|
|
181
546
|
repoRoot?: string;
|
|
182
547
|
daemonId?: string;
|
|
183
548
|
machineId?: string;
|
|
549
|
+
capabilities?: string[];
|
|
184
550
|
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
185
551
|
policy?: RepoMeshNodePolicy;
|
|
186
552
|
isLocalWorktree?: boolean;
|
|
187
553
|
worktreeBranch?: string;
|
|
188
554
|
clonedFromNodeId?: string;
|
|
555
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
556
|
+
role?: RepoMeshDaemonRole;
|
|
189
557
|
}
|
|
190
558
|
|
|
191
559
|
export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntry | undefined {
|
|
@@ -208,11 +576,14 @@ export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntr
|
|
|
208
576
|
repoRoot: opts.repoRoot,
|
|
209
577
|
daemonId: opts.daemonId,
|
|
210
578
|
machineId: opts.machineId,
|
|
579
|
+
capabilities: normalizeCapabilityTags(opts.capabilities),
|
|
211
580
|
userOverrides: opts.userOverrides || {},
|
|
212
581
|
policy: opts.policy || {},
|
|
213
582
|
isLocalWorktree: opts.isLocalWorktree,
|
|
214
583
|
worktreeBranch: opts.worktreeBranch,
|
|
215
584
|
clonedFromNodeId: opts.clonedFromNodeId,
|
|
585
|
+
worktreeBootstrap: opts.worktreeBootstrap,
|
|
586
|
+
role: opts.role,
|
|
216
587
|
};
|
|
217
588
|
|
|
218
589
|
mesh.nodes.push(node);
|
|
@@ -238,7 +609,14 @@ export function removeNode(meshId: string, nodeId: string): boolean {
|
|
|
238
609
|
export function updateNode(
|
|
239
610
|
meshId: string,
|
|
240
611
|
nodeId: string,
|
|
241
|
-
opts: {
|
|
612
|
+
opts: {
|
|
613
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
614
|
+
policy?: RepoMeshNodePolicy;
|
|
615
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
616
|
+
/** Per-node instruction surfaced in the coordinator prompt. Pass an
|
|
617
|
+
* empty string or undefined to clear it. */
|
|
618
|
+
systemPrompt?: string;
|
|
619
|
+
},
|
|
242
620
|
): LocalMeshNodeEntry | undefined {
|
|
243
621
|
const config = loadMeshConfig();
|
|
244
622
|
const mesh = config.meshes.find(m => m.id === meshId);
|
|
@@ -249,6 +627,15 @@ export function updateNode(
|
|
|
249
627
|
|
|
250
628
|
if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
|
|
251
629
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
630
|
+
if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
|
|
631
|
+
if (Object.prototype.hasOwnProperty.call(opts, 'systemPrompt')) {
|
|
632
|
+
// Honor explicit clears: { systemPrompt: undefined } drops the field.
|
|
633
|
+
if (opts.systemPrompt && opts.systemPrompt.trim()) {
|
|
634
|
+
node.systemPrompt = opts.systemPrompt;
|
|
635
|
+
} else {
|
|
636
|
+
delete node.systemPrompt;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
252
639
|
mesh.updatedAt = new Date().toISOString();
|
|
253
640
|
saveMeshConfig(config);
|
|
254
641
|
return node;
|
|
@@ -92,12 +92,18 @@ export function buildSessionReadStateKey(sessionId: string, providerSessionId?:
|
|
|
92
92
|
|
|
93
93
|
export function getSessionSeenAt(state: DaemonState, sessionId: string, providerSessionId?: string | null): number {
|
|
94
94
|
const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
|
|
95
|
-
return state.sessionReads?.[providerKey] || state.sessionReads?.[sessionId] || 0;
|
|
95
|
+
return Math.max(state.sessionReads?.[providerKey] || 0, state.sessionReads?.[sessionId] || 0);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export function getSessionSeenMarker(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
|
|
99
99
|
const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
|
|
100
|
-
|
|
100
|
+
const providerSeenAt = state.sessionReads?.[providerKey] || 0;
|
|
101
|
+
const sessionSeenAt = state.sessionReads?.[sessionId] || 0;
|
|
102
|
+
const providerMarker = state.sessionReadMarkers?.[providerKey] || '';
|
|
103
|
+
const sessionMarker = state.sessionReadMarkers?.[sessionId] || '';
|
|
104
|
+
if (sessionSeenAt > providerSeenAt && sessionMarker) return sessionMarker;
|
|
105
|
+
if (providerSeenAt > sessionSeenAt && providerMarker) return providerMarker;
|
|
106
|
+
return providerMarker || sessionMarker;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
export function getSessionNotificationDismissal(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import * as os from 'os';
|
|
12
|
+
import { DEFAULT_SESSION_HOST_COLS, DEFAULT_SESSION_HOST_ROWS } from '@adhdev/session-host-core';
|
|
12
13
|
import type * as http from 'http';
|
|
13
14
|
import type { ChildProcess } from 'child_process';
|
|
14
15
|
import type { DevServerContext, ProviderCategory } from './dev-server-types.js';
|
|
@@ -482,8 +483,8 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
|
|
|
482
483
|
const isWin = os.platform() === 'win32';
|
|
483
484
|
child = pty.spawn(isWin ? 'cmd.exe' : (process.env.SHELL || '/bin/zsh'), [isWin ? '/c' : '-c', shellCmd], {
|
|
484
485
|
name: 'xterm-256color',
|
|
485
|
-
cols:
|
|
486
|
-
rows:
|
|
486
|
+
cols: DEFAULT_SESSION_HOST_COLS,
|
|
487
|
+
rows: DEFAULT_SESSION_HOST_ROWS,
|
|
487
488
|
cwd: providerDir,
|
|
488
489
|
env: { ...process.env, ...(spawn.env || {}) },
|
|
489
490
|
});
|
|
@@ -802,7 +802,16 @@ export async function handleCliSend(ctx: DevServerContext, req: http.IncomingMes
|
|
|
802
802
|
}
|
|
803
803
|
|
|
804
804
|
try {
|
|
805
|
-
|
|
805
|
+
if (target.category === 'cli') {
|
|
806
|
+
const bundle = getCliTargetBundle(ctx, type, instanceId);
|
|
807
|
+
if (!bundle) {
|
|
808
|
+
ctx.json(res, 404, { error: `No running CLI adapter found for: ${type || instanceId}` });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
await bundle.adapter.sendMessage(text);
|
|
812
|
+
} else {
|
|
813
|
+
ctx.instanceManager!.sendEvent(target.instanceId, 'send_message', { text });
|
|
814
|
+
}
|
|
806
815
|
ctx.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId });
|
|
807
816
|
} catch (e: any) {
|
|
808
817
|
ctx.json(res, 500, { error: `Send failed: ${e.message}` });
|
|
@@ -12,6 +12,7 @@ import * as os from 'os';
|
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import { existsSync } from 'fs';
|
|
14
14
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
15
|
+
import { findBinary } from '../cli-adapters/provider-cli-shared.js';
|
|
15
16
|
|
|
16
17
|
export interface CLIInfo {
|
|
17
18
|
id: string;
|
|
@@ -57,6 +58,29 @@ function resolveCommandPath(command: string): string | null {
|
|
|
57
58
|
return null;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a CLI command to an absolute path for install detection.
|
|
63
|
+
* Order: explicit path → PATH (`where`/`which`) → well-known global-bin dirs
|
|
64
|
+
* (e.g. %APPDATA%\npm) via the shared spawn-layer findBinary.
|
|
65
|
+
*
|
|
66
|
+
* The final step keeps detection consistent with the spawn layer: findBinary
|
|
67
|
+
* already searches npm's default Windows prefix at %APPDATA%\npm (where
|
|
68
|
+
* `npm i -g @openai/codex` lands), so a CLI installed under an npm prefix that
|
|
69
|
+
* is NOT on the daemon's inherited PATH is detected as installed instead of
|
|
70
|
+
* being blocked at the launch gate. findBinary returns a bare "<name>.cmd"
|
|
71
|
+
* (non-absolute) when nothing is found, so we only accept an absolute path
|
|
72
|
+
* that actually exists.
|
|
73
|
+
*/
|
|
74
|
+
async function resolveDetectionPath(command: string, whichCmd: string): Promise<string | null> {
|
|
75
|
+
const explicitPath = resolveCommandPath(command);
|
|
76
|
+
if (explicitPath) return explicitPath;
|
|
77
|
+
const whichResult = await execAsync(`${whichCmd} ${shellQuote(command)}`);
|
|
78
|
+
if (whichResult) return whichResult.split('\n')[0];
|
|
79
|
+
const resolved = findBinary(command);
|
|
80
|
+
if (path.isAbsolute(resolved) && existsSync(resolved)) return resolved;
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
/** Run a shell command with timeout, returning stdout or null on failure */
|
|
61
85
|
function execAsync(cmd: string, timeoutMs = 5000): Promise<string | null> {
|
|
62
86
|
return new Promise((resolve) => {
|
|
@@ -97,11 +121,8 @@ export async function detectCLIs(
|
|
|
97
121
|
const results = await Promise.all(
|
|
98
122
|
cliList.map(async (cli): Promise<CLIInfo> => {
|
|
99
123
|
try {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
103
|
-
|
|
104
|
-
const firstPath = explicitPath || pathResult.split('\n')[0];
|
|
124
|
+
const firstPath = await resolveDetectionPath(cli.command, whichCmd);
|
|
125
|
+
if (!firstPath) return { ...cli, installed: false };
|
|
105
126
|
|
|
106
127
|
// Get version (parallel with other checks)
|
|
107
128
|
let version: string | undefined;
|
|
@@ -148,10 +169,8 @@ export async function detectCLI(
|
|
|
148
169
|
const platform = os.platform();
|
|
149
170
|
const whichCmd = platform === 'win32' ? 'where' : 'which';
|
|
150
171
|
try {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
if (!pathResult) return null;
|
|
154
|
-
const firstPath = explicitPath || pathResult.split('\n')[0];
|
|
172
|
+
const firstPath = await resolveDetectionPath(target.command, whichCmd);
|
|
173
|
+
if (!firstPath) return null;
|
|
155
174
|
let version: string | undefined;
|
|
156
175
|
if (options?.includeVersion !== false) {
|
|
157
176
|
const versionCommands = [
|