@adhdev/daemon-core 0.9.82-rc.31 → 0.9.82-rc.311
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 +214 -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 +34705 -11938
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38412 -15757
- 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 +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 +395 -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 +204 -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 +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/repo-mesh-types.d.ts +291 -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 +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 +6041 -589
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +29 -0
- package/src/config/chat-history.ts +483 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +320 -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/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 +142 -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 +1942 -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 +1565 -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 +703 -140
- 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 +980 -0
- package/src/providers/spec/evaluator.ts +345 -0
- package/src/providers/spec/fsm-driver.ts +888 -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 +1035 -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 +38 -20
- package/src/repo-mesh-types.ts +376 -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/config/config.ts
CHANGED
|
@@ -272,6 +272,18 @@ export function getConfigDir(): string {
|
|
|
272
272
|
return dir;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Get the daemon runtime data directory (~/.adhdev/daemon/).
|
|
277
|
+
* Distinct from the user-config dir so runtime state can be cleared independently.
|
|
278
|
+
*/
|
|
279
|
+
export function getDaemonDataDir(): string {
|
|
280
|
+
const dir = join(getConfigDir(), 'daemon');
|
|
281
|
+
if (!existsSync(dir)) {
|
|
282
|
+
mkdirSync(dir, { recursive: true });
|
|
283
|
+
}
|
|
284
|
+
return dir;
|
|
285
|
+
}
|
|
286
|
+
|
|
275
287
|
/**
|
|
276
288
|
* Get the config file path
|
|
277
289
|
*/
|
|
@@ -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,11 @@ import type {
|
|
|
18
18
|
RepoMeshNodePolicy,
|
|
19
19
|
RepoMeshNodeCapabilities,
|
|
20
20
|
RepoMeshCoordinatorConfig,
|
|
21
|
+
RepoMeshHostMetadata,
|
|
22
|
+
RepoMeshDaemonRole,
|
|
21
23
|
} from '../repo-mesh-types.js';
|
|
22
|
-
import { DEFAULT_MESH_POLICY } from '../repo-mesh-types.js';
|
|
24
|
+
import { DEFAULT_MESH_POLICY, normalizeMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
25
|
+
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
23
26
|
|
|
24
27
|
// ─── Persistence ────────────────────────────────
|
|
25
28
|
|
|
@@ -39,6 +42,20 @@ function loadMeshConfig(): LocalMeshConfig {
|
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
function normalizeCapabilityTags(value: unknown): string[] | undefined {
|
|
46
|
+
if (!Array.isArray(value)) return undefined;
|
|
47
|
+
const seen = new Set<string>();
|
|
48
|
+
const tags = value
|
|
49
|
+
.map(tag => typeof tag === 'string' ? tag.trim() : '')
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.filter(tag => {
|
|
52
|
+
if (seen.has(tag)) return false;
|
|
53
|
+
seen.add(tag);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
return tags.length ? tags : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
function saveMeshConfig(config: LocalMeshConfig): void {
|
|
43
60
|
const path = getMeshConfigPath();
|
|
44
61
|
writeFileSync(path, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
@@ -78,21 +95,60 @@ const SESSION_CLEANUP_MODES = new Set(['preserve', 'stop', 'delete_stopped', 'st
|
|
|
78
95
|
const SPAWNED_SESSION_VISIBILITY_MODES = new Set(['visible', 'hidden']);
|
|
79
96
|
|
|
80
97
|
function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMeshPolicy> | undefined): RepoMeshPolicy {
|
|
81
|
-
const
|
|
98
|
+
const autoFastForward = normalizeAutoFastForwardPolicy({
|
|
99
|
+
...DEFAULT_MESH_POLICY.autoFastForward,
|
|
100
|
+
...((base?.autoFastForward && typeof base.autoFastForward === 'object') ? base.autoFastForward : {}),
|
|
101
|
+
...((patch?.autoFastForward && typeof patch.autoFastForward === 'object') ? patch.autoFastForward : {}),
|
|
102
|
+
});
|
|
103
|
+
const policy: RepoMeshPolicy = {
|
|
104
|
+
...DEFAULT_MESH_POLICY,
|
|
105
|
+
...(base || {}),
|
|
106
|
+
...(patch || {}),
|
|
107
|
+
autoFastForward,
|
|
108
|
+
};
|
|
82
109
|
if (!['block', 'warn', 'checkpoint_then_continue'].includes(policy.dirtyWorkspaceBehavior)) {
|
|
83
110
|
policy.dirtyWorkspaceBehavior = 'warn';
|
|
84
111
|
}
|
|
85
112
|
const maxParallelTasks = Number(policy.maxParallelTasks);
|
|
86
113
|
policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
|
|
114
|
+
policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
|
|
87
115
|
if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
|
|
88
116
|
policy.sessionCleanupOnNodeRemove = 'preserve';
|
|
89
117
|
}
|
|
90
118
|
if (!SPAWNED_SESSION_VISIBILITY_MODES.has(String(policy.spawnedSessionVisibility))) {
|
|
91
119
|
policy.spawnedSessionVisibility = 'visible';
|
|
92
120
|
}
|
|
121
|
+
// Load-balancing: normalize the scheduling strategy so an invalid/blank value
|
|
122
|
+
// falls back to 'first_eligible' (strict no-change). Only persist the field when
|
|
123
|
+
// it is explicitly a non-default value to keep existing meshes.json untouched.
|
|
124
|
+
const normalizedStrategy = normalizeMeshSchedulingStrategy(policy.schedulingStrategy);
|
|
125
|
+
if (normalizedStrategy === 'first_eligible') {
|
|
126
|
+
delete policy.schedulingStrategy;
|
|
127
|
+
} else {
|
|
128
|
+
policy.schedulingStrategy = normalizedStrategy;
|
|
129
|
+
}
|
|
130
|
+
// Convergence routing: strict opt-in (default false). Only persist when explicitly
|
|
131
|
+
// enabled so existing meshes.json stays byte-for-byte untouched.
|
|
132
|
+
if (policy.autoConvergeCodeChange === true) {
|
|
133
|
+
policy.autoConvergeCodeChange = true;
|
|
134
|
+
} else {
|
|
135
|
+
delete policy.autoConvergeCodeChange;
|
|
136
|
+
}
|
|
93
137
|
return policy;
|
|
94
138
|
}
|
|
95
139
|
|
|
140
|
+
function normalizeAutoFastForwardPolicy(value: unknown): NonNullable<RepoMeshPolicy['autoFastForward']> {
|
|
141
|
+
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
142
|
+
? value as Record<string, unknown>
|
|
143
|
+
: {};
|
|
144
|
+
const maxBehind = Number(record.maxBehind);
|
|
145
|
+
return {
|
|
146
|
+
enabled: record.enabled !== false,
|
|
147
|
+
...(Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {}),
|
|
148
|
+
requireCleanSubmodules: record.requireCleanSubmodules !== false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
96
152
|
export function listMeshes(): LocalMeshEntry[] {
|
|
97
153
|
return loadMeshConfig().meshes;
|
|
98
154
|
}
|
|
@@ -112,6 +168,7 @@ export interface CreateMeshOptions {
|
|
|
112
168
|
defaultBranch?: string;
|
|
113
169
|
policy?: Partial<RepoMeshPolicy>;
|
|
114
170
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
171
|
+
meshHost?: RepoMeshHostMetadata;
|
|
115
172
|
}
|
|
116
173
|
|
|
117
174
|
export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
@@ -133,6 +190,7 @@ export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
|
133
190
|
defaultBranch: opts.defaultBranch,
|
|
134
191
|
policy: mergeMeshPolicy(undefined, opts.policy),
|
|
135
192
|
coordinator: opts.coordinator || {},
|
|
193
|
+
meshHost: opts.meshHost || createDefaultMeshHostMetadata(),
|
|
136
194
|
nodes: [],
|
|
137
195
|
createdAt: now,
|
|
138
196
|
updatedAt: now,
|
|
@@ -148,6 +206,7 @@ export interface UpdateMeshOptions {
|
|
|
148
206
|
defaultBranch?: string;
|
|
149
207
|
policy?: Partial<RepoMeshPolicy>;
|
|
150
208
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
209
|
+
meshHost?: RepoMeshHostMetadata;
|
|
151
210
|
}
|
|
152
211
|
|
|
153
212
|
export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEntry | undefined {
|
|
@@ -159,6 +218,7 @@ export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEn
|
|
|
159
218
|
if (opts.defaultBranch !== undefined) mesh.defaultBranch = opts.defaultBranch;
|
|
160
219
|
if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
|
|
161
220
|
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
221
|
+
if (opts.meshHost) mesh.meshHost = opts.meshHost;
|
|
162
222
|
mesh.updatedAt = new Date().toISOString();
|
|
163
223
|
|
|
164
224
|
saveMeshConfig(config);
|
|
@@ -174,6 +234,240 @@ export function deleteMesh(meshId: string): boolean {
|
|
|
174
234
|
return true;
|
|
175
235
|
}
|
|
176
236
|
|
|
237
|
+
function normalizeManualHostAddress(hostAddress: string): string {
|
|
238
|
+
const normalized = hostAddress.trim().replace(/\/+$/, '');
|
|
239
|
+
if (!normalized) throw new Error('hostAddress required');
|
|
240
|
+
let parsed: URL;
|
|
241
|
+
try {
|
|
242
|
+
parsed = new URL(normalized);
|
|
243
|
+
} catch {
|
|
244
|
+
throw new Error('hostAddress must be a valid http(s) or ws(s) URL');
|
|
245
|
+
}
|
|
246
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
|
|
247
|
+
throw new Error('hostAddress must use http, https, ws, or wss');
|
|
248
|
+
}
|
|
249
|
+
return normalized;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function tokenIdForManualPairing(token: string): string {
|
|
253
|
+
return `tok_${createHash('sha256').update(token).digest('hex').slice(0, 16)}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeTokenExpiry(value: unknown): string | undefined {
|
|
257
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
258
|
+
const date = new Date(value);
|
|
259
|
+
if (Number.isNaN(date.getTime())) throw new Error('expiresAt must be a valid ISO date');
|
|
260
|
+
return date.toISOString();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function assertPairingTokenValid(pairing: RepoMeshHostMetadata['pairing'], rawToken: string, nowIso: string): { ok: true; tokenId: string } | { ok: false; reason: string; expectedTokenId?: string; presentedTokenId?: string } {
|
|
264
|
+
const token = rawToken.trim();
|
|
265
|
+
if (!token) return { ok: false, reason: 'token required' };
|
|
266
|
+
const presentedTokenId = tokenIdForManualPairing(token);
|
|
267
|
+
const expectedTokenId = pairing?.tokenId;
|
|
268
|
+
if (!expectedTokenId || pairing?.status === 'not_configured' || pairing?.status === 'revoked') {
|
|
269
|
+
return { ok: false, reason: 'host pairing token is not configured', presentedTokenId };
|
|
270
|
+
}
|
|
271
|
+
if (pairing.expiresAt && new Date(pairing.expiresAt).getTime() <= new Date(nowIso).getTime()) {
|
|
272
|
+
return { ok: false, reason: 'host pairing token expired', expectedTokenId, presentedTokenId };
|
|
273
|
+
}
|
|
274
|
+
if (presentedTokenId !== expectedTokenId) {
|
|
275
|
+
return { ok: false, reason: 'invalid pairing token', expectedTokenId, presentedTokenId };
|
|
276
|
+
}
|
|
277
|
+
return { ok: true, tokenId: presentedTokenId };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface ConfigureMeshHostPairingOptions {
|
|
281
|
+
hostAddress: string;
|
|
282
|
+
token: string;
|
|
283
|
+
now?: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function configureMeshHostPairing(
|
|
287
|
+
meshId: string,
|
|
288
|
+
opts: ConfigureMeshHostPairingOptions,
|
|
289
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; hostAddress: string } | undefined {
|
|
290
|
+
const hostAddress = normalizeManualHostAddress(opts.hostAddress);
|
|
291
|
+
const token = opts.token.trim();
|
|
292
|
+
if (!token) throw new Error('token required');
|
|
293
|
+
|
|
294
|
+
const config = loadMeshConfig();
|
|
295
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
296
|
+
if (!mesh) return undefined;
|
|
297
|
+
|
|
298
|
+
const now = opts.now || new Date().toISOString();
|
|
299
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
300
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
301
|
+
...previous,
|
|
302
|
+
role: 'member',
|
|
303
|
+
hostAddress,
|
|
304
|
+
pairing: {
|
|
305
|
+
status: 'pairing',
|
|
306
|
+
tokenId: tokenIdForManualPairing(token),
|
|
307
|
+
lastPairedAt: now,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
mesh.meshHost = meshHost;
|
|
312
|
+
mesh.updatedAt = now;
|
|
313
|
+
saveMeshConfig(config);
|
|
314
|
+
return { mesh, meshHost, hostAddress };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export interface CreateMeshHostPairingTokenOptions {
|
|
318
|
+
token?: string;
|
|
319
|
+
expiresAt?: string;
|
|
320
|
+
now?: string;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function createMeshHostPairingToken(
|
|
324
|
+
meshId: string,
|
|
325
|
+
opts: CreateMeshHostPairingTokenOptions = {},
|
|
326
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; token: string; tokenId: string; expiresAt?: string } | undefined {
|
|
327
|
+
const config = loadMeshConfig();
|
|
328
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
329
|
+
if (!mesh) return undefined;
|
|
330
|
+
const now = opts.now || new Date().toISOString();
|
|
331
|
+
const token = (opts.token || `mhj_${randomBytes(24).toString('base64url')}`).trim();
|
|
332
|
+
if (!token) throw new Error('token required');
|
|
333
|
+
const tokenId = tokenIdForManualPairing(token);
|
|
334
|
+
const expiresAt = normalizeTokenExpiry(opts.expiresAt);
|
|
335
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
336
|
+
if (previous.role === 'member') {
|
|
337
|
+
throw new Error('Mesh Host daemon required to create host pairing tokens; member daemons cannot mint host join tokens.');
|
|
338
|
+
}
|
|
339
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
340
|
+
...previous,
|
|
341
|
+
role: 'host',
|
|
342
|
+
pairing: {
|
|
343
|
+
status: 'pairing',
|
|
344
|
+
tokenId,
|
|
345
|
+
lastPairedAt: now,
|
|
346
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
mesh.meshHost = meshHost;
|
|
350
|
+
mesh.updatedAt = now;
|
|
351
|
+
saveMeshConfig(config);
|
|
352
|
+
return { mesh, meshHost, token, tokenId, ...(expiresAt ? { expiresAt } : {}) };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export interface MeshHostJoinMemberNodeInput {
|
|
356
|
+
id?: string;
|
|
357
|
+
workspace: string;
|
|
358
|
+
repoRoot?: string;
|
|
359
|
+
daemonId?: string;
|
|
360
|
+
machineId?: string;
|
|
361
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
362
|
+
policy?: RepoMeshNodePolicy;
|
|
363
|
+
role?: RepoMeshDaemonRole;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export interface ApplyMeshHostJoinOptions {
|
|
367
|
+
token: string;
|
|
368
|
+
memberNode: MeshHostJoinMemberNodeInput;
|
|
369
|
+
memberMeshId?: string;
|
|
370
|
+
now?: string;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function applyMeshHostJoinRequest(
|
|
374
|
+
meshId: string,
|
|
375
|
+
opts: ApplyMeshHostJoinOptions,
|
|
376
|
+
): { accepted: true; mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; node: LocalMeshNodeEntry; tokenId: string } | { accepted: false; mesh?: LocalMeshEntry; meshHost?: RepoMeshHostMetadata; tokenId?: string; reason: string } | undefined {
|
|
377
|
+
const config = loadMeshConfig();
|
|
378
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
379
|
+
if (!mesh) return undefined;
|
|
380
|
+
const now = opts.now || new Date().toISOString();
|
|
381
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
382
|
+
if (previous.role === 'member') {
|
|
383
|
+
return { accepted: false, mesh, meshHost: previous, reason: 'Mesh Host daemon required to accept join requests' };
|
|
384
|
+
}
|
|
385
|
+
const meshHost: RepoMeshHostMetadata = { ...previous, role: 'host' };
|
|
386
|
+
const validation = assertPairingTokenValid(meshHost.pairing, opts.token, now);
|
|
387
|
+
if (!validation.ok) {
|
|
388
|
+
mesh.meshHost = {
|
|
389
|
+
...meshHost,
|
|
390
|
+
pairing: {
|
|
391
|
+
...(meshHost.pairing || { status: 'not_configured' as const }),
|
|
392
|
+
status: 'rejected',
|
|
393
|
+
lastRejectedAt: now,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
mesh.updatedAt = now;
|
|
397
|
+
saveMeshConfig(config);
|
|
398
|
+
return { accepted: false, mesh, meshHost: mesh.meshHost, tokenId: validation.presentedTokenId, reason: validation.reason };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const workspace = opts.memberNode.workspace.trim();
|
|
402
|
+
if (!workspace) throw new Error('memberNode.workspace required');
|
|
403
|
+
const memberId = opts.memberNode.id?.trim();
|
|
404
|
+
let node = mesh.nodes.find(n => (memberId && n.id === memberId) || n.workspace === workspace);
|
|
405
|
+
if (node) {
|
|
406
|
+
node.workspace = workspace;
|
|
407
|
+
node.repoRoot = opts.memberNode.repoRoot;
|
|
408
|
+
node.daemonId = opts.memberNode.daemonId;
|
|
409
|
+
node.machineId = opts.memberNode.machineId;
|
|
410
|
+
node.userOverrides = opts.memberNode.userOverrides || node.userOverrides || {};
|
|
411
|
+
node.policy = { ...(node.policy || {}), ...(opts.memberNode.policy || {}) };
|
|
412
|
+
node.role = 'member';
|
|
413
|
+
} else {
|
|
414
|
+
if (mesh.nodes.length >= 10) throw new Error('Maximum 10 nodes per mesh');
|
|
415
|
+
node = {
|
|
416
|
+
id: memberId || `node_${randomUUID().replace(/-/g, '')}`,
|
|
417
|
+
workspace,
|
|
418
|
+
repoRoot: opts.memberNode.repoRoot,
|
|
419
|
+
daemonId: opts.memberNode.daemonId,
|
|
420
|
+
machineId: opts.memberNode.machineId,
|
|
421
|
+
userOverrides: opts.memberNode.userOverrides || {},
|
|
422
|
+
policy: opts.memberNode.policy || {},
|
|
423
|
+
role: 'member',
|
|
424
|
+
};
|
|
425
|
+
mesh.nodes.push(node);
|
|
426
|
+
}
|
|
427
|
+
mesh.meshHost = {
|
|
428
|
+
...meshHost,
|
|
429
|
+
pairing: {
|
|
430
|
+
...(meshHost.pairing || {}),
|
|
431
|
+
status: 'paired',
|
|
432
|
+
tokenId: validation.tokenId,
|
|
433
|
+
joinedAt: now,
|
|
434
|
+
lastPairedAt: meshHost.pairing?.lastPairedAt || now,
|
|
435
|
+
...(meshHost.pairing?.expiresAt ? { expiresAt: meshHost.pairing.expiresAt } : {}),
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
mesh.updatedAt = now;
|
|
439
|
+
saveMeshConfig(config);
|
|
440
|
+
return { accepted: true, mesh, meshHost: mesh.meshHost, node, tokenId: validation.tokenId };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function markMeshHostPairingJoined(
|
|
444
|
+
meshId: string,
|
|
445
|
+
opts: { hostDaemonId?: string; hostNodeId?: string; joinedAt?: string; token?: string; tokenId?: string },
|
|
446
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata } | undefined {
|
|
447
|
+
const config = loadMeshConfig();
|
|
448
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
449
|
+
if (!mesh) return undefined;
|
|
450
|
+
const now = opts.joinedAt || new Date().toISOString();
|
|
451
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
452
|
+
const tokenId = opts.tokenId || (opts.token ? tokenIdForManualPairing(opts.token) : previous.pairing?.tokenId);
|
|
453
|
+
mesh.meshHost = {
|
|
454
|
+
...previous,
|
|
455
|
+
role: 'member',
|
|
456
|
+
...(opts.hostDaemonId ? { hostDaemonId: opts.hostDaemonId } : {}),
|
|
457
|
+
...(opts.hostNodeId ? { hostNodeId: opts.hostNodeId } : {}),
|
|
458
|
+
pairing: {
|
|
459
|
+
...(previous.pairing || {}),
|
|
460
|
+
status: 'paired',
|
|
461
|
+
...(tokenId ? { tokenId } : {}),
|
|
462
|
+
joinedAt: now,
|
|
463
|
+
lastPairedAt: previous.pairing?.lastPairedAt || now,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
mesh.updatedAt = now;
|
|
467
|
+
saveMeshConfig(config);
|
|
468
|
+
return { mesh, meshHost: mesh.meshHost };
|
|
469
|
+
}
|
|
470
|
+
|
|
177
471
|
// ─── Node Operations ────────────────────────────
|
|
178
472
|
|
|
179
473
|
export interface AddNodeOptions {
|
|
@@ -181,11 +475,14 @@ export interface AddNodeOptions {
|
|
|
181
475
|
repoRoot?: string;
|
|
182
476
|
daemonId?: string;
|
|
183
477
|
machineId?: string;
|
|
478
|
+
capabilities?: string[];
|
|
184
479
|
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
185
480
|
policy?: RepoMeshNodePolicy;
|
|
186
481
|
isLocalWorktree?: boolean;
|
|
187
482
|
worktreeBranch?: string;
|
|
188
483
|
clonedFromNodeId?: string;
|
|
484
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
485
|
+
role?: RepoMeshDaemonRole;
|
|
189
486
|
}
|
|
190
487
|
|
|
191
488
|
export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntry | undefined {
|
|
@@ -208,11 +505,14 @@ export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntr
|
|
|
208
505
|
repoRoot: opts.repoRoot,
|
|
209
506
|
daemonId: opts.daemonId,
|
|
210
507
|
machineId: opts.machineId,
|
|
508
|
+
capabilities: normalizeCapabilityTags(opts.capabilities),
|
|
211
509
|
userOverrides: opts.userOverrides || {},
|
|
212
510
|
policy: opts.policy || {},
|
|
213
511
|
isLocalWorktree: opts.isLocalWorktree,
|
|
214
512
|
worktreeBranch: opts.worktreeBranch,
|
|
215
513
|
clonedFromNodeId: opts.clonedFromNodeId,
|
|
514
|
+
worktreeBootstrap: opts.worktreeBootstrap,
|
|
515
|
+
role: opts.role,
|
|
216
516
|
};
|
|
217
517
|
|
|
218
518
|
mesh.nodes.push(node);
|
|
@@ -238,7 +538,14 @@ export function removeNode(meshId: string, nodeId: string): boolean {
|
|
|
238
538
|
export function updateNode(
|
|
239
539
|
meshId: string,
|
|
240
540
|
nodeId: string,
|
|
241
|
-
opts: {
|
|
541
|
+
opts: {
|
|
542
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
543
|
+
policy?: RepoMeshNodePolicy;
|
|
544
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
545
|
+
/** Per-node instruction surfaced in the coordinator prompt. Pass an
|
|
546
|
+
* empty string or undefined to clear it. */
|
|
547
|
+
systemPrompt?: string;
|
|
548
|
+
},
|
|
242
549
|
): LocalMeshNodeEntry | undefined {
|
|
243
550
|
const config = loadMeshConfig();
|
|
244
551
|
const mesh = config.meshes.find(m => m.id === meshId);
|
|
@@ -249,6 +556,15 @@ export function updateNode(
|
|
|
249
556
|
|
|
250
557
|
if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
|
|
251
558
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
559
|
+
if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
|
|
560
|
+
if (Object.prototype.hasOwnProperty.call(opts, 'systemPrompt')) {
|
|
561
|
+
// Honor explicit clears: { systemPrompt: undefined } drops the field.
|
|
562
|
+
if (opts.systemPrompt && opts.systemPrompt.trim()) {
|
|
563
|
+
node.systemPrompt = opts.systemPrompt;
|
|
564
|
+
} else {
|
|
565
|
+
delete node.systemPrompt;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
252
568
|
mesh.updatedAt = new Date().toISOString();
|
|
253
569
|
saveMeshConfig(config);
|
|
254
570
|
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}` });
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* Migrated from @adhdev/core — this is now the single source of truth.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
import { existsSync, statSync } from 'fs';
|
|
12
14
|
import { platform, homedir } from 'os';
|
|
13
15
|
import * as path from 'path';
|
|
14
16
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
@@ -73,25 +75,33 @@ function findCliCommand(command: string): string | null {
|
|
|
73
75
|
const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(candidate);
|
|
74
76
|
return existsSync(resolved) ? resolved : null;
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const isWin = platform() === 'win32';
|
|
79
|
+
const paths = (process.env.PATH || '').split(isWin ? ';' : ':');
|
|
80
|
+
const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
81
|
+
for (const p of paths) {
|
|
82
|
+
if (!p) continue;
|
|
83
|
+
for (const ext of exes) {
|
|
84
|
+
const fullPath = path.join(p, trimmed + ext);
|
|
85
|
+
try {
|
|
86
|
+
if (existsSync(fullPath)) {
|
|
87
|
+
const stat = statSync(fullPath);
|
|
88
|
+
if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
|
|
89
|
+
return fullPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch { }
|
|
93
|
+
}
|
|
84
94
|
}
|
|
95
|
+
return null;
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
function getIdeVersion(cliCommand: string): string | null {
|
|
98
|
+
async function getIdeVersion(cliCommand: string): Promise<string | null> {
|
|
88
99
|
try {
|
|
89
|
-
const
|
|
100
|
+
const { stdout } = await execAsync(`"${cliCommand}" --version`, {
|
|
90
101
|
encoding: 'utf-8',
|
|
91
102
|
timeout: 10000,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return result.split('\n')[0] || null;
|
|
103
|
+
});
|
|
104
|
+
return stdout.trim().split('\n')[0] || null;
|
|
95
105
|
} catch {
|
|
96
106
|
return null;
|
|
97
107
|
}
|
|
@@ -152,7 +162,7 @@ export async function detectIDEs(providerLoader?: ProviderLoader): Promise<IDEIn
|
|
|
152
162
|
const installed = os === 'darwin'
|
|
153
163
|
? !!(resolvedCli || appPath)
|
|
154
164
|
: !!resolvedCli;
|
|
155
|
-
const version = resolvedCli ? getIdeVersion(resolvedCli) : null;
|
|
165
|
+
const version = resolvedCli ? await getIdeVersion(resolvedCli) : null;
|
|
156
166
|
|
|
157
167
|
results.push({
|
|
158
168
|
id: def.id,
|