@scotthuang/agent-knock-knock 0.14.13 → 0.14.15
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/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +7 -6
- package/dist/src/desktop-cli-adapter.js +5 -6
- package/dist/src/desktop-cli-adapter.js.map +1 -1
- package/dist/src/desktop-cli-controls.js +0 -9
- package/dist/src/desktop-cli-controls.js.map +1 -1
- package/dist/src/desktop-compatibility.d.ts +19 -2
- package/dist/src/desktop-compatibility.js +17 -3
- package/dist/src/desktop-compatibility.js.map +1 -1
- package/dist/src/desktop-creation-native.d.ts +6 -1
- package/dist/src/desktop-creation-native.js +26 -15
- package/dist/src/desktop-creation-native.js.map +1 -1
- package/dist/src/desktop-interaction-projection.d.ts +1 -0
- package/dist/src/desktop-interaction-projection.js +7 -4
- package/dist/src/desktop-interaction-projection.js.map +1 -1
- package/dist/src/desktop-ipc-client.js +9 -11
- package/dist/src/desktop-ipc-client.js.map +1 -1
- package/dist/src/desktop-ipc-native-operations.d.ts +1 -2
- package/dist/src/desktop-ipc-native-operations.js +4 -4
- package/dist/src/desktop-ipc-native-operations.js.map +1 -1
- package/dist/src/desktop-list.d.ts +2 -0
- package/dist/src/desktop-list.js +2 -0
- package/dist/src/desktop-list.js.map +1 -1
- package/dist/src/desktop-public-projection.js +20 -18
- package/dist/src/desktop-public-projection.js.map +1 -1
- package/dist/src/desktop-runtime.js +3 -3
- package/dist/src/desktop-runtime.js.map +1 -1
- package/dist/src/desktop-task-interactions.js +8 -6
- package/dist/src/desktop-task-interactions.js.map +1 -1
- package/dist/src/desktop-task-service.js +1 -1
- package/dist/src/desktop-task-service.js.map +1 -1
- package/dist/src/desktop-types.d.ts +2 -1
- package/dist/src/doctor-capabilities.d.ts +7 -2
- package/dist/src/doctor-capabilities.js +15 -14
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/herdr-api-contract.d.ts +15 -0
- package/dist/src/herdr-api-contract.js +152 -0
- package/dist/src/herdr-api-contract.js.map +1 -0
- package/dist/src/herdr-compatibility.d.ts +48 -0
- package/dist/src/herdr-compatibility.js +36 -0
- package/dist/src/herdr-compatibility.js.map +1 -0
- package/dist/src/herdr-operation-capabilities.d.ts +17 -0
- package/dist/src/herdr-operation-capabilities.js +66 -0
- package/dist/src/herdr-operation-capabilities.js.map +1 -0
- package/dist/src/herdr-terminal-control-provider.d.ts +11 -6
- package/dist/src/herdr-terminal-control-provider.js +158 -79
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/install-doctor-command-adapter.js +5 -2
- package/dist/src/install-doctor-command-adapter.js.map +1 -1
- package/dist/src/semantic-tool-list-projection.js +3 -2
- package/dist/src/semantic-tool-list-projection.js.map +1 -1
- package/dist/src/terminal-monitor-decision-policy.d.ts +1 -1
- package/dist/src/terminal-permission-control-list-projection.d.ts +1 -1
- package/docs/codex-0.162.1-compatibility.md +1 -1
- package/docs/codex-desktop-compatibility.md +68 -42
- package/docs/desktop-create.md +11 -7
- package/docs/openclaw-operations.md +1 -1
- package/docs/quickstart-herdr.md +15 -3
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +4 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { inspectHerdrInputSchema } from "./herdr-api-contract.js";
|
|
2
|
+
import { TerminalControlInputNotSentError } from "./terminal-control-provider.js";
|
|
3
|
+
export function declaredHerdrInputCapabilities(cli, server) {
|
|
4
|
+
const mismatched = cli.version !== server.version;
|
|
5
|
+
if (mismatched || cli.schemaState === "unavailable") {
|
|
6
|
+
const capability = server.baselineKnown
|
|
7
|
+
? { state: "available", reason: "verified release input behavior; no matching CLI schema", evidence: "verified_release" }
|
|
8
|
+
: { state: "unknown", reason: "no matching CLI schema or verified input behavior", evidence: "none" };
|
|
9
|
+
return { textInput: capability, keyInput: capability, rawTextInput: capability };
|
|
10
|
+
}
|
|
11
|
+
const declaration = inspectHerdrInputSchema(cli.schema, server.protocol);
|
|
12
|
+
const capability = (support) => ({
|
|
13
|
+
...support,
|
|
14
|
+
evidence: support.state === "unknown" ? "none" : "matching_cli_schema"
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
textInput: capability(declaration.textInput),
|
|
18
|
+
keyInput: capability(declaration.keyInput),
|
|
19
|
+
rawTextInput: capability(declaration.rawTextInput)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function herdrControlCapabilities(operations) {
|
|
23
|
+
const readable = [operations.visibleText, operations.detectionText]
|
|
24
|
+
.some((capability) => capability.state === "available");
|
|
25
|
+
// Public Send is a composite text+Enter operation. Keep detailed partial
|
|
26
|
+
// input support in diagnostics instead of advertising an unusable Send.
|
|
27
|
+
const sendable = operations.textInput.state === "available" &&
|
|
28
|
+
operations.keyInput.state === "available";
|
|
29
|
+
return [
|
|
30
|
+
...(readable ? ["screen_status", "screen_completion"] : []),
|
|
31
|
+
"durable_completion",
|
|
32
|
+
...(sendable ? ["send_keys", "terminal_approval", "terminal_cancel"] : [])
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
export function assertOperationAvailable(capabilities, operation) {
|
|
36
|
+
const capability = capabilities[operation];
|
|
37
|
+
if (capability.state !== "available") {
|
|
38
|
+
throw new Error(`Herdr ${operation} is ${capability.state}: ${capability.reason}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function assertInputEndpointCapability(terminal, providerCapabilities, providerCapability, operations) {
|
|
42
|
+
if (terminal.identity.providerKind !== "herdr") {
|
|
43
|
+
throw new TerminalControlInputNotSentError(`terminal control provider herdr cannot use ${terminal.identity.providerKind}`);
|
|
44
|
+
}
|
|
45
|
+
if (!terminal.capabilities.includes("send_keys")) {
|
|
46
|
+
const detail = operations ? ["textInput", "keyInput"]
|
|
47
|
+
.filter((operation) => operations[operation].state !== "available")
|
|
48
|
+
.map((operation) => `${operation} is ${operations[operation].state}: ${operations[operation].reason}`)
|
|
49
|
+
.join("; ") : "";
|
|
50
|
+
throw new TerminalControlInputNotSentError(`Herdr terminal does not permit send_keys${detail ? `; ${detail}` : ""}`);
|
|
51
|
+
}
|
|
52
|
+
if (!providerCapabilities.includes(providerCapability)) {
|
|
53
|
+
throw new TerminalControlInputNotSentError(`Herdr provider does not support ${providerCapability}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Prefer agent detection for plain status; visible text is a bounded fallback. */
|
|
57
|
+
export function selectHerdrReadOperation(capabilities, viewportOnly, preserveEscapes) {
|
|
58
|
+
if (preserveEscapes)
|
|
59
|
+
return { source: "visible", format: "ansi", operation: "visibleAnsi" };
|
|
60
|
+
if (viewportOnly || (capabilities.detectionText.state !== "available" &&
|
|
61
|
+
capabilities.visibleText.state === "available")) {
|
|
62
|
+
return { source: "visible", format: "text", operation: "visibleText" };
|
|
63
|
+
}
|
|
64
|
+
return { source: "detection", format: "text", operation: "detectionText" };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=herdr-operation-capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"herdr-operation-capabilities.js","sourceRoot":"","sources":["../../src/herdr-operation-capabilities.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { type HerdrOperationCapabilities, type HerdrServerObservation } from "./herdr-compatibility.js";
|
|
2
|
+
export { HERDR_EXACT_PROTOCOL, HERDR_EXACT_VERSION } from "./herdr-compatibility.js";
|
|
1
3
|
import { type CommandResult, type TerminalDiscoveryDiagnosticLog, type TerminalControlProvider, type TerminalViewport } from "./terminal-control-provider.js";
|
|
2
4
|
import { type TerminalControlCapability, type TerminalControlRef, type TerminalEndpointRef, type TerminalProviderCapability } from "./terminal-control-ref.js";
|
|
3
5
|
import type { TerminalProcessSnapshot } from "./terminal-agent-adapter.js";
|
|
4
6
|
import { type HerdrRequestFunction, type HerdrSocketIdentity } from "./herdr-socket-transport.js";
|
|
5
7
|
export { HerdrApiError, HerdrTransportError, readHerdrSocketIdentity, requestHerdrUnixSocket, type HerdrRequestFunction, type HerdrRequestOptions, type HerdrSocketIdentity, type HerdrWireRequest } from "./herdr-socket-transport.js";
|
|
6
|
-
export declare const HERDR_EXACT_VERSION = "0.8.0";
|
|
7
|
-
export declare const HERDR_EXACT_PROTOCOL = 19;
|
|
8
8
|
export interface HerdrTtyDeviceIdentity {
|
|
9
9
|
device: string;
|
|
10
10
|
inode: string;
|
|
@@ -32,14 +32,17 @@ export interface HerdrSessionInfo {
|
|
|
32
32
|
export interface HerdrTerminalControlDiagnostics {
|
|
33
33
|
commands: string[];
|
|
34
34
|
selectedCommand?: string;
|
|
35
|
-
expectedVersion: string;
|
|
36
|
-
expectedProtocol: number;
|
|
37
35
|
sessions: HerdrSessionInfo[];
|
|
38
36
|
attempts: Array<{
|
|
39
37
|
session: string;
|
|
40
38
|
socketPath: string;
|
|
41
39
|
status: "ok" | "error";
|
|
42
40
|
paneCount?: number;
|
|
41
|
+
server?: HerdrServerObservation;
|
|
42
|
+
capabilities?: Array<{
|
|
43
|
+
paneId: string;
|
|
44
|
+
operations: HerdrOperationCapabilities;
|
|
45
|
+
}>;
|
|
43
46
|
skippedWithoutShellPid?: string[];
|
|
44
47
|
error?: string;
|
|
45
48
|
}>;
|
|
@@ -56,7 +59,7 @@ export declare class HerdrTerminalControlProvider implements TerminalControlProv
|
|
|
56
59
|
private readonly statSocket;
|
|
57
60
|
private readonly inspectTtyViewport;
|
|
58
61
|
private readonly diagnosticLog?;
|
|
59
|
-
private readonly
|
|
62
|
+
private readonly endpointObservations;
|
|
60
63
|
private requestSequence;
|
|
61
64
|
constructor(options?: {
|
|
62
65
|
command?: string;
|
|
@@ -83,13 +86,15 @@ export declare class HerdrTerminalControlProvider implements TerminalControlProv
|
|
|
83
86
|
scrollHistoryDown(terminal: TerminalEndpointRef): Promise<void>;
|
|
84
87
|
private logDiagnostic;
|
|
85
88
|
private discoverSessions;
|
|
89
|
+
private inspectCliSchema;
|
|
86
90
|
private probeCommand;
|
|
87
91
|
private inspectSession;
|
|
92
|
+
private probeReadCapability;
|
|
88
93
|
private assertCompatibleServer;
|
|
89
94
|
private invoke;
|
|
90
95
|
private resolveForInput;
|
|
91
96
|
private sendInput;
|
|
92
|
-
private
|
|
97
|
+
private requireEndpointObservation;
|
|
93
98
|
}
|
|
94
99
|
export declare function parseHerdrSessionList(output: string): HerdrSessionInfo[];
|
|
95
100
|
/** Translate the tmux-oriented bridge key vocabulary to Herdr key combos. */
|
|
@@ -2,12 +2,13 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isRecord } from "./value-guards.js";
|
|
5
|
+
import { matchesHerdrCompatibilityProfile, observeHerdrServer } from "./herdr-compatibility.js";
|
|
6
|
+
import { assertInputEndpointCapability, assertOperationAvailable, declaredHerdrInputCapabilities, herdrControlCapabilities, selectHerdrReadOperation } from "./herdr-operation-capabilities.js";
|
|
7
|
+
export { HERDR_EXACT_PROTOCOL, HERDR_EXACT_VERSION } from "./herdr-compatibility.js";
|
|
5
8
|
import { TERMINAL_HISTORY_SCROLL_DOWN_BYTES, TerminalControlInputNotSentError, TerminalControlUnavailableError } from "./terminal-control-provider.js";
|
|
6
9
|
import { createTerminalEndpointRef, herdrTerminalRouteKey, sameTerminalControlIncarnation, sameTerminalEndpointIdentity, terminalControlWithCapabilities, terminalEndpointFromControlRef } from "./terminal-control-ref.js";
|
|
7
10
|
import { HERDR_DEFAULT_TIMEOUT_MS, HerdrApiError, HerdrTransportError, readHerdrSocketIdentity, requestHerdrUnixSocket, sameHerdrSocketIdentity } from "./herdr-socket-transport.js";
|
|
8
11
|
export { HerdrApiError, HerdrTransportError, readHerdrSocketIdentity, requestHerdrUnixSocket } from "./herdr-socket-transport.js";
|
|
9
|
-
export const HERDR_EXACT_VERSION = "0.8.0";
|
|
10
|
-
export const HERDR_EXACT_PROTOCOL = 19;
|
|
11
12
|
const HERDR_MAX_READ_LINES = 1_000;
|
|
12
13
|
const HERDR_MAX_TTY_PATH_BYTES = 1_024;
|
|
13
14
|
const HERDR_MAX_TTY_VIEWPORT_DIMENSION = 65_535;
|
|
@@ -55,7 +56,7 @@ export class HerdrTerminalControlProvider {
|
|
|
55
56
|
statSocket;
|
|
56
57
|
inspectTtyViewport;
|
|
57
58
|
diagnosticLog;
|
|
58
|
-
|
|
59
|
+
endpointObservations = new WeakMap();
|
|
59
60
|
requestSequence = 0;
|
|
60
61
|
constructor(options = {}) {
|
|
61
62
|
this.commands = options.command
|
|
@@ -76,15 +77,14 @@ export class HerdrTerminalControlProvider {
|
|
|
76
77
|
const diagnostics = {
|
|
77
78
|
commands: [...this.commands],
|
|
78
79
|
selectedCommand: this.selectedCommand,
|
|
79
|
-
expectedVersion: HERDR_EXACT_VERSION,
|
|
80
|
-
expectedProtocol: HERDR_EXACT_PROTOCOL,
|
|
81
80
|
sessions: [],
|
|
82
81
|
attempts: [],
|
|
83
82
|
terminalCount: 0
|
|
84
83
|
};
|
|
85
84
|
let sessions;
|
|
85
|
+
let cli;
|
|
86
86
|
try {
|
|
87
|
-
sessions = this.discoverSessions();
|
|
87
|
+
({ sessions, cli } = this.discoverSessions());
|
|
88
88
|
diagnostics.selectedCommand = this.selectedCommand;
|
|
89
89
|
diagnostics.sessions = sessions;
|
|
90
90
|
}
|
|
@@ -100,13 +100,18 @@ export class HerdrTerminalControlProvider {
|
|
|
100
100
|
}
|
|
101
101
|
for (const session of sessions.filter((candidate) => candidate.running)) {
|
|
102
102
|
try {
|
|
103
|
-
const inspection = await this.inspectSession(session);
|
|
103
|
+
const inspection = await this.inspectSession(session, cli);
|
|
104
104
|
diagnostics.terminalCount += inspection.endpoints.length;
|
|
105
105
|
diagnostics.attempts.push({
|
|
106
106
|
session: session.name,
|
|
107
107
|
socketPath: session.socketPath,
|
|
108
108
|
status: "ok",
|
|
109
109
|
paneCount: inspection.endpoints.length,
|
|
110
|
+
server: inspection.server,
|
|
111
|
+
capabilities: inspection.endpoints.map((endpoint) => ({
|
|
112
|
+
paneId: herdrControlFromEndpoint(endpoint).paneId,
|
|
113
|
+
operations: this.requireEndpointObservation(endpoint).capabilities
|
|
114
|
+
})),
|
|
110
115
|
skippedWithoutShellPid: inspection.skippedWithoutShellPid
|
|
111
116
|
});
|
|
112
117
|
}
|
|
@@ -128,8 +133,10 @@ export class HerdrTerminalControlProvider {
|
|
|
128
133
|
selected_command: this.selectedCommand
|
|
129
134
|
});
|
|
130
135
|
let sessions;
|
|
136
|
+
let cli;
|
|
131
137
|
try {
|
|
132
|
-
sessions = this.discoverSessions()
|
|
138
|
+
({ sessions, cli } = this.discoverSessions());
|
|
139
|
+
sessions = sessions.filter((session) => session.running);
|
|
133
140
|
}
|
|
134
141
|
catch (error) {
|
|
135
142
|
if (error instanceof HerdrProviderUnavailableError) {
|
|
@@ -163,12 +170,12 @@ export class HerdrTerminalControlProvider {
|
|
|
163
170
|
});
|
|
164
171
|
const inspections = await Promise.all(sessions.map(async (session) => {
|
|
165
172
|
try {
|
|
166
|
-
return await this.inspectSession(session);
|
|
173
|
+
return await this.inspectSession(session, cli);
|
|
167
174
|
}
|
|
168
175
|
catch (error) {
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
176
|
+
// Malformed or changing server observations cannot authorize endpoints.
|
|
177
|
+
// Other providers in the registry may still discover their own terminals.
|
|
178
|
+
// Other live API failures remain fail-closed.
|
|
172
179
|
if (error instanceof HerdrCompatibilityError) {
|
|
173
180
|
this.logDiagnostic("herdr_terminal_discovery", {
|
|
174
181
|
stage: "session_inspection",
|
|
@@ -222,10 +229,9 @@ export class HerdrTerminalControlProvider {
|
|
|
222
229
|
toControlRef(terminal, capabilities = terminal.capabilities) {
|
|
223
230
|
const control = herdrControlFromEndpoint(terminal);
|
|
224
231
|
const next = terminalControlWithCapabilities(control, intersectCapabilities(intersectCapabilities(capabilities, terminal.capabilities), this.supportedCapabilities));
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
this.
|
|
228
|
-
}
|
|
232
|
+
const observation = this.endpointObservations.get(terminal);
|
|
233
|
+
if (observation)
|
|
234
|
+
this.endpointObservations.set(this.endpoint(next), observation);
|
|
229
235
|
return next;
|
|
230
236
|
}
|
|
231
237
|
async resolve(terminal) {
|
|
@@ -238,18 +244,18 @@ export class HerdrTerminalControlProvider {
|
|
|
238
244
|
if (matches.length !== 1) {
|
|
239
245
|
throw new Error(`Herdr terminal resource ${terminal.route.label} is ambiguous`);
|
|
240
246
|
}
|
|
241
|
-
const expectedSocketIdentity = this.
|
|
242
|
-
const candidateSocketIdentity = this.
|
|
247
|
+
const expectedSocketIdentity = this.endpointObservations.get(terminal)?.socketIdentity;
|
|
248
|
+
const candidateSocketIdentity = this.endpointObservations.get(matches[0])?.socketIdentity;
|
|
243
249
|
if (expectedSocketIdentity &&
|
|
244
250
|
(!candidateSocketIdentity || !sameHerdrSocketIdentity(expectedSocketIdentity, candidateSocketIdentity))) {
|
|
245
251
|
throw new Error(`Herdr server socket changed while resolving ${terminal.route.label}`);
|
|
246
252
|
}
|
|
247
|
-
const resolved = endpointWithCapabilities(matches[0], intersectCapabilities(terminal.capabilities,
|
|
253
|
+
const resolved = endpointWithCapabilities(matches[0], intersectCapabilities(terminal.capabilities, matches[0].capabilities));
|
|
248
254
|
const socketIdentity = candidateSocketIdentity;
|
|
249
255
|
if (!socketIdentity) {
|
|
250
256
|
throw new Error(`Herdr terminal resource ${terminal.route.label} is missing socket-incarnation evidence`);
|
|
251
257
|
}
|
|
252
|
-
this.
|
|
258
|
+
this.endpointObservations.set(resolved, this.requireEndpointObservation(matches[0]));
|
|
253
259
|
return resolved;
|
|
254
260
|
}
|
|
255
261
|
containsProcess(terminal, process, processes) {
|
|
@@ -285,15 +291,14 @@ export class HerdrTerminalControlProvider {
|
|
|
285
291
|
throw new Error("Herdr terminal stable resource or process anchor changed before viewport inspection");
|
|
286
292
|
}
|
|
287
293
|
const control = herdrControlFromEndpoint(resolved);
|
|
288
|
-
const socketIdentity = this.
|
|
294
|
+
const socketIdentity = this.requireEndpointObservation(resolved).socketIdentity;
|
|
289
295
|
const result = await this.invoke(control.socketPath, "session.snapshot", {}, { expectedSocketIdentity: socketIdentity });
|
|
290
296
|
if (result.type !== "session_snapshot" || !isRecord(result.snapshot)) {
|
|
291
297
|
throw new Error(`Herdr session ${control.session} returned an unexpected viewport snapshot`);
|
|
292
298
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
throw new HerdrCompatibilityError(`Herdr session ${control.session} viewport snapshot
|
|
296
|
-
`${HERDR_EXACT_VERSION}/protocol ${HERDR_EXACT_PROTOCOL}`);
|
|
299
|
+
const server = this.requireEndpointObservation(resolved).server;
|
|
300
|
+
if (!matchesHerdrCompatibilityProfile(result.snapshot, server)) {
|
|
301
|
+
throw new HerdrCompatibilityError(`Herdr session ${control.session} viewport snapshot changed server compatibility`);
|
|
297
302
|
}
|
|
298
303
|
if (!Array.isArray(result.snapshot.panes)) {
|
|
299
304
|
throw new Error(`Herdr session ${control.session} viewport snapshot panes are invalid`);
|
|
@@ -419,7 +424,7 @@ export class HerdrTerminalControlProvider {
|
|
|
419
424
|
throw new Error("Herdr terminal stable resource or process anchor changed before capture");
|
|
420
425
|
}
|
|
421
426
|
const control = herdrControlFromEndpoint(resolved);
|
|
422
|
-
const socketIdentity = this.
|
|
427
|
+
const socketIdentity = this.requireEndpointObservation(resolved).socketIdentity;
|
|
423
428
|
const requestedLines = Number.isFinite(options.scrollbackLines)
|
|
424
429
|
? Math.max(0, Math.floor(options.scrollbackLines ?? 200))
|
|
425
430
|
: 200;
|
|
@@ -432,8 +437,10 @@ export class HerdrTerminalControlProvider {
|
|
|
432
437
|
// means zero returned lines; an unbounded visible-source read instead
|
|
433
438
|
// returns the viewport alone, still within the transport response limit.
|
|
434
439
|
const viewportOnly = requestedLines === 0;
|
|
435
|
-
const
|
|
436
|
-
const format = options.preserveEscapes
|
|
440
|
+
const observation = this.requireEndpointObservation(resolved);
|
|
441
|
+
const { source, format, operation } = selectHerdrReadOperation(observation.capabilities, viewportOnly, options.preserveEscapes === true);
|
|
442
|
+
assertEndpointCapability(resolved, "screen_status");
|
|
443
|
+
assertOperationAvailable(observation.capabilities, operation);
|
|
437
444
|
const result = await this.invoke(control.socketPath, "pane.read", {
|
|
438
445
|
pane_id: control.paneId,
|
|
439
446
|
// Herdr's recent scrollback may be empty for a live Claude alternate
|
|
@@ -457,7 +464,7 @@ export class HerdrTerminalControlProvider {
|
|
|
457
464
|
return result.read.text;
|
|
458
465
|
}
|
|
459
466
|
async sendText(terminal, text) {
|
|
460
|
-
assertInputEndpointCapability(terminal, this.providerCapabilities, "text_delivery");
|
|
467
|
+
assertInputEndpointCapability(terminal, this.providerCapabilities, "text_delivery", this.endpointObservations.get(terminal)?.capabilities);
|
|
461
468
|
if (text.length === 0) {
|
|
462
469
|
return;
|
|
463
470
|
}
|
|
@@ -465,7 +472,7 @@ export class HerdrTerminalControlProvider {
|
|
|
465
472
|
await this.sendInput(resolved, { text });
|
|
466
473
|
}
|
|
467
474
|
async sendKeys(terminal, keys) {
|
|
468
|
-
assertInputEndpointCapability(terminal, this.providerCapabilities, "key_delivery");
|
|
475
|
+
assertInputEndpointCapability(terminal, this.providerCapabilities, "key_delivery", this.endpointObservations.get(terminal)?.capabilities);
|
|
469
476
|
let translated;
|
|
470
477
|
try {
|
|
471
478
|
translated = keys.map(translateHerdrKey);
|
|
@@ -484,7 +491,7 @@ export class HerdrTerminalControlProvider {
|
|
|
484
491
|
await this.sendInput(resolved, navigation ? { navigation } : { keys: translated });
|
|
485
492
|
}
|
|
486
493
|
async scrollHistoryDown(terminal) {
|
|
487
|
-
assertInputEndpointCapability(terminal, this.providerCapabilities, "key_delivery");
|
|
494
|
+
assertInputEndpointCapability(terminal, this.providerCapabilities, "key_delivery", this.endpointObservations.get(terminal)?.capabilities);
|
|
488
495
|
const resolved = await this.resolveForInput(terminal);
|
|
489
496
|
await this.sendInput(resolved, { navigation: "history-down" });
|
|
490
497
|
}
|
|
@@ -503,16 +510,19 @@ export class HerdrTerminalControlProvider {
|
|
|
503
510
|
throw new HerdrProviderUnavailableError(version.stderr || version.error?.message ||
|
|
504
511
|
`failed to run ${command} --version`);
|
|
505
512
|
}
|
|
506
|
-
const reportedVersion = version.stdout.trim();
|
|
507
|
-
if (reportedVersion !== `herdr ${HERDR_EXACT_VERSION}`) {
|
|
508
|
-
throw new HerdrProviderUnavailableError(`Herdr provider requires exact CLI ${HERDR_EXACT_VERSION}; ` +
|
|
509
|
-
`found ${reportedVersion || "unknown"}`);
|
|
510
|
-
}
|
|
511
513
|
const result = this.runCommand(command, ["session", "list", "--json"]);
|
|
512
514
|
if (result.status !== 0) {
|
|
513
515
|
throw new HerdrProviderUnavailableError(result.stderr || result.error?.message || "herdr session list failed");
|
|
514
516
|
}
|
|
515
|
-
|
|
517
|
+
const sessions = parseHerdrSessionList(result.stdout);
|
|
518
|
+
const cli = {
|
|
519
|
+
version: /^herdr\s+(\S+)$/u.exec(version.stdout.trim())?.[1],
|
|
520
|
+
schemaState: "unavailable"
|
|
521
|
+
};
|
|
522
|
+
if (sessions.some((session) => session.running)) {
|
|
523
|
+
this.inspectCliSchema(command, cli);
|
|
524
|
+
}
|
|
525
|
+
return { sessions, cli };
|
|
516
526
|
}
|
|
517
527
|
catch (error) {
|
|
518
528
|
if (error instanceof HerdrProviderUnavailableError) {
|
|
@@ -521,6 +531,28 @@ export class HerdrTerminalControlProvider {
|
|
|
521
531
|
throw new HerdrProviderUnavailableError(`Herdr provider is unavailable: ${describeError(error)}`, { cause: error });
|
|
522
532
|
}
|
|
523
533
|
}
|
|
534
|
+
inspectCliSchema(command, cli) {
|
|
535
|
+
let result;
|
|
536
|
+
try {
|
|
537
|
+
result = this.runCommand(command, ["api", "schema", "--json"]);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (result.status !== 0)
|
|
543
|
+
return;
|
|
544
|
+
// Successful but unreadable declarations must not be masked by a baseline.
|
|
545
|
+
cli.schemaState = "malformed";
|
|
546
|
+
if (Buffer.byteLength(result.stdout) > 10 * 1024 * 1024)
|
|
547
|
+
return;
|
|
548
|
+
try {
|
|
549
|
+
cli.schema = JSON.parse(result.stdout);
|
|
550
|
+
cli.schemaState = "available";
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
// Keep discovery and independently probed reads available.
|
|
554
|
+
}
|
|
555
|
+
}
|
|
524
556
|
probeCommand() {
|
|
525
557
|
if (this.selectedCommand) {
|
|
526
558
|
return {
|
|
@@ -536,14 +568,14 @@ export class HerdrTerminalControlProvider {
|
|
|
536
568
|
lastError = result.error;
|
|
537
569
|
continue;
|
|
538
570
|
}
|
|
539
|
-
// Once a candidate starts, pin it.
|
|
571
|
+
// Once a candidate starts, pin it. Any later
|
|
540
572
|
// failure must not silently switch to a different installation.
|
|
541
573
|
this.selectedCommand = command;
|
|
542
574
|
return { command, version: result };
|
|
543
575
|
}
|
|
544
576
|
throw new HerdrProviderUnavailableError(lastError?.message || "no Herdr executable candidate could be started", { cause: lastError });
|
|
545
577
|
}
|
|
546
|
-
async inspectSession(session) {
|
|
578
|
+
async inspectSession(session, cli) {
|
|
547
579
|
let socketIdentity;
|
|
548
580
|
try {
|
|
549
581
|
socketIdentity = this.statSocket(session.socketPath);
|
|
@@ -568,13 +600,16 @@ export class HerdrTerminalControlProvider {
|
|
|
568
600
|
});
|
|
569
601
|
throw error;
|
|
570
602
|
}
|
|
603
|
+
let profile;
|
|
571
604
|
try {
|
|
572
|
-
await this.assertCompatibleServer(session, socketIdentity);
|
|
605
|
+
profile = await this.assertCompatibleServer(session, socketIdentity);
|
|
573
606
|
this.logDiagnostic("herdr_session_observation", {
|
|
574
607
|
session: session.name,
|
|
575
608
|
socket_path: session.socketPath,
|
|
576
609
|
stage: "ping",
|
|
577
|
-
status: "available"
|
|
610
|
+
status: "available",
|
|
611
|
+
version: profile.version,
|
|
612
|
+
protocol: profile.protocol
|
|
578
613
|
});
|
|
579
614
|
}
|
|
580
615
|
catch (error) {
|
|
@@ -606,10 +641,9 @@ export class HerdrTerminalControlProvider {
|
|
|
606
641
|
if (result.type !== "session_snapshot" || !isRecord(result.snapshot)) {
|
|
607
642
|
throw new Error(`Herdr session ${session.name} returned an unexpected session.snapshot result`);
|
|
608
643
|
}
|
|
609
|
-
if (result.snapshot
|
|
610
|
-
result.snapshot.protocol !== HERDR_EXACT_PROTOCOL) {
|
|
644
|
+
if (!matchesHerdrCompatibilityProfile(result.snapshot, profile)) {
|
|
611
645
|
throw new HerdrCompatibilityError(`Herdr session ${session.name} snapshot is not exact ` +
|
|
612
|
-
`${
|
|
646
|
+
`${profile.version}/protocol ${profile.protocol}`);
|
|
613
647
|
}
|
|
614
648
|
if (!Array.isArray(result.snapshot.panes)) {
|
|
615
649
|
throw new Error(`Herdr session ${session.name} snapshot panes are invalid`);
|
|
@@ -678,6 +712,17 @@ export class HerdrTerminalControlProvider {
|
|
|
678
712
|
throw error;
|
|
679
713
|
}
|
|
680
714
|
}));
|
|
715
|
+
const inputCapabilities = declaredHerdrInputCapabilities(cli, profile);
|
|
716
|
+
const operations = await Promise.all(panes.map(async (pane, index) => {
|
|
717
|
+
if (!positiveInteger(processInfos[index]?.shellPid))
|
|
718
|
+
return undefined;
|
|
719
|
+
const [visibleText, visibleAnsi, detectionText] = await Promise.all([
|
|
720
|
+
this.probeReadCapability(session, pane, socketIdentity, "visible", "text"),
|
|
721
|
+
this.probeReadCapability(session, pane, socketIdentity, "visible", "ansi"),
|
|
722
|
+
this.probeReadCapability(session, pane, socketIdentity, "detection", "text")
|
|
723
|
+
]);
|
|
724
|
+
return { visibleText, visibleAnsi, detectionText, ...inputCapabilities };
|
|
725
|
+
}));
|
|
681
726
|
// A server restart can reuse a public pane route while representing a new
|
|
682
727
|
// PTY. Re-read the authoritative snapshot after process inspection and
|
|
683
728
|
// only join process data to a pane whose stable terminal identity and
|
|
@@ -699,8 +744,7 @@ export class HerdrTerminalControlProvider {
|
|
|
699
744
|
}
|
|
700
745
|
if (verifiedSnapshotResult.type !== "session_snapshot" ||
|
|
701
746
|
!isRecord(verifiedSnapshotResult.snapshot) ||
|
|
702
|
-
verifiedSnapshotResult.snapshot
|
|
703
|
-
verifiedSnapshotResult.snapshot.protocol !== HERDR_EXACT_PROTOCOL ||
|
|
747
|
+
!matchesHerdrCompatibilityProfile(verifiedSnapshotResult.snapshot, profile) ||
|
|
704
748
|
!Array.isArray(verifiedSnapshotResult.snapshot.panes)) {
|
|
705
749
|
throw new Error(`Herdr session ${session.name} changed during terminal discovery`);
|
|
706
750
|
}
|
|
@@ -756,8 +800,10 @@ export class HerdrTerminalControlProvider {
|
|
|
756
800
|
});
|
|
757
801
|
continue;
|
|
758
802
|
}
|
|
759
|
-
const endpoint = endpointFromPane(session, verifiedPane, processInfo,
|
|
760
|
-
this.
|
|
803
|
+
const endpoint = endpointFromPane(session, verifiedPane, processInfo, herdrControlCapabilities(operations[index]));
|
|
804
|
+
this.endpointObservations.set(endpoint, {
|
|
805
|
+
socketIdentity, server: profile, capabilities: operations[index]
|
|
806
|
+
});
|
|
761
807
|
endpoints.push(endpoint);
|
|
762
808
|
this.logDiagnostic("herdr_pane_endpoint_observation", {
|
|
763
809
|
session: session.name,
|
|
@@ -779,7 +825,40 @@ export class HerdrTerminalControlProvider {
|
|
|
779
825
|
endpoint_count: endpoints.length,
|
|
780
826
|
skipped_without_shell_pid: skippedWithoutShellPid
|
|
781
827
|
});
|
|
782
|
-
return { session, endpoints, skippedWithoutShellPid };
|
|
828
|
+
return { session, server: profile, endpoints, skippedWithoutShellPid };
|
|
829
|
+
}
|
|
830
|
+
async probeReadCapability(session, pane, socketIdentity, source, format) {
|
|
831
|
+
try {
|
|
832
|
+
const result = await this.invoke(session.socketPath, "pane.read", {
|
|
833
|
+
pane_id: pane.paneId, source, format, lines: 0
|
|
834
|
+
}, { expectedSocketIdentity: socketIdentity });
|
|
835
|
+
const read = result.read;
|
|
836
|
+
if (result.type !== "pane_read" || !isRecord(read) ||
|
|
837
|
+
read.pane_id !== pane.paneId || read.workspace_id !== pane.workspaceId ||
|
|
838
|
+
read.tab_id !== pane.tabId || read.source !== source || read.format !== format ||
|
|
839
|
+
read.text !== "" || !Number.isSafeInteger(read.revision) ||
|
|
840
|
+
read.revision < 0 || typeof read.truncated !== "boolean") {
|
|
841
|
+
return { state: "unknown", reason: "zero-line read returned invalid pane data", evidence: "none" };
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
state: "available",
|
|
845
|
+
reason: `pane.read accepted zero-line ${source}/${format} response`,
|
|
846
|
+
evidence: "runtime_probe"
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
const unsupported = error instanceof HerdrApiError && [
|
|
851
|
+
"invalid_request", "unknown_method", "method_not_found", "unsupported_method",
|
|
852
|
+
"unsupported_source", "unsupported_format"
|
|
853
|
+
].includes(error.code);
|
|
854
|
+
return {
|
|
855
|
+
state: unsupported ? "unsupported" : "unknown",
|
|
856
|
+
reason: error instanceof HerdrApiError
|
|
857
|
+
? `pane.read rejected ${source}/${format}: ${error.code}`
|
|
858
|
+
: `pane.read ${source}/${format} probe failed`,
|
|
859
|
+
evidence: unsupported ? "runtime_probe" : "none"
|
|
860
|
+
};
|
|
861
|
+
}
|
|
783
862
|
}
|
|
784
863
|
async assertCompatibleServer(session, socketIdentity) {
|
|
785
864
|
let result;
|
|
@@ -788,18 +867,15 @@ export class HerdrTerminalControlProvider {
|
|
|
788
867
|
}
|
|
789
868
|
catch (error) {
|
|
790
869
|
if (error instanceof HerdrApiError && error.code === "protocol_mismatch") {
|
|
791
|
-
throw new HerdrCompatibilityError(`Herdr session ${session.name} rejected protocol
|
|
870
|
+
throw new HerdrCompatibilityError(`Herdr session ${session.name} rejected the API protocol`);
|
|
792
871
|
}
|
|
793
872
|
throw error;
|
|
794
873
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
throw new HerdrCompatibilityError(`Herdr session ${session.name} requires exact server ` +
|
|
799
|
-
`${HERDR_EXACT_VERSION}/protocol ${HERDR_EXACT_PROTOCOL}; found ` +
|
|
800
|
-
`${String(result.version ?? "unknown")}/protocol ` +
|
|
801
|
-
`${String(result.protocol ?? "unknown")}`);
|
|
874
|
+
const profile = observeHerdrServer(result);
|
|
875
|
+
if (result.type !== "pong" || !profile) {
|
|
876
|
+
throw new HerdrCompatibilityError(`Herdr session ${session.name} returned malformed server version/protocol metadata`);
|
|
802
877
|
}
|
|
878
|
+
return profile;
|
|
803
879
|
}
|
|
804
880
|
async invoke(socketPath, method, params, options = {}) {
|
|
805
881
|
const request = {
|
|
@@ -831,9 +907,13 @@ export class HerdrTerminalControlProvider {
|
|
|
831
907
|
if (!sameTerminalControlIncarnation(terminal, resolved)) {
|
|
832
908
|
throw new Error("Herdr terminal stable resource or process anchor changed before input");
|
|
833
909
|
}
|
|
910
|
+
const observation = this.requireEndpointObservation(resolved);
|
|
911
|
+
assertOperationAvailable(observation.capabilities, "textInput");
|
|
912
|
+
assertOperationAvailable(observation.capabilities, "keyInput");
|
|
913
|
+
assertEndpointCapability(resolved, "send_keys");
|
|
834
914
|
return {
|
|
835
915
|
control: herdrControlFromEndpoint(resolved),
|
|
836
|
-
|
|
916
|
+
observation
|
|
837
917
|
};
|
|
838
918
|
}
|
|
839
919
|
catch (error) {
|
|
@@ -843,10 +923,18 @@ export class HerdrTerminalControlProvider {
|
|
|
843
923
|
}
|
|
844
924
|
}
|
|
845
925
|
async sendInput(resolved, params) {
|
|
846
|
-
const { control,
|
|
926
|
+
const { control, observation } = resolved;
|
|
927
|
+
const { socketIdentity } = observation;
|
|
847
928
|
// pane.send_input.text is bracketed paste. Only closed fixed navigation
|
|
848
929
|
// uses pane.send_text's direct PTY bytes; callers cannot supply raw data.
|
|
849
930
|
const method = "navigation" in params ? "pane.send_text" : "pane.send_input";
|
|
931
|
+
try {
|
|
932
|
+
assertOperationAvailable(observation.capabilities, "navigation" in params ? "rawTextInput" :
|
|
933
|
+
"text" in params ? "textInput" : "keyInput");
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
throw new TerminalControlInputNotSentError(describeError(error));
|
|
937
|
+
}
|
|
850
938
|
const input = "navigation" in params
|
|
851
939
|
? { text: HERDR_NAVIGATION_KEY_BYTES[params.navigation] } : params;
|
|
852
940
|
try {
|
|
@@ -861,21 +949,21 @@ export class HerdrTerminalControlProvider {
|
|
|
861
949
|
}
|
|
862
950
|
}
|
|
863
951
|
catch (error) {
|
|
864
|
-
if (inputDefinitelyNotSent(error)) {
|
|
952
|
+
if (inputDefinitelyNotSent(error, observation.server.baselineKnown)) {
|
|
865
953
|
throw new TerminalControlInputNotSentError(`Herdr input was not sent: ${describeError(error)}`);
|
|
866
954
|
}
|
|
867
|
-
// Request ids
|
|
868
|
-
//
|
|
955
|
+
// Request ids provide correlation, not replay authority. A post-connect
|
|
956
|
+
// timeout, EOF, malformed response, or lost ACK must
|
|
869
957
|
// remain uncertain and must never be retried here.
|
|
870
958
|
throw error;
|
|
871
959
|
}
|
|
872
960
|
}
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
if (!
|
|
961
|
+
requireEndpointObservation(terminal) {
|
|
962
|
+
const observation = this.endpointObservations.get(terminal);
|
|
963
|
+
if (!observation) {
|
|
876
964
|
throw new Error(`Herdr terminal resource ${terminal.route.label} is missing socket-incarnation evidence`);
|
|
877
965
|
}
|
|
878
|
-
return
|
|
966
|
+
return observation;
|
|
879
967
|
}
|
|
880
968
|
}
|
|
881
969
|
export function parseHerdrSessionList(output) {
|
|
@@ -1119,27 +1207,18 @@ function assertEndpointCapability(terminal, capability) {
|
|
|
1119
1207
|
throw new Error(`Herdr terminal does not permit ${capability}`);
|
|
1120
1208
|
}
|
|
1121
1209
|
}
|
|
1122
|
-
function assertInputEndpointCapability(terminal, providerCapabilities, providerCapability) {
|
|
1123
|
-
try {
|
|
1124
|
-
assertEndpointCapability(terminal, "send_keys");
|
|
1125
|
-
}
|
|
1126
|
-
catch (error) {
|
|
1127
|
-
throw new TerminalControlInputNotSentError(describeError(error));
|
|
1128
|
-
}
|
|
1129
|
-
if (!providerCapabilities.includes(providerCapability)) {
|
|
1130
|
-
throw new TerminalControlInputNotSentError(`Herdr provider does not support ${providerCapability}`);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
1210
|
function assertProviderCapability(capabilities, capability) {
|
|
1134
1211
|
if (!capabilities.includes(capability)) {
|
|
1135
1212
|
throw new Error(`Herdr provider does not support ${capability}`);
|
|
1136
1213
|
}
|
|
1137
1214
|
}
|
|
1138
|
-
function inputDefinitelyNotSent(error) {
|
|
1215
|
+
function inputDefinitelyNotSent(error, baselineKnown) {
|
|
1139
1216
|
if (error instanceof HerdrTransportError) {
|
|
1140
1217
|
return error.definitelyNotSent;
|
|
1141
1218
|
}
|
|
1142
|
-
|
|
1219
|
+
// Unknown releases may reuse an error code after an input effect. A schema
|
|
1220
|
+
// declaration does not establish the server's failure/queue semantics.
|
|
1221
|
+
return baselineKnown && error instanceof HerdrApiError && new Set([
|
|
1143
1222
|
"invalid_request",
|
|
1144
1223
|
"invalid_key",
|
|
1145
1224
|
"pane_not_found",
|