@adhdev/daemon-core 0.9.82-rc.15 → 0.9.82-rc.151
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/process-hardening.d.ts +50 -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 +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +104 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
- package/dist/commands/handler.d.ts +66 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +20 -6
- package/dist/index.js +13153 -3031
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13099 -3023
- 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/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/providers/contracts.d.ts +50 -5
- 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/index.d.ts +11 -0
- package/dist/providers/provider-loader.d.ts +19 -1
- 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 +64 -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 +169 -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/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +67 -0
- package/dist/shared-types.d.ts +12 -0
- 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 +6 -2
- package/src/boot/daemon-lifecycle.ts +17 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1086 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +664 -1137
- 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 +57 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1451 -52
- package/src/commands/cli-manager.ts +184 -3
- package/src/commands/handler.ts +547 -2
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3273 -428
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +72 -5
- 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/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +820 -61
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +380 -44
- package/src/providers/contracts.ts +52 -5
- package/src/providers/ide-provider-instance.ts +17 -3
- 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 +419 -0
- package/src/providers/native-history/index.ts +23 -0
- package/src/providers/provider-loader.ts +294 -28
- package/src/providers/provider-schema.ts +31 -13
- 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 +245 -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 +444 -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 +119 -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 +210 -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/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { MessagePart, ModalInfo, ReadChatResult } from './contracts.js'
|
|
2
2
|
import { normalizeMessageParts } from './contracts.js'
|
|
3
3
|
import type { ChatBubbleState, ChatMessage } from '../types.js'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
CHAT_CONTRACT_VERSION_V1,
|
|
6
|
+
CHAT_CONTRACT_VERSION_V2,
|
|
7
|
+
assertReadChatResultV2Payload,
|
|
8
|
+
isSupportedChatContractVersion,
|
|
9
|
+
type ChatContractVersion,
|
|
10
|
+
type ReadChatResultV2,
|
|
11
|
+
} from './transcript-v2.js'
|
|
12
|
+
|
|
13
|
+
const VALID_STATUSES = ['idle', 'generating', 'waiting_approval', 'error', 'panel_hidden', 'starting', 'streaming', 'long_generating'] as const
|
|
6
14
|
const VALID_ROLES = ['user', 'assistant', 'system', 'human'] as const
|
|
7
15
|
const VALID_BUBBLE_STATES = ['draft', 'streaming', 'final', 'removed'] as const
|
|
8
16
|
const VALID_TURN_STATUSES = ['open', 'waiting_approval', 'complete', 'error'] as const
|
|
@@ -63,7 +71,7 @@ function validateMessage(message: unknown, source: string, index: number): ChatM
|
|
|
63
71
|
content: validateMessageContent(message.content, source, index),
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
if (typeof message.kind === 'string') normalized.kind = message.kind as
|
|
74
|
+
if (typeof message.kind === 'string') normalized.kind = message.kind as ChatMessage['kind']
|
|
67
75
|
if (typeof message.id === 'string') normalized.id = message.id
|
|
68
76
|
if (typeof message.bubbleId === 'string') normalized.bubbleId = message.bubbleId
|
|
69
77
|
if (typeof message.providerUnitKey === 'string') normalized.providerUnitKey = message.providerUnitKey
|
|
@@ -71,20 +79,23 @@ function validateMessage(message: unknown, source: string, index: number): ChatM
|
|
|
71
79
|
if (isFiniteNumber(message.index)) normalized.index = message.index
|
|
72
80
|
if (isFiniteNumber(message.timestamp)) normalized.timestamp = message.timestamp
|
|
73
81
|
if (isFiniteNumber(message.receivedAt)) normalized.receivedAt = message.receivedAt
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
82
|
+
// (A2.3) sequence is the monotonic ordering key consumed by ChatSourceMachine.
|
|
83
|
+
// v1 producers omit it; the daemon derives it in normalizeNativeHistoryMessages.
|
|
84
|
+
if (isFiniteNumber(message.sequence)) normalized.sequence = message.sequence
|
|
85
|
+
if (typeof message._turnKey === 'string') normalized._turnKey = message._turnKey
|
|
86
|
+
if (Array.isArray(message.toolCalls)) normalized.toolCalls = message.toolCalls as ChatMessage['toolCalls']
|
|
87
|
+
if (isPlainObject(message.meta)) normalized.meta = message.meta as ChatMessage['meta']
|
|
77
88
|
if (typeof message.senderName === 'string') normalized.senderName = message.senderName
|
|
78
|
-
if (typeof
|
|
79
|
-
if (typeof
|
|
80
|
-
if (typeof
|
|
81
|
-
if (typeof
|
|
82
|
-
if (typeof
|
|
83
|
-
if (typeof
|
|
84
|
-
if (typeof
|
|
85
|
-
if (typeof
|
|
86
|
-
if (typeof
|
|
87
|
-
if (typeof
|
|
89
|
+
if (typeof message._type === 'string') normalized._type = message._type
|
|
90
|
+
if (typeof message._sub === 'string') normalized._sub = message._sub
|
|
91
|
+
if (typeof message.visibility === 'string') normalized.visibility = message.visibility
|
|
92
|
+
if (typeof message.transcriptVisibility === 'string') normalized.transcriptVisibility = message.transcriptVisibility
|
|
93
|
+
if (typeof message.audience === 'string') normalized.audience = message.audience
|
|
94
|
+
if (typeof message.source === 'string') normalized.source = message.source
|
|
95
|
+
if (typeof message.userFacing === 'boolean') normalized.userFacing = message.userFacing
|
|
96
|
+
if (typeof message.internal === 'boolean') normalized.internal = message.internal
|
|
97
|
+
if (typeof message.isInternal === 'boolean') normalized.isInternal = message.isInternal
|
|
98
|
+
if (typeof message.debug === 'boolean') normalized.debug = message.debug
|
|
88
99
|
|
|
89
100
|
return normalized
|
|
90
101
|
}
|
|
@@ -129,6 +140,55 @@ function validateControlValues(controlValues: unknown, source: string): Record<s
|
|
|
129
140
|
return normalized
|
|
130
141
|
}
|
|
131
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Read the producer-declared contract version from a raw read_chat payload.
|
|
145
|
+
* Returns v1 when absent or unrecognised, so legacy producers keep working
|
|
146
|
+
* through A1. A2 will tighten this to throw when an unsupported version is
|
|
147
|
+
* declared.
|
|
148
|
+
*/
|
|
149
|
+
export function readPayloadContractVersion(raw: unknown): ChatContractVersion {
|
|
150
|
+
if (!isPlainObject(raw)) return CHAT_CONTRACT_VERSION_V1
|
|
151
|
+
const declared = (raw as Record<string, unknown>).contractVersion
|
|
152
|
+
if (isSupportedChatContractVersion(declared)) return declared
|
|
153
|
+
return CHAT_CONTRACT_VERSION_V1
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validate a v2 payload. Thin wrapper around assertReadChatResultV2Payload
|
|
158
|
+
* that prefixes the contract violation with the caller's source label.
|
|
159
|
+
*/
|
|
160
|
+
export function validateReadChatResultV2Payload(raw: unknown, source = 'read_chat'): ReadChatResultV2 {
|
|
161
|
+
try {
|
|
162
|
+
return assertReadChatResultV2Payload(raw)
|
|
163
|
+
} catch (err) {
|
|
164
|
+
if (err instanceof Error) {
|
|
165
|
+
err.message = `${source}: ${err.message}`
|
|
166
|
+
}
|
|
167
|
+
throw err
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Versioned entry point. Routes on the producer-declared contractVersion:
|
|
173
|
+
* - v2 → strict v2 validation (transcript-v2.ts invariants)
|
|
174
|
+
* - v1 (or absent) → legacy permissive validation
|
|
175
|
+
*
|
|
176
|
+
* Callers that have not yet been audited to handle v2 outputs should use
|
|
177
|
+
* validateReadChatResultPayload directly; that path stays bound to v1 shape
|
|
178
|
+
* during the A1 transition.
|
|
179
|
+
*/
|
|
180
|
+
export function validateReadChatResultPayloadVersioned(
|
|
181
|
+
raw: unknown,
|
|
182
|
+
source = 'read_chat',
|
|
183
|
+
): { version: typeof CHAT_CONTRACT_VERSION_V1; payload: ReadChatResult & Record<string, unknown> }
|
|
184
|
+
| { version: typeof CHAT_CONTRACT_VERSION_V2; payload: ReadChatResultV2 } {
|
|
185
|
+
const version = readPayloadContractVersion(raw)
|
|
186
|
+
if (version === CHAT_CONTRACT_VERSION_V2) {
|
|
187
|
+
return { version, payload: validateReadChatResultV2Payload(raw, source) }
|
|
188
|
+
}
|
|
189
|
+
return { version: CHAT_CONTRACT_VERSION_V1, payload: validateReadChatResultPayload(raw, source) }
|
|
190
|
+
}
|
|
191
|
+
|
|
132
192
|
export function validateReadChatResultPayload(raw: unknown, source = 'read_chat'): ReadChatResult & Record<string, unknown> {
|
|
133
193
|
if (!isPlainObject(raw)) {
|
|
134
194
|
throw new Error(`${source}: payload must be an object`)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Provider SDK
|
|
2
|
+
|
|
3
|
+
> Status: Phase 1 in progress · License of this directory: AGPL-3.0 (part of `oss/`) · See [marketplace plan](../../../../../../docs/design/v1.0.0-marketplace-plan.md).
|
|
4
|
+
|
|
5
|
+
This directory is the **Provider SDK** — the framework that lets external developers author CLI providers (and later IDE, Extension, ACP) against a versioned, typed contract.
|
|
6
|
+
|
|
7
|
+
It lives **inside daemon-core**, not as a separate package, by design:
|
|
8
|
+
|
|
9
|
+
- the contract definitions live next to the runtime that consumes them, eliminating version drift
|
|
10
|
+
- `adhdev provider *` CLI commands call straight into these modules
|
|
11
|
+
- two npm packages are extracted from this source at build time:
|
|
12
|
+
- `@adhdev/provider-types` (Apache 2.0) — TypeScript types for external authors
|
|
13
|
+
- `@adhdev/provider-schemas` (Apache 2.0) — JSON Schemas for editor support
|
|
14
|
+
|
|
15
|
+
## Layout
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
sdk/
|
|
19
|
+
v1/ ← contract version 1 (current)
|
|
20
|
+
types/ ← TypeScript types — extracted to @adhdev/provider-types
|
|
21
|
+
cli/ ← CLI category types
|
|
22
|
+
common/ ← shared types (settings, capabilities, auth, spawn)
|
|
23
|
+
schemas/ ← JSON Schemas — extracted to @adhdev/provider-schemas
|
|
24
|
+
cli/
|
|
25
|
+
provider.schema.json ← top-level provider.json (mirrors adhdev-providers/schemas/v1/cli/)
|
|
26
|
+
primitives/ ← per-primitive schemas, namespaced by category
|
|
27
|
+
common/
|
|
28
|
+
primitives/ ← daemon-side primitive implementations
|
|
29
|
+
cli/ ← TUI primitives, native-history adapters, capability handlers
|
|
30
|
+
builders/ ← functions that turn manifest blocks into runtime handlers
|
|
31
|
+
validators/ ← schema check, AST taint analysis, fixture replay
|
|
32
|
+
scaffolders/ ← `adhdev provider init` templates
|
|
33
|
+
fixture-tooling/ ← PTY capture + replay utilities
|
|
34
|
+
v2/ ← reserved for next contract major
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Primitive identifiers
|
|
38
|
+
|
|
39
|
+
Primitives are referenced by `$schema: adhdev:<category>/<id>@<version>` in provider manifests. See the audit-derived v1 catalog of 50 primitives at [`audit-cli-v1.md §5`](../../../../../../../adhdev-providers/docs/provider-contract/cli/audit-cli-v1.md#5-proposed-v1-primitive-set).
|
|
40
|
+
|
|
41
|
+
## Stability
|
|
42
|
+
|
|
43
|
+
`v1/` is unstable until SDK `1.0.0` is published. Breaking changes between SDK `0.x` releases are permitted during Phase 1-2 migration. After SDK `1.0.0`, breaking changes require an `engines.adhdev` major bump.
|
|
44
|
+
|
|
45
|
+
## Out of scope
|
|
46
|
+
|
|
47
|
+
- This SDK does not provide IDE, Extension, or ACP primitives in v1.0.0. They follow in v1.1+.
|
|
48
|
+
- This SDK does not include the registry server code — that lives in [`oss/packages/registry/`](../../../../../registry/) (not yet created).
|
|
49
|
+
- This SDK does not include the marketplace web UI — that lives separately.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildDetectStatusFromAcp
|
|
3
|
+
*
|
|
4
|
+
* Turns a declarative `acp/session-protocol@1` block into a runtime
|
|
5
|
+
* `(input: AcpStatusInput) => 'idle' | 'generating' | 'waiting_approval' | null`
|
|
6
|
+
* function.
|
|
7
|
+
*
|
|
8
|
+
* Dispatch order:
|
|
9
|
+
* 1. Not connected → null (no information available).
|
|
10
|
+
* 2. Error patterns — checked first; first match returns the declared verdict.
|
|
11
|
+
* 3. Generating pattern — `generating`.
|
|
12
|
+
* 4. Idle pattern — `idle`.
|
|
13
|
+
* 5. Default: `null` (no change — caller preserves last known status).
|
|
14
|
+
*
|
|
15
|
+
* All regex patterns are compiled once at builder time so the returned
|
|
16
|
+
* detector function is allocation-free on the hot path.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ─── Spec shapes (mirror the JSON schema) ──────────────────────────────
|
|
20
|
+
|
|
21
|
+
interface AcpRegexPatternSpec {
|
|
22
|
+
regex: string;
|
|
23
|
+
flags?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AcpErrorPatternSpec extends AcpRegexPatternSpec {
|
|
28
|
+
verdict: 'idle' | 'generating' | 'waiting_approval';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AcpSessionSpec {
|
|
32
|
+
$schema?: 'adhdev:acp/session-protocol@1';
|
|
33
|
+
/** Pattern that matches a line emitted when the agent is idle/ready. */
|
|
34
|
+
idlePattern?: AcpRegexPatternSpec;
|
|
35
|
+
/** Pattern that matches a line emitted while the agent is actively working. */
|
|
36
|
+
generatingPattern?: AcpRegexPatternSpec;
|
|
37
|
+
/**
|
|
38
|
+
* Error/edge-case patterns. Checked in order; first match returns the
|
|
39
|
+
* declared verdict. Useful for approval prompts, fatal errors, etc.
|
|
40
|
+
*/
|
|
41
|
+
errorPatterns?: AcpErrorPatternSpec[];
|
|
42
|
+
/** Wire format; informational only at the builder level — regex matching
|
|
43
|
+
* is identical regardless of promptStyle. */
|
|
44
|
+
promptStyle?: 'json-rpc' | 'plain-text' | 'mcp';
|
|
45
|
+
/** Message delimiter used to split incoming bytes (default: newline). */
|
|
46
|
+
messageDelimiter?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Input shape ────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
export interface AcpStatusInput {
|
|
52
|
+
/** The most recently received line from the stdio stream. */
|
|
53
|
+
lastLine: string;
|
|
54
|
+
/**
|
|
55
|
+
* A sliding window of recent output lines (newest last).
|
|
56
|
+
* Builders may check these when a single line is not enough for context.
|
|
57
|
+
*/
|
|
58
|
+
recentLines: string[];
|
|
59
|
+
/** Whether the ACP process is currently connected and running. */
|
|
60
|
+
isConnected: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Output type ────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export type AcpDetectedStatus = 'idle' | 'generating' | 'waiting_approval' | null;
|
|
66
|
+
|
|
67
|
+
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function compile(re: string, flags?: string): RegExp {
|
|
70
|
+
try {
|
|
71
|
+
return new RegExp(re, flags ?? 'i');
|
|
72
|
+
} catch (e) {
|
|
73
|
+
throw new Error(`Invalid regex /${re}/${flags ?? 'i'}: ${(e as Error).message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Safe `.test()` wrapper: resets `lastIndex` before each call so that
|
|
79
|
+
* regexes compiled with the `g` or `y` flag remain stateless across
|
|
80
|
+
* multiple detect invocations.
|
|
81
|
+
*/
|
|
82
|
+
function testRe(re: RegExp, text: string): boolean {
|
|
83
|
+
re.lastIndex = 0;
|
|
84
|
+
return re.test(text);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Compiled internal representation ───────────────────────────────────
|
|
88
|
+
|
|
89
|
+
interface CompiledAcpSpec {
|
|
90
|
+
idle: RegExp | null;
|
|
91
|
+
generating: RegExp | null;
|
|
92
|
+
errors: Array<{ re: RegExp; verdict: AcpDetectedStatus }>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compileSpec(spec: AcpSessionSpec): CompiledAcpSpec {
|
|
96
|
+
const idle = spec.idlePattern
|
|
97
|
+
? compile(spec.idlePattern.regex, spec.idlePattern.flags)
|
|
98
|
+
: null;
|
|
99
|
+
|
|
100
|
+
const generating = spec.generatingPattern
|
|
101
|
+
? compile(spec.generatingPattern.regex, spec.generatingPattern.flags)
|
|
102
|
+
: null;
|
|
103
|
+
|
|
104
|
+
const errors = (spec.errorPatterns ?? []).map((ep) => ({
|
|
105
|
+
re: compile(ep.regex, ep.flags),
|
|
106
|
+
verdict: ep.verdict as AcpDetectedStatus,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return { idle, generating, errors };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Public builder ─────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export function buildDetectStatusFromAcp(
|
|
115
|
+
spec: AcpSessionSpec,
|
|
116
|
+
): (input: AcpStatusInput) => AcpDetectedStatus {
|
|
117
|
+
const compiled = compileSpec(spec);
|
|
118
|
+
|
|
119
|
+
return function detectAcpStatus(input: AcpStatusInput): AcpDetectedStatus {
|
|
120
|
+
// 1. No connection → no information.
|
|
121
|
+
if (!input.isConnected) return null;
|
|
122
|
+
|
|
123
|
+
const line = input.lastLine;
|
|
124
|
+
|
|
125
|
+
// 2. Error/edge-case patterns take priority.
|
|
126
|
+
for (const { re, verdict } of compiled.errors) {
|
|
127
|
+
if (testRe(re, line)) return verdict;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 3. Generating pattern.
|
|
131
|
+
if (compiled.generating && testRe(compiled.generating, line)) return 'generating';
|
|
132
|
+
|
|
133
|
+
// 4. Idle pattern.
|
|
134
|
+
if (compiled.idle && testRe(compiled.idle, line)) return 'idle';
|
|
135
|
+
|
|
136
|
+
// 5. Nothing matched.
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Internal exports for builder reuse + tests.
|
|
142
|
+
export const __internal = {
|
|
143
|
+
compileSpec,
|
|
144
|
+
};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildDetectStatusFromTui
|
|
3
|
+
*
|
|
4
|
+
* Turns a manifest's declarative TUI block into a runtime
|
|
5
|
+
* `(input: CliStatusInput) => CliStatus | null` function.
|
|
6
|
+
*
|
|
7
|
+
* Inputs handled (all optional except spinner):
|
|
8
|
+
* - tui/spinner@1 → contributes `generating`
|
|
9
|
+
* - tui/settled-prompt@1 → contributes `idle`
|
|
10
|
+
* - tui/modal@1 → contributes `waiting_approval`
|
|
11
|
+
*
|
|
12
|
+
* Dispatch order (sprint-2026-06 contract):
|
|
13
|
+
* 1. Active generation cues win over idle prompt — checked first.
|
|
14
|
+
* Reason: codex/claude keep model footer visible during `Working` and
|
|
15
|
+
* `hasReadyPrompt` falsely fired idle. Audit §4.2 introduces
|
|
16
|
+
* `tui/cue-ordering` as the named primitive for this rule.
|
|
17
|
+
* 2. Modal cue (if present) is checked next — `waiting_approval`.
|
|
18
|
+
* 3. Settled-prompt cue — `idle`.
|
|
19
|
+
* 4. Default: `null` (no change).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
CliStatusInput,
|
|
24
|
+
CliStatus,
|
|
25
|
+
CliDetectStatusFn,
|
|
26
|
+
} from '../../types/cli/index.js';
|
|
27
|
+
import {
|
|
28
|
+
applyVisibleRegion,
|
|
29
|
+
type VisibleRegionSpec,
|
|
30
|
+
} from './visible-region.js';
|
|
31
|
+
|
|
32
|
+
// ─── Primitive spec shapes (mirror the JSON schemas) ───────────────────
|
|
33
|
+
|
|
34
|
+
interface RegexLikeSpec {
|
|
35
|
+
regex: string;
|
|
36
|
+
flags?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SpinnerPatternSpec extends RegexLikeSpec {
|
|
40
|
+
description?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface SpinnerSpec {
|
|
44
|
+
$schema: 'adhdev:tui/spinner@1';
|
|
45
|
+
patterns: SpinnerPatternSpec[];
|
|
46
|
+
scope?: 'live-frame-tail' | 'whole-screen' | 'recent-buffer';
|
|
47
|
+
scopeWindowLines?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface SettledPromptFooterSpec {
|
|
51
|
+
pattern: string;
|
|
52
|
+
kind?: 'substring' | 'regex';
|
|
53
|
+
flags?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface SettledPromptSpec {
|
|
57
|
+
$schema: 'adhdev:tui/settled-prompt@1';
|
|
58
|
+
regex: string;
|
|
59
|
+
flags?: string;
|
|
60
|
+
withFooter?: SettledPromptFooterSpec[];
|
|
61
|
+
scope?: 'last-n-lines' | 'whole-screen';
|
|
62
|
+
scopeWindowLines?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ModalSpec {
|
|
66
|
+
$schema: 'adhdev:tui/modal@1';
|
|
67
|
+
questionPattern: string;
|
|
68
|
+
questionFlags?: string;
|
|
69
|
+
questionVariants?: Array<{ regex: string; flags?: string; label?: string }>;
|
|
70
|
+
buttonPattern: string;
|
|
71
|
+
buttonFlags?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type DispatchGroup =
|
|
75
|
+
| 'spinner'
|
|
76
|
+
| 'modal'
|
|
77
|
+
| 'settled-prompt'
|
|
78
|
+
| 'cue-ordering'
|
|
79
|
+
| 'error-detection'
|
|
80
|
+
| 'approval-stitching';
|
|
81
|
+
|
|
82
|
+
export interface DispatchOrderSpec {
|
|
83
|
+
$schema: 'adhdev:tui/dispatch-order@1';
|
|
84
|
+
order: DispatchGroup[];
|
|
85
|
+
onNoMatch?: 'idle' | 'unknown' | 'preserve-last';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface DetectStatusTuiSpec {
|
|
89
|
+
spinner?: SpinnerSpec;
|
|
90
|
+
settledPrompt?: SettledPromptSpec;
|
|
91
|
+
modal?: ModalSpec;
|
|
92
|
+
dispatchOrder?: DispatchOrderSpec;
|
|
93
|
+
visibleRegion?: VisibleRegionSpec;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Helpers ───────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function compile(re: string, flags?: string): RegExp {
|
|
99
|
+
try {
|
|
100
|
+
return new RegExp(re, flags ?? '');
|
|
101
|
+
} catch (e) {
|
|
102
|
+
throw new Error(`Invalid regex /${re}/${flags ?? ''}: ${(e as Error).message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function takeTail(text: string, lines: number): string {
|
|
107
|
+
if (!text) return '';
|
|
108
|
+
const split = text.split('\n');
|
|
109
|
+
if (split.length <= lines) return text;
|
|
110
|
+
return split.slice(-lines).join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function scopeText(
|
|
114
|
+
input: CliStatusInput,
|
|
115
|
+
scope: SpinnerSpec['scope'] | SettledPromptSpec['scope'],
|
|
116
|
+
windowLines: number | undefined,
|
|
117
|
+
): string {
|
|
118
|
+
const lines = windowLines && windowLines > 0 ? windowLines : 8;
|
|
119
|
+
switch (scope) {
|
|
120
|
+
case 'whole-screen':
|
|
121
|
+
return input.screenText ?? '';
|
|
122
|
+
case 'recent-buffer':
|
|
123
|
+
return input.tail ?? '';
|
|
124
|
+
case 'last-n-lines':
|
|
125
|
+
case 'live-frame-tail':
|
|
126
|
+
case undefined:
|
|
127
|
+
default:
|
|
128
|
+
return takeTail(input.screenText ?? '', lines);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function compileSpinnerMatchers(spec: SpinnerSpec): RegExp[] {
|
|
133
|
+
return spec.patterns.map((p) => compile(p.regex, p.flags ?? 'i'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function spinnerMatches(spec: SpinnerSpec, input: CliStatusInput): boolean {
|
|
137
|
+
const matchers = compileSpinnerMatchers(spec);
|
|
138
|
+
const text = scopeText(input, spec.scope, spec.scopeWindowLines);
|
|
139
|
+
return matchers.some((re) => re.test(text));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function compileSettledPromptMatchers(spec: SettledPromptSpec): {
|
|
143
|
+
prompt: RegExp;
|
|
144
|
+
footers: Array<{ test: (s: string) => boolean }>;
|
|
145
|
+
} {
|
|
146
|
+
const prompt = compile(spec.regex, spec.flags ?? 'm');
|
|
147
|
+
const footers = (spec.withFooter ?? []).map((f) => {
|
|
148
|
+
if (f.kind === 'regex') {
|
|
149
|
+
const re = compile(f.pattern, f.flags ?? 'i');
|
|
150
|
+
return { test: (s: string) => re.test(s) };
|
|
151
|
+
}
|
|
152
|
+
const needle = f.pattern.toLowerCase();
|
|
153
|
+
return { test: (s: string) => s.toLowerCase().includes(needle) };
|
|
154
|
+
});
|
|
155
|
+
return { prompt, footers };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function settledPromptMatches(spec: SettledPromptSpec, input: CliStatusInput): boolean {
|
|
159
|
+
const { prompt, footers } = compileSettledPromptMatchers(spec);
|
|
160
|
+
const text = scopeText(input, spec.scope, spec.scopeWindowLines);
|
|
161
|
+
if (!prompt.test(text)) return false;
|
|
162
|
+
if (footers.length === 0) return true;
|
|
163
|
+
return footers.every((f) => f.test(text));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function modalMatches(spec: ModalSpec, input: CliStatusInput): boolean {
|
|
167
|
+
// Status-level modal detection is cue-only — does the question appear at all?
|
|
168
|
+
// Button extraction lives in buildParseApprovalFromTui.
|
|
169
|
+
const text = input.screenText ?? '';
|
|
170
|
+
const question = compile(spec.questionPattern, spec.questionFlags ?? 'i');
|
|
171
|
+
if (question.test(text)) return true;
|
|
172
|
+
for (const variant of spec.questionVariants ?? []) {
|
|
173
|
+
const re = compile(variant.regex, variant.flags ?? 'i');
|
|
174
|
+
if (re.test(text)) return true;
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Public builder ────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
const DEFAULT_ORDER: DispatchGroup[] = ['spinner', 'modal', 'settled-prompt'];
|
|
182
|
+
|
|
183
|
+
function evaluateGroup(
|
|
184
|
+
group: DispatchGroup,
|
|
185
|
+
spec: DetectStatusTuiSpec,
|
|
186
|
+
input: CliStatusInput,
|
|
187
|
+
compiled: {
|
|
188
|
+
spinner: RegExp[] | null;
|
|
189
|
+
settled: ReturnType<typeof compileSettledPromptMatchers> | null;
|
|
190
|
+
},
|
|
191
|
+
): CliStatus | null {
|
|
192
|
+
switch (group) {
|
|
193
|
+
case 'spinner': {
|
|
194
|
+
if (!spec.spinner || !compiled.spinner) return null;
|
|
195
|
+
const text = scopeText(input, spec.spinner.scope, spec.spinner.scopeWindowLines);
|
|
196
|
+
return compiled.spinner.some((re) => re.test(text)) ? 'generating' : null;
|
|
197
|
+
}
|
|
198
|
+
case 'modal': {
|
|
199
|
+
if (!spec.modal) return null;
|
|
200
|
+
return modalMatches(spec.modal, input) ? 'waiting_approval' : null;
|
|
201
|
+
}
|
|
202
|
+
case 'settled-prompt': {
|
|
203
|
+
if (!spec.settledPrompt || !compiled.settled) return null;
|
|
204
|
+
const text = scopeText(input, spec.settledPrompt.scope, spec.settledPrompt.scopeWindowLines);
|
|
205
|
+
if (!compiled.settled.prompt.test(text)) return null;
|
|
206
|
+
if (compiled.settled.footers.length === 0) return 'idle';
|
|
207
|
+
return compiled.settled.footers.every((f) => f.test(text)) ? 'idle' : null;
|
|
208
|
+
}
|
|
209
|
+
// Groups declared in the catalog but not yet implemented as builder steps
|
|
210
|
+
// return null so they are no-ops in dispatch — declaring them in `order`
|
|
211
|
+
// is forward-compatible. Phase 2 Week 8+ will wire them.
|
|
212
|
+
case 'cue-ordering':
|
|
213
|
+
case 'error-detection':
|
|
214
|
+
case 'approval-stitching':
|
|
215
|
+
return null;
|
|
216
|
+
default:
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function buildDetectStatusFromTui(spec: DetectStatusTuiSpec): CliDetectStatusFn {
|
|
222
|
+
const compiledSpinner = spec.spinner ? compileSpinnerMatchers(spec.spinner) : null;
|
|
223
|
+
const compiledSettled = spec.settledPrompt
|
|
224
|
+
? compileSettledPromptMatchers(spec.settledPrompt)
|
|
225
|
+
: null;
|
|
226
|
+
const compiled = { spinner: compiledSpinner, settled: compiledSettled };
|
|
227
|
+
const order = spec.dispatchOrder?.order && spec.dispatchOrder.order.length > 0
|
|
228
|
+
? spec.dispatchOrder.order
|
|
229
|
+
: DEFAULT_ORDER;
|
|
230
|
+
|
|
231
|
+
return function detectStatus(input: CliStatusInput): CliStatus | null {
|
|
232
|
+
// Apply visible-region scoping before running any matchers.
|
|
233
|
+
const effectiveInput: CliStatusInput = spec.visibleRegion
|
|
234
|
+
? {
|
|
235
|
+
...input,
|
|
236
|
+
screenText: applyVisibleRegion(spec.visibleRegion, input.screenText ?? ''),
|
|
237
|
+
tail: applyVisibleRegion(spec.visibleRegion, input.tail),
|
|
238
|
+
}
|
|
239
|
+
: input;
|
|
240
|
+
|
|
241
|
+
for (const group of order) {
|
|
242
|
+
const verdict = evaluateGroup(group, spec, effectiveInput, compiled);
|
|
243
|
+
if (verdict !== null) return verdict;
|
|
244
|
+
}
|
|
245
|
+
// onNoMatch policy: the builder always returns null when nothing matched.
|
|
246
|
+
// The daemon's outer state machine maps null per the policy below:
|
|
247
|
+
// 'idle' → caller treats null as idle (default)
|
|
248
|
+
// 'unknown' → caller keeps status unknown / shows last
|
|
249
|
+
// 'preserve-last' → caller preserves the last reported verdict
|
|
250
|
+
// Encoding this here would require call-site state; instead the daemon
|
|
251
|
+
// reads `dispatchOrder.onNoMatch` from the manifest directly.
|
|
252
|
+
return null;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Internal exports for builder reuse + tests.
|
|
257
|
+
export const __internal = {
|
|
258
|
+
scopeText,
|
|
259
|
+
spinnerMatches,
|
|
260
|
+
settledPromptMatches,
|
|
261
|
+
modalMatches,
|
|
262
|
+
};
|