@adhdev/daemon-core 0.9.82-rc.31 → 0.9.82-rc.310
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/daemon-lifecycle.d.ts +8 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +23 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +38 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +14 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +210 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +9 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +9 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/index.d.ts +51 -12
- package/dist/index.js +38320 -16024
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38009 -15817
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +108 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +138 -0
- package/dist/mesh/mesh-events-pending.d.ts +45 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +47 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-init.d.ts +86 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +116 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +7 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +377 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
- package/dist/mesh/mesh-work-queue.d.ts +181 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +215 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
- package/dist/providers/acp-provider-instance.d.ts +2 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +61 -3
- package/dist/providers/contracts.d.ts +130 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +13 -0
- package/dist/providers/provider-instance.d.ts +13 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +59 -0
- package/dist/providers/spec/cli-adapter.d.ts +186 -0
- package/dist/providers/spec/evaluator.d.ts +23 -0
- package/dist/providers/spec/fsm-driver.d.ts +278 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +72 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +128 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +144 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/repo-mesh-types.d.ts +203 -13
- package/dist/sessions/registry.d.ts +6 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +42 -1
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +55 -12
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1215 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +674 -1139
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +68 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +45 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands.ts +1813 -60
- package/src/commands/cli-manager.ts +286 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +5911 -595
- package/src/commands/upgrade-helper.ts +29 -0
- package/src/config/chat-history.ts +474 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +303 -3
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +42 -12
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +302 -18
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +131 -11
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +234 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +469 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +1786 -0
- package/src/mesh/mesh-events-pending.ts +508 -0
- package/src/mesh/mesh-events-stale.ts +290 -0
- package/src/mesh/mesh-events-utils.ts +254 -0
- package/src/mesh/mesh-events.ts +30 -1035
- package/src/mesh/mesh-fast-forward.ts +839 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-init.ts +260 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +589 -102
- package/src/mesh/mesh-missions.ts +248 -0
- package/src/mesh/mesh-reconcile-loop.ts +466 -0
- package/src/mesh/mesh-refine-batch.ts +197 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +1513 -0
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
- package/src/mesh/mesh-work-queue.ts +604 -145
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +403 -0
- package/src/mesh/worktree-bootstrap-config.ts +326 -0
- package/src/providers/acp-provider-instance.ts +15 -1
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +953 -87
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/extension-provider-instance.ts +5 -1
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +25 -6
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +584 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +155 -0
- package/src/providers/spec/cli-adapter.ts +838 -0
- package/src/providers/spec/evaluator.ts +345 -0
- package/src/providers/spec/fsm-driver.ts +875 -0
- package/src/providers/spec/fsm-evaluator.ts +255 -0
- package/src/providers/spec/fsm-loader.ts +112 -0
- package/src/providers/spec/fsm-types.ts +222 -0
- package/src/providers/spec/native-history-executor.ts +943 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +189 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +257 -13
- package/src/sessions/registry.ts +6 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +26 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -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
24
|
import { DEFAULT_MESH_POLICY } 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,12 +95,23 @@ 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
|
}
|
|
@@ -93,6 +121,18 @@ function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMe
|
|
|
93
121
|
return policy;
|
|
94
122
|
}
|
|
95
123
|
|
|
124
|
+
function normalizeAutoFastForwardPolicy(value: unknown): NonNullable<RepoMeshPolicy['autoFastForward']> {
|
|
125
|
+
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
126
|
+
? value as Record<string, unknown>
|
|
127
|
+
: {};
|
|
128
|
+
const maxBehind = Number(record.maxBehind);
|
|
129
|
+
return {
|
|
130
|
+
enabled: record.enabled !== false,
|
|
131
|
+
...(Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {}),
|
|
132
|
+
requireCleanSubmodules: record.requireCleanSubmodules !== false,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
96
136
|
export function listMeshes(): LocalMeshEntry[] {
|
|
97
137
|
return loadMeshConfig().meshes;
|
|
98
138
|
}
|
|
@@ -112,6 +152,7 @@ export interface CreateMeshOptions {
|
|
|
112
152
|
defaultBranch?: string;
|
|
113
153
|
policy?: Partial<RepoMeshPolicy>;
|
|
114
154
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
155
|
+
meshHost?: RepoMeshHostMetadata;
|
|
115
156
|
}
|
|
116
157
|
|
|
117
158
|
export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
@@ -133,6 +174,7 @@ export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
|
133
174
|
defaultBranch: opts.defaultBranch,
|
|
134
175
|
policy: mergeMeshPolicy(undefined, opts.policy),
|
|
135
176
|
coordinator: opts.coordinator || {},
|
|
177
|
+
meshHost: opts.meshHost || createDefaultMeshHostMetadata(),
|
|
136
178
|
nodes: [],
|
|
137
179
|
createdAt: now,
|
|
138
180
|
updatedAt: now,
|
|
@@ -148,6 +190,7 @@ export interface UpdateMeshOptions {
|
|
|
148
190
|
defaultBranch?: string;
|
|
149
191
|
policy?: Partial<RepoMeshPolicy>;
|
|
150
192
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
193
|
+
meshHost?: RepoMeshHostMetadata;
|
|
151
194
|
}
|
|
152
195
|
|
|
153
196
|
export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEntry | undefined {
|
|
@@ -159,6 +202,7 @@ export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEn
|
|
|
159
202
|
if (opts.defaultBranch !== undefined) mesh.defaultBranch = opts.defaultBranch;
|
|
160
203
|
if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
|
|
161
204
|
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
205
|
+
if (opts.meshHost) mesh.meshHost = opts.meshHost;
|
|
162
206
|
mesh.updatedAt = new Date().toISOString();
|
|
163
207
|
|
|
164
208
|
saveMeshConfig(config);
|
|
@@ -174,6 +218,240 @@ export function deleteMesh(meshId: string): boolean {
|
|
|
174
218
|
return true;
|
|
175
219
|
}
|
|
176
220
|
|
|
221
|
+
function normalizeManualHostAddress(hostAddress: string): string {
|
|
222
|
+
const normalized = hostAddress.trim().replace(/\/+$/, '');
|
|
223
|
+
if (!normalized) throw new Error('hostAddress required');
|
|
224
|
+
let parsed: URL;
|
|
225
|
+
try {
|
|
226
|
+
parsed = new URL(normalized);
|
|
227
|
+
} catch {
|
|
228
|
+
throw new Error('hostAddress must be a valid http(s) or ws(s) URL');
|
|
229
|
+
}
|
|
230
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
|
|
231
|
+
throw new Error('hostAddress must use http, https, ws, or wss');
|
|
232
|
+
}
|
|
233
|
+
return normalized;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function tokenIdForManualPairing(token: string): string {
|
|
237
|
+
return `tok_${createHash('sha256').update(token).digest('hex').slice(0, 16)}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function normalizeTokenExpiry(value: unknown): string | undefined {
|
|
241
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
242
|
+
const date = new Date(value);
|
|
243
|
+
if (Number.isNaN(date.getTime())) throw new Error('expiresAt must be a valid ISO date');
|
|
244
|
+
return date.toISOString();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function assertPairingTokenValid(pairing: RepoMeshHostMetadata['pairing'], rawToken: string, nowIso: string): { ok: true; tokenId: string } | { ok: false; reason: string; expectedTokenId?: string; presentedTokenId?: string } {
|
|
248
|
+
const token = rawToken.trim();
|
|
249
|
+
if (!token) return { ok: false, reason: 'token required' };
|
|
250
|
+
const presentedTokenId = tokenIdForManualPairing(token);
|
|
251
|
+
const expectedTokenId = pairing?.tokenId;
|
|
252
|
+
if (!expectedTokenId || pairing?.status === 'not_configured' || pairing?.status === 'revoked') {
|
|
253
|
+
return { ok: false, reason: 'host pairing token is not configured', presentedTokenId };
|
|
254
|
+
}
|
|
255
|
+
if (pairing.expiresAt && new Date(pairing.expiresAt).getTime() <= new Date(nowIso).getTime()) {
|
|
256
|
+
return { ok: false, reason: 'host pairing token expired', expectedTokenId, presentedTokenId };
|
|
257
|
+
}
|
|
258
|
+
if (presentedTokenId !== expectedTokenId) {
|
|
259
|
+
return { ok: false, reason: 'invalid pairing token', expectedTokenId, presentedTokenId };
|
|
260
|
+
}
|
|
261
|
+
return { ok: true, tokenId: presentedTokenId };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface ConfigureMeshHostPairingOptions {
|
|
265
|
+
hostAddress: string;
|
|
266
|
+
token: string;
|
|
267
|
+
now?: string;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function configureMeshHostPairing(
|
|
271
|
+
meshId: string,
|
|
272
|
+
opts: ConfigureMeshHostPairingOptions,
|
|
273
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; hostAddress: string } | undefined {
|
|
274
|
+
const hostAddress = normalizeManualHostAddress(opts.hostAddress);
|
|
275
|
+
const token = opts.token.trim();
|
|
276
|
+
if (!token) throw new Error('token required');
|
|
277
|
+
|
|
278
|
+
const config = loadMeshConfig();
|
|
279
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
280
|
+
if (!mesh) return undefined;
|
|
281
|
+
|
|
282
|
+
const now = opts.now || new Date().toISOString();
|
|
283
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
284
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
285
|
+
...previous,
|
|
286
|
+
role: 'member',
|
|
287
|
+
hostAddress,
|
|
288
|
+
pairing: {
|
|
289
|
+
status: 'pairing',
|
|
290
|
+
tokenId: tokenIdForManualPairing(token),
|
|
291
|
+
lastPairedAt: now,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
mesh.meshHost = meshHost;
|
|
296
|
+
mesh.updatedAt = now;
|
|
297
|
+
saveMeshConfig(config);
|
|
298
|
+
return { mesh, meshHost, hostAddress };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface CreateMeshHostPairingTokenOptions {
|
|
302
|
+
token?: string;
|
|
303
|
+
expiresAt?: string;
|
|
304
|
+
now?: string;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function createMeshHostPairingToken(
|
|
308
|
+
meshId: string,
|
|
309
|
+
opts: CreateMeshHostPairingTokenOptions = {},
|
|
310
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; token: string; tokenId: string; expiresAt?: string } | undefined {
|
|
311
|
+
const config = loadMeshConfig();
|
|
312
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
313
|
+
if (!mesh) return undefined;
|
|
314
|
+
const now = opts.now || new Date().toISOString();
|
|
315
|
+
const token = (opts.token || `mhj_${randomBytes(24).toString('base64url')}`).trim();
|
|
316
|
+
if (!token) throw new Error('token required');
|
|
317
|
+
const tokenId = tokenIdForManualPairing(token);
|
|
318
|
+
const expiresAt = normalizeTokenExpiry(opts.expiresAt);
|
|
319
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
320
|
+
if (previous.role === 'member') {
|
|
321
|
+
throw new Error('Mesh Host daemon required to create host pairing tokens; member daemons cannot mint host join tokens.');
|
|
322
|
+
}
|
|
323
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
324
|
+
...previous,
|
|
325
|
+
role: 'host',
|
|
326
|
+
pairing: {
|
|
327
|
+
status: 'pairing',
|
|
328
|
+
tokenId,
|
|
329
|
+
lastPairedAt: now,
|
|
330
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
mesh.meshHost = meshHost;
|
|
334
|
+
mesh.updatedAt = now;
|
|
335
|
+
saveMeshConfig(config);
|
|
336
|
+
return { mesh, meshHost, token, tokenId, ...(expiresAt ? { expiresAt } : {}) };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export interface MeshHostJoinMemberNodeInput {
|
|
340
|
+
id?: string;
|
|
341
|
+
workspace: string;
|
|
342
|
+
repoRoot?: string;
|
|
343
|
+
daemonId?: string;
|
|
344
|
+
machineId?: string;
|
|
345
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
346
|
+
policy?: RepoMeshNodePolicy;
|
|
347
|
+
role?: RepoMeshDaemonRole;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface ApplyMeshHostJoinOptions {
|
|
351
|
+
token: string;
|
|
352
|
+
memberNode: MeshHostJoinMemberNodeInput;
|
|
353
|
+
memberMeshId?: string;
|
|
354
|
+
now?: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function applyMeshHostJoinRequest(
|
|
358
|
+
meshId: string,
|
|
359
|
+
opts: ApplyMeshHostJoinOptions,
|
|
360
|
+
): { accepted: true; mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; node: LocalMeshNodeEntry; tokenId: string } | { accepted: false; mesh?: LocalMeshEntry; meshHost?: RepoMeshHostMetadata; tokenId?: string; reason: string } | undefined {
|
|
361
|
+
const config = loadMeshConfig();
|
|
362
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
363
|
+
if (!mesh) return undefined;
|
|
364
|
+
const now = opts.now || new Date().toISOString();
|
|
365
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
366
|
+
if (previous.role === 'member') {
|
|
367
|
+
return { accepted: false, mesh, meshHost: previous, reason: 'Mesh Host daemon required to accept join requests' };
|
|
368
|
+
}
|
|
369
|
+
const meshHost: RepoMeshHostMetadata = { ...previous, role: 'host' };
|
|
370
|
+
const validation = assertPairingTokenValid(meshHost.pairing, opts.token, now);
|
|
371
|
+
if (!validation.ok) {
|
|
372
|
+
mesh.meshHost = {
|
|
373
|
+
...meshHost,
|
|
374
|
+
pairing: {
|
|
375
|
+
...(meshHost.pairing || { status: 'not_configured' as const }),
|
|
376
|
+
status: 'rejected',
|
|
377
|
+
lastRejectedAt: now,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
mesh.updatedAt = now;
|
|
381
|
+
saveMeshConfig(config);
|
|
382
|
+
return { accepted: false, mesh, meshHost: mesh.meshHost, tokenId: validation.presentedTokenId, reason: validation.reason };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const workspace = opts.memberNode.workspace.trim();
|
|
386
|
+
if (!workspace) throw new Error('memberNode.workspace required');
|
|
387
|
+
const memberId = opts.memberNode.id?.trim();
|
|
388
|
+
let node = mesh.nodes.find(n => (memberId && n.id === memberId) || n.workspace === workspace);
|
|
389
|
+
if (node) {
|
|
390
|
+
node.workspace = workspace;
|
|
391
|
+
node.repoRoot = opts.memberNode.repoRoot;
|
|
392
|
+
node.daemonId = opts.memberNode.daemonId;
|
|
393
|
+
node.machineId = opts.memberNode.machineId;
|
|
394
|
+
node.userOverrides = opts.memberNode.userOverrides || node.userOverrides || {};
|
|
395
|
+
node.policy = { ...(node.policy || {}), ...(opts.memberNode.policy || {}) };
|
|
396
|
+
node.role = 'member';
|
|
397
|
+
} else {
|
|
398
|
+
if (mesh.nodes.length >= 10) throw new Error('Maximum 10 nodes per mesh');
|
|
399
|
+
node = {
|
|
400
|
+
id: memberId || `node_${randomUUID().replace(/-/g, '')}`,
|
|
401
|
+
workspace,
|
|
402
|
+
repoRoot: opts.memberNode.repoRoot,
|
|
403
|
+
daemonId: opts.memberNode.daemonId,
|
|
404
|
+
machineId: opts.memberNode.machineId,
|
|
405
|
+
userOverrides: opts.memberNode.userOverrides || {},
|
|
406
|
+
policy: opts.memberNode.policy || {},
|
|
407
|
+
role: 'member',
|
|
408
|
+
};
|
|
409
|
+
mesh.nodes.push(node);
|
|
410
|
+
}
|
|
411
|
+
mesh.meshHost = {
|
|
412
|
+
...meshHost,
|
|
413
|
+
pairing: {
|
|
414
|
+
...(meshHost.pairing || {}),
|
|
415
|
+
status: 'paired',
|
|
416
|
+
tokenId: validation.tokenId,
|
|
417
|
+
joinedAt: now,
|
|
418
|
+
lastPairedAt: meshHost.pairing?.lastPairedAt || now,
|
|
419
|
+
...(meshHost.pairing?.expiresAt ? { expiresAt: meshHost.pairing.expiresAt } : {}),
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
mesh.updatedAt = now;
|
|
423
|
+
saveMeshConfig(config);
|
|
424
|
+
return { accepted: true, mesh, meshHost: mesh.meshHost, node, tokenId: validation.tokenId };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function markMeshHostPairingJoined(
|
|
428
|
+
meshId: string,
|
|
429
|
+
opts: { hostDaemonId?: string; hostNodeId?: string; joinedAt?: string; token?: string; tokenId?: string },
|
|
430
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata } | undefined {
|
|
431
|
+
const config = loadMeshConfig();
|
|
432
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
433
|
+
if (!mesh) return undefined;
|
|
434
|
+
const now = opts.joinedAt || new Date().toISOString();
|
|
435
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
436
|
+
const tokenId = opts.tokenId || (opts.token ? tokenIdForManualPairing(opts.token) : previous.pairing?.tokenId);
|
|
437
|
+
mesh.meshHost = {
|
|
438
|
+
...previous,
|
|
439
|
+
role: 'member',
|
|
440
|
+
...(opts.hostDaemonId ? { hostDaemonId: opts.hostDaemonId } : {}),
|
|
441
|
+
...(opts.hostNodeId ? { hostNodeId: opts.hostNodeId } : {}),
|
|
442
|
+
pairing: {
|
|
443
|
+
...(previous.pairing || {}),
|
|
444
|
+
status: 'paired',
|
|
445
|
+
...(tokenId ? { tokenId } : {}),
|
|
446
|
+
joinedAt: now,
|
|
447
|
+
lastPairedAt: previous.pairing?.lastPairedAt || now,
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
mesh.updatedAt = now;
|
|
451
|
+
saveMeshConfig(config);
|
|
452
|
+
return { mesh, meshHost: mesh.meshHost };
|
|
453
|
+
}
|
|
454
|
+
|
|
177
455
|
// ─── Node Operations ────────────────────────────
|
|
178
456
|
|
|
179
457
|
export interface AddNodeOptions {
|
|
@@ -181,11 +459,14 @@ export interface AddNodeOptions {
|
|
|
181
459
|
repoRoot?: string;
|
|
182
460
|
daemonId?: string;
|
|
183
461
|
machineId?: string;
|
|
462
|
+
capabilities?: string[];
|
|
184
463
|
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
185
464
|
policy?: RepoMeshNodePolicy;
|
|
186
465
|
isLocalWorktree?: boolean;
|
|
187
466
|
worktreeBranch?: string;
|
|
188
467
|
clonedFromNodeId?: string;
|
|
468
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
469
|
+
role?: RepoMeshDaemonRole;
|
|
189
470
|
}
|
|
190
471
|
|
|
191
472
|
export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntry | undefined {
|
|
@@ -208,11 +489,14 @@ export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntr
|
|
|
208
489
|
repoRoot: opts.repoRoot,
|
|
209
490
|
daemonId: opts.daemonId,
|
|
210
491
|
machineId: opts.machineId,
|
|
492
|
+
capabilities: normalizeCapabilityTags(opts.capabilities),
|
|
211
493
|
userOverrides: opts.userOverrides || {},
|
|
212
494
|
policy: opts.policy || {},
|
|
213
495
|
isLocalWorktree: opts.isLocalWorktree,
|
|
214
496
|
worktreeBranch: opts.worktreeBranch,
|
|
215
497
|
clonedFromNodeId: opts.clonedFromNodeId,
|
|
498
|
+
worktreeBootstrap: opts.worktreeBootstrap,
|
|
499
|
+
role: opts.role,
|
|
216
500
|
};
|
|
217
501
|
|
|
218
502
|
mesh.nodes.push(node);
|
|
@@ -238,7 +522,14 @@ export function removeNode(meshId: string, nodeId: string): boolean {
|
|
|
238
522
|
export function updateNode(
|
|
239
523
|
meshId: string,
|
|
240
524
|
nodeId: string,
|
|
241
|
-
opts: {
|
|
525
|
+
opts: {
|
|
526
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
527
|
+
policy?: RepoMeshNodePolicy;
|
|
528
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
529
|
+
/** Per-node instruction surfaced in the coordinator prompt. Pass an
|
|
530
|
+
* empty string or undefined to clear it. */
|
|
531
|
+
systemPrompt?: string;
|
|
532
|
+
},
|
|
242
533
|
): LocalMeshNodeEntry | undefined {
|
|
243
534
|
const config = loadMeshConfig();
|
|
244
535
|
const mesh = config.meshes.find(m => m.id === meshId);
|
|
@@ -249,6 +540,15 @@ export function updateNode(
|
|
|
249
540
|
|
|
250
541
|
if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
|
|
251
542
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
543
|
+
if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
|
|
544
|
+
if (Object.prototype.hasOwnProperty.call(opts, 'systemPrompt')) {
|
|
545
|
+
// Honor explicit clears: { systemPrompt: undefined } drops the field.
|
|
546
|
+
if (opts.systemPrompt && opts.systemPrompt.trim()) {
|
|
547
|
+
node.systemPrompt = opts.systemPrompt;
|
|
548
|
+
} else {
|
|
549
|
+
delete node.systemPrompt;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
252
552
|
mesh.updatedAt = new Date().toISOString();
|
|
253
553
|
saveMeshConfig(config);
|
|
254
554
|
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,
|
package/src/git/git-commands.ts
CHANGED
|
@@ -42,8 +42,12 @@ export interface GitLogResult extends GitRepoIdentity {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface GitCheckpointResult extends GitRepoIdentity {
|
|
45
|
-
commit
|
|
45
|
+
commit?: string;
|
|
46
46
|
message: string;
|
|
47
|
+
status?: 'created' | 'skipped';
|
|
48
|
+
skipped?: boolean;
|
|
49
|
+
noop?: boolean;
|
|
50
|
+
reason?: 'nothing_to_commit';
|
|
47
51
|
lastCheckedAt: number;
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -62,9 +66,9 @@ export interface GitPushResult extends GitRepoIdentity {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
export interface GitCommandServices {
|
|
65
|
-
getStatus?: (params: { workspace: string; refreshUpstream?: boolean }) => Promise<GitRepoStatus> | GitRepoStatus;
|
|
66
|
-
getDiffSummary?: (params: { workspace: string; staged?: boolean }) => Promise<GitDiffSummary> | GitDiffSummary;
|
|
67
|
-
getDiffFile?: (params: { workspace: string; path: string; staged?: boolean }) => Promise<GitFileDiff> | GitFileDiff;
|
|
69
|
+
getStatus?: (params: { workspace: string; refreshUpstream?: boolean; includeSubmodules?: boolean; submoduleIgnorePaths?: string[] }) => Promise<GitRepoStatus> | GitRepoStatus;
|
|
70
|
+
getDiffSummary?: (params: { workspace: string; staged?: boolean; base?: string }) => Promise<GitDiffSummary> | GitDiffSummary;
|
|
71
|
+
getDiffFile?: (params: { workspace: string; path: string; staged?: boolean; base?: string }) => Promise<GitFileDiff> | GitFileDiff;
|
|
68
72
|
createSnapshot?: (params: {
|
|
69
73
|
workspace: string;
|
|
70
74
|
reason: GitSnapshotReason;
|
|
@@ -172,8 +176,8 @@ const defaultSnapshotStore = createGitSnapshotStore({
|
|
|
172
176
|
export function createDefaultGitCommandServices(): GitCommandServices {
|
|
173
177
|
return {
|
|
174
178
|
getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
|
|
175
|
-
getDiffSummary: ({ workspace }) => getGitDiffSummary(workspace),
|
|
176
|
-
getDiffFile: ({ workspace, path: filePath }) => getGitFileDiff(workspace, filePath),
|
|
179
|
+
getDiffSummary: ({ workspace, base }) => getGitDiffSummary(workspace, base ? { baseRef: base } : {}),
|
|
180
|
+
getDiffFile: ({ workspace, path: filePath, base }) => getGitFileDiff(workspace, filePath, base ? { baseRef: base } : {}),
|
|
177
181
|
createSnapshot: ({ workspace, reason, sessionId, turnId }) => defaultSnapshotStore.create({
|
|
178
182
|
workspace,
|
|
179
183
|
reason,
|
|
@@ -290,13 +294,22 @@ export async function handleGitCommand(
|
|
|
290
294
|
switch (command) {
|
|
291
295
|
case 'git_status': {
|
|
292
296
|
if (!services.getStatus) return serviceNotImplemented(command);
|
|
293
|
-
const
|
|
297
|
+
const submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
|
|
298
|
+
? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string' && value.trim().length > 0)
|
|
299
|
+
: undefined;
|
|
300
|
+
const statusParams: { workspace: string; refreshUpstream?: boolean; includeSubmodules?: boolean; submoduleIgnorePaths?: string[] } = { workspace };
|
|
301
|
+
const refreshUpstream = optionalBoolean(args?.refreshUpstream);
|
|
302
|
+
const includeSubmodules = optionalBoolean(args?.includeSubmodules);
|
|
303
|
+
if (refreshUpstream !== undefined) statusParams.refreshUpstream = refreshUpstream;
|
|
304
|
+
if (includeSubmodules !== undefined) statusParams.includeSubmodules = includeSubmodules;
|
|
305
|
+
if (submoduleIgnorePaths && submoduleIgnorePaths.length > 0) statusParams.submoduleIgnorePaths = submoduleIgnorePaths;
|
|
306
|
+
const status = await runService(() => services.getStatus!(statusParams));
|
|
294
307
|
return 'success' in status ? status : { success: true, status };
|
|
295
308
|
}
|
|
296
309
|
|
|
297
310
|
case 'git_diff_summary': {
|
|
298
311
|
if (!services.getDiffSummary) return serviceNotImplemented(command);
|
|
299
|
-
const diffSummary = await runService(() => services.getDiffSummary!({ workspace, staged: optionalBoolean(args?.staged) }));
|
|
312
|
+
const diffSummary = await runService(() => services.getDiffSummary!({ workspace, staged: optionalBoolean(args?.staged), base: optionalString(args?.base) }));
|
|
300
313
|
return 'success' in diffSummary ? diffSummary : { success: true, diffSummary };
|
|
301
314
|
}
|
|
302
315
|
|
|
@@ -442,6 +455,15 @@ async function gitCheckpoint(
|
|
|
442
455
|
if (statusResult.hasConflicts) {
|
|
443
456
|
throw new GitCommandError('conflict', 'Repository has conflicts — resolve before checkpointing');
|
|
444
457
|
}
|
|
458
|
+
const dirtySubmodules = (statusResult.submodules || []).filter(submodule => submodule.dirty);
|
|
459
|
+
if (dirtySubmodules.length > 0) {
|
|
460
|
+
const paths = dirtySubmodules.map(submodule => submodule.path).join(', ');
|
|
461
|
+
throw new GitCommandError(
|
|
462
|
+
'dirty_index_required',
|
|
463
|
+
`Repository has dirty submodules that must be checkpointed first: ${paths}. ` +
|
|
464
|
+
'Checkpoint or commit each dirty submodule, then checkpoint this repository to record gitlink changes.',
|
|
465
|
+
);
|
|
466
|
+
}
|
|
445
467
|
|
|
446
468
|
const addArgs = includeUntracked ? ['-A'] : ['-u'];
|
|
447
469
|
await runGit(repo, ['add', ...addArgs], { cwd: repoRoot });
|
|
@@ -455,10 +477,17 @@ async function gitCheckpoint(
|
|
|
455
477
|
} catch (err: any) {
|
|
456
478
|
const output = (err?.stdout || '') + (err?.stderr || '');
|
|
457
479
|
if (/nothing to commit/i.test(output)) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
480
|
+
return {
|
|
481
|
+
workspace: repo.workspace,
|
|
482
|
+
repoRoot,
|
|
483
|
+
isGitRepo: true,
|
|
484
|
+
message: fullMsg,
|
|
485
|
+
status: 'skipped',
|
|
486
|
+
skipped: true,
|
|
487
|
+
noop: true,
|
|
488
|
+
reason: 'nothing_to_commit',
|
|
489
|
+
lastCheckedAt: Date.now(),
|
|
490
|
+
};
|
|
462
491
|
}
|
|
463
492
|
throw err;
|
|
464
493
|
}
|
|
@@ -469,6 +498,7 @@ async function gitCheckpoint(
|
|
|
469
498
|
isGitRepo: true,
|
|
470
499
|
commit: commitSha,
|
|
471
500
|
message: fullMsg,
|
|
501
|
+
status: 'created',
|
|
472
502
|
lastCheckedAt: Date.now(),
|
|
473
503
|
};
|
|
474
504
|
}
|