@adhdev/daemon-core 0.9.82-rc.35 → 0.9.82-rc.350
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/daemon-lifecycle.d.ts +8 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +46 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +116 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +53 -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 +324 -1
- package/dist/commands/upgrade-helper.d.ts +12 -0
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +77 -1
- package/dist/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- package/dist/git/change-impact-config.d.ts +159 -0
- package/dist/git/git-commands.d.ts +11 -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 +23 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +52 -13
- package/dist/index.js +39739 -14302
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41369 -16055
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +46 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +174 -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 +55 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +62 -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 +164 -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 +419 -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 +218 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +216 -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/chat-message-normalization.d.ts +12 -0
- package/dist/providers/cli-provider-instance.d.ts +77 -3
- package/dist/providers/contracts.d.ts +139 -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 +14 -0
- package/dist/providers/provider-instance.d.ts +15 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +69 -0
- package/dist/providers/spec/cli-adapter.d.ts +217 -0
- package/dist/providers/spec/evaluator.d.ts +23 -0
- package/dist/providers/spec/fsm-driver.d.ts +322 -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 +144 -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/status-monitor.d.ts +7 -7
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +295 -13
- package/dist/sessions/registry.d.ts +6 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +59 -2
- 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 +26 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/agent-stream/provider-adapter.ts +1 -1
- package/src/boot/daemon-lifecycle.ts +55 -12
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +47 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1215 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +774 -1141
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
- package/src/cli-adapters/provider-cli-shared.ts +100 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +90 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands.ts +1823 -62
- package/src/commands/cli-manager.ts +304 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +7505 -1311
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +81 -30
- package/src/config/chat-history.ts +483 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +388 -6
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/cli-detector.ts +28 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- package/src/git/change-impact-config.ts +354 -0
- package/src/git/git-commands.ts +55 -14
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +574 -43
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +8 -1
- package/src/git/index.ts +16 -0
- package/src/index.ts +146 -12
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +220 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +234 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +623 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +2224 -0
- package/src/mesh/mesh-events-pending.ts +558 -0
- package/src/mesh/mesh-events-stale.ts +290 -0
- package/src/mesh/mesh-events-utils.ts +381 -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 +590 -102
- package/src/mesh/mesh-missions.ts +322 -0
- package/src/mesh/mesh-reconcile-loop.ts +989 -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 +1660 -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 +901 -141
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +423 -0
- package/src/mesh/worktree-bootstrap-config.ts +331 -0
- package/src/providers/acp-provider-instance.ts +25 -9
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +45 -5
- package/src/providers/cli-provider-instance.ts +1041 -99
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +150 -6
- package/src/providers/extension-provider-instance.ts +12 -7
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +35 -12
- 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 +670 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +168 -0
- package/src/providers/spec/cli-adapter.ts +986 -0
- package/src/providers/spec/evaluator.ts +345 -0
- package/src/providers/spec/fsm-driver.ts +1065 -0
- package/src/providers/spec/fsm-evaluator.ts +255 -0
- package/src/providers/spec/fsm-loader.ts +120 -0
- package/src/providers/spec/fsm-types.ts +238 -0
- package/src/providers/spec/native-history-executor.ts +1136 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +226 -0
- package/src/providers/status-monitor.d.ts +7 -7
- package/src/providers/status-monitor.ts +37 -22
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +378 -14
- package/src/sessions/registry.ts +6 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.d.ts +8 -0
- package/src/shared-types.ts +64 -1
- package/src/status/builders.ts +26 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +19 -1
- package/src/status/snapshot.ts +95 -26
- package/src/system/host-memory.ts +29 -12
- package/src/system/load-better-sqlite3.ts +68 -0
- 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
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, resolve as pathResolve } from 'path';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import * as yaml from 'js-yaml';
|
|
7
|
+
import { resolveWin32Executable } from '../cli-adapters/resolve-executable.js';
|
|
8
|
+
import {
|
|
9
|
+
isMeshConfigRecord,
|
|
10
|
+
normalizeMeshCommandConfig,
|
|
11
|
+
type MeshRefineValidationCommandPlan,
|
|
12
|
+
type RepoMeshRefineValidationCommandConfig,
|
|
13
|
+
} from './refine-config.js';
|
|
14
|
+
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
15
|
+
|
|
16
|
+
export type WorktreeBootstrapStatus = 'ready' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
17
|
+
|
|
18
|
+
export interface RepoMeshWorktreeBootstrapConfig {
|
|
19
|
+
version: 1;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
runOnClone?: boolean;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
commands?: RepoMeshRefineValidationCommandConfig[];
|
|
24
|
+
staleInputs?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WorktreeBootstrapState extends MeshAsyncJobLifecycle {
|
|
28
|
+
status: WorktreeBootstrapStatus;
|
|
29
|
+
required: boolean;
|
|
30
|
+
configSource?: string;
|
|
31
|
+
configSourceType?: 'repo_file' | 'mesh_policy' | 'unavailable' | 'invalid';
|
|
32
|
+
lastCommand?: string;
|
|
33
|
+
exitCode?: number | null;
|
|
34
|
+
commandsRun?: Array<Record<string, unknown>>;
|
|
35
|
+
staleInputs?: string[];
|
|
36
|
+
/**
|
|
37
|
+
* M2-1: sha256 per staleInputs path recorded when the bootstrap reached
|
|
38
|
+
* 'ready'. evaluateWorktreeBootstrapState compares current file hashes
|
|
39
|
+
* against this to detect staleness (e.g. base merge changed a lockfile).
|
|
40
|
+
* Missing files hash to the literal 'absent'.
|
|
41
|
+
*/
|
|
42
|
+
staleInputsDigest?: Record<string, string>;
|
|
43
|
+
/** M2-1: why an evaluated state resolved to stale (digest_mismatch | never_ran). */
|
|
44
|
+
staleReason?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface WorktreeBootstrapConfigLoadResult {
|
|
48
|
+
config?: RepoMeshWorktreeBootstrapConfig;
|
|
49
|
+
source: string;
|
|
50
|
+
sourceType: 'repo_file' | 'mesh_policy' | 'unavailable' | 'invalid';
|
|
51
|
+
path?: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS = [
|
|
56
|
+
'.adhdev/worktree_bootstrap.json',
|
|
57
|
+
'.adhdev/worktree_bootstrap.yaml',
|
|
58
|
+
'.adhdev/worktree_bootstrap.yml',
|
|
59
|
+
'.adhdev/worktree-bootstrap.json',
|
|
60
|
+
'.adhdev/worktree-bootstrap.yaml',
|
|
61
|
+
'.adhdev/worktree-bootstrap.yml',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export const MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA = {
|
|
65
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
66
|
+
title: 'ADHDev Repo Mesh Worktree Bootstrap Config',
|
|
67
|
+
type: 'object',
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
required: ['version'],
|
|
70
|
+
properties: {
|
|
71
|
+
version: { const: 1 },
|
|
72
|
+
enabled: { type: 'boolean', default: true },
|
|
73
|
+
runOnClone: { type: 'boolean', default: true },
|
|
74
|
+
required: { type: 'boolean', default: true },
|
|
75
|
+
staleInputs: { type: 'array', maxItems: 16, items: { type: 'string', minLength: 1 } },
|
|
76
|
+
commands: {
|
|
77
|
+
type: 'array',
|
|
78
|
+
minItems: 1,
|
|
79
|
+
maxItems: 4,
|
|
80
|
+
items: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
required: ['command'],
|
|
84
|
+
properties: {
|
|
85
|
+
command: { type: 'string', minLength: 1 },
|
|
86
|
+
args: { type: 'array', items: { type: 'string' } },
|
|
87
|
+
category: { enum: ['typecheck', 'test', 'lint', 'build', 'custom'] },
|
|
88
|
+
cwd: { type: 'string' },
|
|
89
|
+
timeoutMs: { type: 'number', minimum: 1000, maximum: 600000 },
|
|
90
|
+
outputLimitBytes: { type: 'number', minimum: 1024, maximum: 1048576 },
|
|
91
|
+
env: { type: 'object', additionalProperties: { type: 'string' } },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
} as const;
|
|
97
|
+
|
|
98
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
99
|
+
const DEFAULT_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
100
|
+
const OUTPUT_SUMMARY_CHARS = 2_000;
|
|
101
|
+
|
|
102
|
+
function parseConfigText(path: string, text: string): unknown {
|
|
103
|
+
if (/\.json$/i.test(path)) return JSON.parse(text);
|
|
104
|
+
return yaml.load(text);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function truncateOutput(value: unknown): string {
|
|
108
|
+
const text = typeof value === 'string' ? value : value == null ? '' : String(value);
|
|
109
|
+
if (text.length <= OUTPUT_SUMMARY_CHARS) return text;
|
|
110
|
+
return `${text.slice(0, OUTPUT_SUMMARY_CHARS)}\n[truncated ${text.length - OUTPUT_SUMMARY_CHARS} chars]`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function validateMeshWorktreeBootstrapConfig(config: unknown, source = 'inline'): {
|
|
114
|
+
valid: boolean;
|
|
115
|
+
errors: string[];
|
|
116
|
+
commands: MeshRefineValidationCommandPlan[];
|
|
117
|
+
rejectedCommands: Array<Record<string, unknown>>;
|
|
118
|
+
} {
|
|
119
|
+
const errors: string[] = [];
|
|
120
|
+
const commands: MeshRefineValidationCommandPlan[] = [];
|
|
121
|
+
const rejectedCommands: Array<Record<string, unknown>> = [];
|
|
122
|
+
if (!isMeshConfigRecord(config)) return { valid: false, errors: ['config must be an object'], commands, rejectedCommands };
|
|
123
|
+
if (config.version !== 1) errors.push('version must be 1');
|
|
124
|
+
if (config.enabled !== undefined && typeof config.enabled !== 'boolean') errors.push('enabled must be a boolean when provided');
|
|
125
|
+
if (config.runOnClone !== undefined && typeof config.runOnClone !== 'boolean') errors.push('runOnClone must be a boolean when provided');
|
|
126
|
+
if (config.required !== undefined && typeof config.required !== 'boolean') errors.push('required must be a boolean when provided');
|
|
127
|
+
if (config.staleInputs !== undefined && (!Array.isArray(config.staleInputs) || !config.staleInputs.every(input => typeof input === 'string' && input.trim()))) {
|
|
128
|
+
errors.push('staleInputs must be an array of non-empty strings when provided');
|
|
129
|
+
}
|
|
130
|
+
if (config.commands !== undefined && !Array.isArray(config.commands)) errors.push('commands must be an array');
|
|
131
|
+
if (Array.isArray(config.commands)) {
|
|
132
|
+
config.commands.forEach((entry, index) => {
|
|
133
|
+
const normalized = normalizeMeshCommandConfig(entry, `${source}:commands[${index}]`);
|
|
134
|
+
if (normalized.command) commands.push(normalized.command);
|
|
135
|
+
if (normalized.rejected) rejectedCommands.push(normalized.rejected);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (config.enabled !== false && config.runOnClone !== false && commands.length === 0) errors.push('commands must contain at least one command when bootstrap is enabled');
|
|
139
|
+
if (rejectedCommands.length) errors.push('one or more bootstrap commands are invalid');
|
|
140
|
+
return { valid: errors.length === 0, errors, commands, rejectedCommands };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function loadMeshWorktreeBootstrapConfig(mesh: any, workspace: string): WorktreeBootstrapConfigLoadResult {
|
|
144
|
+
const inline = mesh?.worktreeBootstrapConfig || mesh?.policy?.worktreeBootstrapConfig || mesh?.policy?.worktreeBootstrap;
|
|
145
|
+
if (inline !== undefined) {
|
|
146
|
+
const validation = validateMeshWorktreeBootstrapConfig(inline, 'mesh.policy.worktreeBootstrapConfig');
|
|
147
|
+
if (!validation.valid) return { source: 'mesh.policy.worktreeBootstrapConfig', sourceType: 'invalid', error: String(validation.rejectedCommands[0]?.reason || validation.errors.join('; ')) };
|
|
148
|
+
return { config: inline as RepoMeshWorktreeBootstrapConfig, source: 'mesh.policy.worktreeBootstrapConfig', sourceType: 'mesh_policy' };
|
|
149
|
+
}
|
|
150
|
+
for (const relative of MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS) {
|
|
151
|
+
const configPath = join(workspace, relative);
|
|
152
|
+
if (!existsSync(configPath)) continue;
|
|
153
|
+
try {
|
|
154
|
+
const parsed = parseConfigText(configPath, readFileSync(configPath, 'utf-8'));
|
|
155
|
+
const validation = validateMeshWorktreeBootstrapConfig(parsed, relative);
|
|
156
|
+
if (!validation.valid) return { source: relative, sourceType: 'invalid', path: configPath, error: String(validation.rejectedCommands[0]?.reason || validation.errors.join('; ')) };
|
|
157
|
+
return { config: parsed as RepoMeshWorktreeBootstrapConfig, source: relative, sourceType: 'repo_file', path: configPath };
|
|
158
|
+
} catch (error: any) {
|
|
159
|
+
return { source: relative, sourceType: 'invalid', path: configPath, error: error?.message || String(error) };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { source: 'unavailable', sourceType: 'unavailable', error: `No worktree bootstrap config found. Checked: ${MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS.join(', ')}` };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** M2-1: hash staleInputs files so 'ready' can be invalidated when they change. */
|
|
166
|
+
export function computeStaleInputsDigest(workspace: string, staleInputs: string[] | undefined): Record<string, string> {
|
|
167
|
+
const digest: Record<string, string> = {};
|
|
168
|
+
for (const relative of staleInputs ?? []) {
|
|
169
|
+
const filePath = join(workspace, relative);
|
|
170
|
+
try {
|
|
171
|
+
digest[relative] = createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
172
|
+
} catch {
|
|
173
|
+
digest[relative] = 'absent';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return digest;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* M2-1: the official bootstrap state contract. Resolves the effective state
|
|
181
|
+
* for a node workspace from config presence + the persisted last-run state +
|
|
182
|
+
* a staleInputs digest comparison. Read-only — never runs commands.
|
|
183
|
+
*
|
|
184
|
+
* ready — last run succeeded and staleInputs are unchanged
|
|
185
|
+
* stale — never ran, or a staleInputs file changed since 'ready'
|
|
186
|
+
* running/failed — persisted lifecycle state passes through
|
|
187
|
+
* not_configured / disabled / (invalid → failed) — from config resolution
|
|
188
|
+
*/
|
|
189
|
+
export function evaluateWorktreeBootstrapState(mesh: any, workspace: string, persisted?: WorktreeBootstrapState | null): WorktreeBootstrapState {
|
|
190
|
+
const loaded = loadMeshWorktreeBootstrapConfig(mesh, workspace);
|
|
191
|
+
if (!loaded.config) {
|
|
192
|
+
return { status: 'not_configured', required: false, configSource: loaded.source, configSourceType: loaded.sourceType, error: loaded.error };
|
|
193
|
+
}
|
|
194
|
+
const required = loaded.config.required !== false;
|
|
195
|
+
if (loaded.config.enabled === false || loaded.config.runOnClone === false) {
|
|
196
|
+
return { status: 'disabled', required, configSource: loaded.path || loaded.source, configSourceType: loaded.sourceType };
|
|
197
|
+
}
|
|
198
|
+
if (loaded.sourceType === 'invalid') {
|
|
199
|
+
return { status: 'failed', required, configSource: loaded.path || loaded.source, configSourceType: 'invalid', error: loaded.error };
|
|
200
|
+
}
|
|
201
|
+
if (persisted?.status === 'running') return { ...persisted, required };
|
|
202
|
+
if (persisted?.status === 'failed') return { ...persisted, required };
|
|
203
|
+
if (persisted?.status === 'ready') {
|
|
204
|
+
const staleInputs = loaded.config.staleInputs ?? persisted.staleInputs ?? [];
|
|
205
|
+
if (staleInputs.length > 0 && persisted.staleInputsDigest) {
|
|
206
|
+
const current = computeStaleInputsDigest(workspace, staleInputs);
|
|
207
|
+
const changed = staleInputs.filter(p => current[p] !== persisted.staleInputsDigest![p]);
|
|
208
|
+
if (changed.length > 0) {
|
|
209
|
+
return {
|
|
210
|
+
...persisted,
|
|
211
|
+
status: 'stale',
|
|
212
|
+
required,
|
|
213
|
+
staleReason: `digest_mismatch: ${changed.join(', ')}`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { ...persisted, required };
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
status: 'stale',
|
|
221
|
+
required,
|
|
222
|
+
configSource: loaded.path || loaded.source,
|
|
223
|
+
configSourceType: loaded.sourceType,
|
|
224
|
+
staleInputs: loaded.config.staleInputs,
|
|
225
|
+
staleReason: 'never_ran',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function runMeshWorktreeBootstrap(mesh: any, workspace: string): Promise<WorktreeBootstrapState> {
|
|
230
|
+
const loaded = loadMeshWorktreeBootstrapConfig(mesh, workspace);
|
|
231
|
+
if (!loaded.config) {
|
|
232
|
+
return { status: 'not_configured', required: false, configSource: loaded.source, configSourceType: loaded.sourceType, error: loaded.error };
|
|
233
|
+
}
|
|
234
|
+
const required = loaded.config.required !== false;
|
|
235
|
+
if (loaded.config.enabled === false || loaded.config.runOnClone === false) {
|
|
236
|
+
return { status: 'disabled', required, configSource: loaded.path || loaded.source, configSourceType: loaded.sourceType };
|
|
237
|
+
}
|
|
238
|
+
const validation = validateMeshWorktreeBootstrapConfig(loaded.config, loaded.source);
|
|
239
|
+
if (!validation.valid) {
|
|
240
|
+
return { status: 'failed', required, configSource: loaded.path || loaded.source, configSourceType: 'invalid', error: String(validation.rejectedCommands[0]?.reason || validation.errors.join('; ')), commandsRun: [] };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const execFileAsync = promisify(execFile);
|
|
244
|
+
const state: WorktreeBootstrapState = {
|
|
245
|
+
status: 'running',
|
|
246
|
+
required,
|
|
247
|
+
configSource: loaded.path || loaded.source,
|
|
248
|
+
configSourceType: loaded.sourceType,
|
|
249
|
+
startedAt: new Date().toISOString(),
|
|
250
|
+
commandsRun: [],
|
|
251
|
+
staleInputs: loaded.config.staleInputs,
|
|
252
|
+
};
|
|
253
|
+
const staleInputPaths = loaded.config.staleInputs ?? [];
|
|
254
|
+
// Snapshot which staleInputs files are absent at bootstrap start.
|
|
255
|
+
// Files already present before we start are not interference signals.
|
|
256
|
+
const initiallyAbsent = staleInputPaths.filter(p => !existsSync(join(workspace, p)));
|
|
257
|
+
for (const command of validation.commands) {
|
|
258
|
+
// Check if any initially-absent staleInputs files appeared since bootstrap started
|
|
259
|
+
// (indicates another process modified the environment mid-run).
|
|
260
|
+
if (initiallyAbsent.length > 0) {
|
|
261
|
+
const appearedNow = initiallyAbsent.filter(p => existsSync(join(workspace, p)));
|
|
262
|
+
if (appearedNow.length > 0) {
|
|
263
|
+
state.status = 'stale';
|
|
264
|
+
state.completedAt = new Date().toISOString();
|
|
265
|
+
state.error = `Bootstrap interrupted: staleInputs files appeared during run: ${appearedNow.join(', ')}`;
|
|
266
|
+
return state;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const cwd = command.cwd ? pathResolve(workspace, command.cwd) : workspace;
|
|
270
|
+
const startedAt = Date.now();
|
|
271
|
+
state.lastCommand = command.displayCommand;
|
|
272
|
+
// On win32 a bare npm/npx/tsc is a .cmd shim that libuv's spawn search
|
|
273
|
+
// (which appends only .com/.exe) cannot resolve → spawn ENOENT. Resolve
|
|
274
|
+
// to an absolute path first (no-op on non-win32 / already-absolute).
|
|
275
|
+
const resolvedCommand = resolveWin32Executable(command.command);
|
|
276
|
+
try {
|
|
277
|
+
const result = await execFileAsync(resolvedCommand, command.args, {
|
|
278
|
+
cwd,
|
|
279
|
+
encoding: 'utf8',
|
|
280
|
+
timeout: command.timeoutMs || DEFAULT_TIMEOUT_MS,
|
|
281
|
+
maxBuffer: command.outputLimitBytes || DEFAULT_OUTPUT_LIMIT_BYTES,
|
|
282
|
+
env: { ...process.env, CI: process.env.CI || '1', ...(command.env || {}) },
|
|
283
|
+
windowsHide: true,
|
|
284
|
+
});
|
|
285
|
+
state.commandsRun?.push({
|
|
286
|
+
command: command.command,
|
|
287
|
+
args: command.args,
|
|
288
|
+
displayCommand: command.displayCommand,
|
|
289
|
+
category: command.category,
|
|
290
|
+
source: command.source,
|
|
291
|
+
cwd,
|
|
292
|
+
passed: true,
|
|
293
|
+
durationMs: Date.now() - startedAt,
|
|
294
|
+
exitCode: 0,
|
|
295
|
+
stdout: truncateOutput(result.stdout),
|
|
296
|
+
stderr: truncateOutput(result.stderr),
|
|
297
|
+
});
|
|
298
|
+
} catch (error: any) {
|
|
299
|
+
const exitCode = typeof error?.code === 'number' ? error.code : null;
|
|
300
|
+
state.status = 'failed';
|
|
301
|
+
state.exitCode = exitCode;
|
|
302
|
+
state.error = error?.message || String(error);
|
|
303
|
+
state.completedAt = new Date().toISOString();
|
|
304
|
+
state.commandsRun?.push({
|
|
305
|
+
command: command.command,
|
|
306
|
+
args: command.args,
|
|
307
|
+
displayCommand: command.displayCommand,
|
|
308
|
+
category: command.category,
|
|
309
|
+
source: command.source,
|
|
310
|
+
cwd,
|
|
311
|
+
passed: false,
|
|
312
|
+
durationMs: Date.now() - startedAt,
|
|
313
|
+
exitCode,
|
|
314
|
+
signal: typeof error?.signal === 'string' ? error.signal : null,
|
|
315
|
+
timedOut: error?.killed === true || /timed out/i.test(String(error?.message || '')),
|
|
316
|
+
stdout: truncateOutput(error?.stdout),
|
|
317
|
+
stderr: truncateOutput(error?.stderr || error?.message),
|
|
318
|
+
});
|
|
319
|
+
return state;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
state.status = 'ready';
|
|
323
|
+
state.exitCode = 0;
|
|
324
|
+
state.completedAt = new Date().toISOString();
|
|
325
|
+
// M2-1: record staleInputs hashes so evaluateWorktreeBootstrapState can
|
|
326
|
+
// invalidate this 'ready' when an input (e.g. lockfile) changes later.
|
|
327
|
+
if (staleInputPaths.length > 0) {
|
|
328
|
+
state.staleInputsDigest = computeStaleInputsDigest(workspace, staleInputPaths);
|
|
329
|
+
}
|
|
330
|
+
return state;
|
|
331
|
+
}
|
|
@@ -52,6 +52,7 @@ import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport }
|
|
|
52
52
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext, SessionModalState } from './provider-instance.js';
|
|
53
53
|
import { StatusMonitor } from './status-monitor.js';
|
|
54
54
|
import { buildLegacyModelModeSummaryMetadata } from './summary-metadata.js';
|
|
55
|
+
import { workingDirBasename } from './working-dir.js';
|
|
55
56
|
import {
|
|
56
57
|
buildAssistantChatMessage,
|
|
57
58
|
buildChatMessage,
|
|
@@ -287,6 +288,8 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
287
288
|
private partialBlocks: ContentBlock[] = [];
|
|
288
289
|
/** Tool calls collected during current turn */
|
|
289
290
|
private turnToolCalls: ToolCallInfo[] = [];
|
|
291
|
+
/** Guard: prevent concurrent sendPrompt calls from racing on shared state */
|
|
292
|
+
private _sendPromptInFlight = false;
|
|
290
293
|
|
|
291
294
|
// Error tracking
|
|
292
295
|
private errorMessage: string | null = null;
|
|
@@ -326,8 +329,8 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
326
329
|
this.settings = context.settings || {};
|
|
327
330
|
this.monitor.updateConfig({
|
|
328
331
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
329
|
-
|
|
330
|
-
|
|
332
|
+
noProgressAlert: (this.settings.noProgressAlert ?? this.settings.longGeneratingAlert) !== false,
|
|
333
|
+
noProgressThresholdSec: this.settings.noProgressThresholdSec ?? this.settings.longGeneratingThresholdSec ?? 180,
|
|
331
334
|
});
|
|
332
335
|
|
|
333
336
|
await this.spawnAgent();
|
|
@@ -343,7 +346,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
343
346
|
}
|
|
344
347
|
|
|
345
348
|
getSessionModalState(): SessionModalState {
|
|
346
|
-
const dirName = this.workingDir
|
|
349
|
+
const dirName = workingDirBasename(this.workingDir);
|
|
347
350
|
return {
|
|
348
351
|
id: this.instanceId,
|
|
349
352
|
status: this.currentStatus,
|
|
@@ -356,7 +359,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
356
359
|
}
|
|
357
360
|
|
|
358
361
|
getState(): AcpProviderState {
|
|
359
|
-
const dirName = this.workingDir
|
|
362
|
+
const dirName = workingDirBasename(this.workingDir);
|
|
360
363
|
|
|
361
364
|
const recentMessages = normalizeChatMessages(this.messages.map(m => {
|
|
362
365
|
const content = m.content;
|
|
@@ -671,8 +674,8 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
671
674
|
this.settings = { ...this.settings, ...newSettings };
|
|
672
675
|
this.monitor.updateConfig({
|
|
673
676
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
674
|
-
|
|
675
|
-
|
|
677
|
+
noProgressAlert: (this.settings.noProgressAlert ?? this.settings.longGeneratingAlert) !== false,
|
|
678
|
+
noProgressThresholdSec: this.settings.noProgressThresholdSec ?? this.settings.longGeneratingThresholdSec ?? 180,
|
|
676
679
|
});
|
|
677
680
|
this.log.info(`[${this.type}] Settings updated: ${Object.keys(newSettings).join(', ')}`);
|
|
678
681
|
}
|
|
@@ -1002,7 +1005,11 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1002
1005
|
}
|
|
1003
1006
|
} catch (e: any) {
|
|
1004
1007
|
this.log.warn(`[${this.type}] session/new failed: ${e?.message}`);
|
|
1005
|
-
this.
|
|
1008
|
+
if (!this.errorReason) {
|
|
1009
|
+
this.errorReason = 'init_failed';
|
|
1010
|
+
this.errorMessage = `ACP session creation failed: ${e?.message}`;
|
|
1011
|
+
}
|
|
1012
|
+
this.currentStatus = 'error';
|
|
1006
1013
|
}
|
|
1007
1014
|
}
|
|
1008
1015
|
|
|
@@ -1012,6 +1019,12 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1012
1019
|
return;
|
|
1013
1020
|
}
|
|
1014
1021
|
|
|
1022
|
+
if (this._sendPromptInFlight) {
|
|
1023
|
+
this.log.warn(`[${this.type}] sendPrompt already in flight — dropping concurrent request`);
|
|
1024
|
+
throw new Error('ACP sendPrompt already in flight');
|
|
1025
|
+
}
|
|
1026
|
+
this._sendPromptInFlight = true;
|
|
1027
|
+
|
|
1015
1028
|
// Build prompt content
|
|
1016
1029
|
const promptParts: any[] = contentBlocks && contentBlocks.length > 0
|
|
1017
1030
|
? contentBlocks.map((b) => {
|
|
@@ -1098,6 +1111,8 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1098
1111
|
this.finalizeAssistantMessage();
|
|
1099
1112
|
this.currentStatus = 'idle';
|
|
1100
1113
|
this.detectStatusTransition();
|
|
1114
|
+
} finally {
|
|
1115
|
+
this._sendPromptInFlight = false;
|
|
1101
1116
|
}
|
|
1102
1117
|
}
|
|
1103
1118
|
|
|
@@ -1490,7 +1505,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1490
1505
|
private detectStatusTransition(): void {
|
|
1491
1506
|
const now = Date.now();
|
|
1492
1507
|
const newStatus = this.currentStatus;
|
|
1493
|
-
const dirName = this.workingDir
|
|
1508
|
+
const dirName = workingDirBasename(this.workingDir);
|
|
1494
1509
|
const chatTitle = `${this.provider.name} · ${dirName}`;
|
|
1495
1510
|
const progressFingerprint = newStatus === 'generating'
|
|
1496
1511
|
? `${this.partialContent}::${JSON.stringify(this.partialBlocks)}::${JSON.stringify(this.activeToolCalls.map(t => ({ name: t.name, status: t.status })))}`.slice(-2000)
|
|
@@ -1518,7 +1533,8 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1518
1533
|
|
|
1519
1534
|
// Monitor check
|
|
1520
1535
|
const agentKey = `${this.type}:acp`;
|
|
1521
|
-
const
|
|
1536
|
+
const approvalPending = newStatus === 'waiting_approval';
|
|
1537
|
+
const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint, approvalPending);
|
|
1522
1538
|
for (const me of monitorEvents) {
|
|
1523
1539
|
this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
|
|
1524
1540
|
}
|
|
@@ -4,4 +4,8 @@ export declare function pickApprovalButton(buttons: string[] | null | undefined,
|
|
|
4
4
|
index: number;
|
|
5
5
|
label: string;
|
|
6
6
|
};
|
|
7
|
+
export declare function pickAutoApprovalButton(buttons: string[] | null | undefined): {
|
|
8
|
+
index: number;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
7
11
|
export declare function formatAutoApprovalMessage(modalMessage?: string, buttonLabel?: string): string;
|
|
@@ -24,6 +24,13 @@ function normalizeApprovalLabel(value: string): string {
|
|
|
24
24
|
.trim();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function isNegativeApprovalLabel(value: string): boolean {
|
|
28
|
+
const label = normalizeApprovalLabel(value);
|
|
29
|
+
return /^(no|deny|reject|cancel|skip|exit|stop)\b/.test(label)
|
|
30
|
+
|| /\bwithout\b/.test(label)
|
|
31
|
+
|| /\bdo not\b/.test(label);
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
export function getApprovalPositiveHints(provider?: Pick<ProviderModule, 'approvalPositiveHints'> | null): string[] {
|
|
28
35
|
const customHints = Array.isArray(provider?.approvalPositiveHints)
|
|
29
36
|
? provider.approvalPositiveHints
|
|
@@ -39,24 +46,32 @@ export function pickApprovalButton(
|
|
|
39
46
|
): { index: number; label: string } {
|
|
40
47
|
const labels = (buttons || []).map((button) => String(button || '').trim()).filter(Boolean);
|
|
41
48
|
if (labels.length === 0) {
|
|
42
|
-
return { index:
|
|
49
|
+
return { index: -1, label: '' };
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
const normalizedButtons = labels.map((label) => normalizeApprovalLabel(label));
|
|
46
53
|
const hints = getApprovalPositiveHints(provider);
|
|
47
54
|
|
|
48
55
|
for (const hint of hints) {
|
|
49
|
-
const exactIndex = normalizedButtons.findIndex((label) => label === hint);
|
|
56
|
+
const exactIndex = normalizedButtons.findIndex((label, index) => label === hint && !isNegativeApprovalLabel(labels[index]));
|
|
50
57
|
if (exactIndex >= 0) return { index: exactIndex, label: labels[exactIndex] };
|
|
51
58
|
|
|
52
|
-
const prefixIndex = normalizedButtons.findIndex((label) => label.startsWith(hint));
|
|
59
|
+
const prefixIndex = normalizedButtons.findIndex((label, index) => label.startsWith(hint) && !isNegativeApprovalLabel(labels[index]));
|
|
53
60
|
if (prefixIndex >= 0) return { index: prefixIndex, label: labels[prefixIndex] };
|
|
54
61
|
|
|
55
|
-
const includeIndex = normalizedButtons.findIndex((label) => label.includes(hint));
|
|
62
|
+
const includeIndex = normalizedButtons.findIndex((label, index) => label.includes(hint) && !isNegativeApprovalLabel(labels[index]));
|
|
56
63
|
if (includeIndex >= 0) return { index: includeIndex, label: labels[includeIndex] };
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
return { index:
|
|
66
|
+
return { index: -1, label: '' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function pickAutoApprovalButton(
|
|
70
|
+
buttons: string[] | null | undefined,
|
|
71
|
+
): { index: number; label: string } {
|
|
72
|
+
const labels = (buttons || []).map((button) => String(button || '').trim());
|
|
73
|
+
const index = labels.findIndex(Boolean);
|
|
74
|
+
return index >= 0 ? { index, label: labels[index] } : { index: -1, label: '' };
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
export function formatAutoApprovalMessage(modalMessage?: string, buttonLabel?: string): string {
|
|
@@ -65,3 +80,30 @@ export function formatAutoApprovalMessage(modalMessage?: string, buttonLabel?: s
|
|
|
65
80
|
if (cleanMessage) lines.push(cleanMessage);
|
|
66
81
|
return lines.join('\n');
|
|
67
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns true when the given text (e.g. last assistant message content, or
|
|
86
|
+
* the tail of the PTY screen) looks like an active approval/input prompt
|
|
87
|
+
* rather than a completed assistant response. Used to prevent false
|
|
88
|
+
* idle/completion events when Claude Code surfaces a "Do you want to proceed?"
|
|
89
|
+
* style prompt that the PTY parser captures as an assistant turn while the
|
|
90
|
+
* session is still awaiting user input.
|
|
91
|
+
*/
|
|
92
|
+
export function looksLikeActiveApprovalPromptText(content: string): boolean {
|
|
93
|
+
const text = content.trim();
|
|
94
|
+
if (!text || text.length > 2000) return false;
|
|
95
|
+
const hasApprovalQuestion = /do you want to (?:proceed|allow|run|make this edit|create)/i.test(text)
|
|
96
|
+
|| /this command requires approval/i.test(text)
|
|
97
|
+
|| /quick safety check/i.test(text)
|
|
98
|
+
|| /is this a project you trust/i.test(text);
|
|
99
|
+
const hasNumberedChoices = /^\s*[❯›>]?\s*1[.)]\s+(?:yes|allow|proceed|run)/im.test(text)
|
|
100
|
+
|| /^\s*1[.)]\s+yes\b/im.test(text);
|
|
101
|
+
if (hasApprovalQuestion && hasNumberedChoices) return true;
|
|
102
|
+
const lastLines = text.split(/\r?\n/).slice(-12).join('\n');
|
|
103
|
+
const hasDontAskAgain = /yes.*don'?t ask again/i.test(lastLines)
|
|
104
|
+
|| /yes.*always allow/i.test(lastLines);
|
|
105
|
+
const hasNoOption = /^\s*[❯›>]?\s*\d+[.)]\s+no\b/im.test(lastLines);
|
|
106
|
+
if (hasDontAskAgain && hasNoOption) return true;
|
|
107
|
+
if (/what do you want to do\?/i.test(text) && /^\s*\d+[.)]\s+\S/m.test(text)) return true;
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
@@ -20,18 +20,58 @@ export function extractFinalSummaryFromMessages(
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Completion summaries must describe the assistant/model result. If no
|
|
24
|
+
// user-facing assistant/model message exists yet (for example, only the
|
|
25
|
+
// dispatched user prompt is visible), return empty instead of echoing the
|
|
26
|
+
// prompt as a misleading finalSummary.
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readChatMessageTimestampIso(message: ChatMessage | null | undefined): string | undefined {
|
|
31
|
+
if (!message) return undefined;
|
|
32
|
+
const record = message as ChatMessage & Record<string, unknown>;
|
|
33
|
+
for (const value of [record.timestamp, record.createdAt, record.created_at, record.updatedAt, record.time]) {
|
|
34
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
35
|
+
// Heuristic seconds-vs-ms detection mirrors the mesh transcript reader.
|
|
36
|
+
const ms = value > 10_000_000_000 ? value : value * 1000;
|
|
37
|
+
return new Date(ms).toISOString();
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === 'string' && value.trim()) {
|
|
40
|
+
const ms = new Date(value.trim()).getTime();
|
|
41
|
+
if (Number.isFinite(ms)) return new Date(ms).toISOString();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Like extractFinalSummaryFromMessages but also returns the ISO timestamp of the
|
|
49
|
+
* selected final assistant/model message. Completion reconciliation needs the
|
|
50
|
+
* timestamp to prove the transcript was produced AFTER the dispatch — without it
|
|
51
|
+
* reconcileDirectDispatchCompletionFromTranscript rejects non-JSON summaries as
|
|
52
|
+
* "transcript_not_proven_after_dispatch". The summary selection is identical so the
|
|
53
|
+
* timestamp always belongs to the message whose text became the summary.
|
|
54
|
+
*/
|
|
55
|
+
export function extractFinalAssistantSummaryEvidence(
|
|
56
|
+
messages: ChatMessage[] | null | undefined,
|
|
57
|
+
maxChars: number = DEFAULT_FINAL_SUMMARY_MAX_CHARS,
|
|
58
|
+
): { finalSummary: string; transcriptMessageAt?: string } {
|
|
59
|
+
if (!Array.isArray(messages) || messages.length === 0) return { finalSummary: '' };
|
|
24
60
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
25
61
|
const msg = messages[i];
|
|
26
62
|
if (!msg) continue;
|
|
27
63
|
const classification = classifyChatMessageVisibility(msg);
|
|
28
|
-
if (classification.isUserFacing) {
|
|
64
|
+
if (classification.isUserFacing && (msg.role === 'assistant' || msg.role === 'model')) {
|
|
29
65
|
const text = flattenContent(msg.content).trim();
|
|
30
|
-
if (text)
|
|
66
|
+
if (text) {
|
|
67
|
+
return {
|
|
68
|
+
finalSummary: text.slice(0, maxChars),
|
|
69
|
+
transcriptMessageAt: readChatMessageTimestampIso(msg),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
31
72
|
}
|
|
32
73
|
}
|
|
33
|
-
|
|
34
|
-
return '';
|
|
74
|
+
return { finalSummary: '' };
|
|
35
75
|
}
|
|
36
76
|
|
|
37
77
|
export const BUILTIN_CHAT_MESSAGE_KINDS = ['standard', 'thought', 'tool', 'terminal', 'system'] as const;
|