dsh-agy-cli 0.0.0-stage → 0.12.0
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/LICENSE +30 -0
- package/README.en.md +27 -0
- package/README.md +94 -2
- package/agents/dsh-agy-image-view.md +17 -0
- package/agents/dsh-agy-mcp-executor.md +32 -0
- package/agents/dsh-agy-read-only.md +20 -0
- package/agents/dsh-agy-tool-free.md +16 -0
- package/agents/dsh-agy-workspace-write.md +23 -0
- package/bin/dsh-agy-cli.js +9 -0
- package/bin/win32-x64/agy-launcher.exe +0 -0
- package/cordis.patch.yml +16 -0
- package/dist/client.js +4259 -0
- package/lib/agent-installer.d.ts +66 -0
- package/lib/agent-installer.js +147 -0
- package/lib/agent-presets.d.ts +42 -0
- package/lib/agent-presets.js +130 -0
- package/lib/agents-cli.d.ts +3 -0
- package/lib/agents-cli.js +110 -0
- package/lib/agy/diagnostics.d.ts +29 -0
- package/lib/agy/diagnostics.js +109 -0
- package/lib/agy/errors.d.ts +20 -0
- package/lib/agy/errors.js +77 -0
- package/lib/agy/experimental-transport.d.ts +79 -0
- package/lib/agy/experimental-transport.js +612 -0
- package/lib/agy/keepalive.d.ts +140 -0
- package/lib/agy/keepalive.js +424 -0
- package/lib/agy/limiter.d.ts +32 -0
- package/lib/agy/limiter.js +111 -0
- package/lib/agy/log.d.ts +214 -0
- package/lib/agy/log.js +503 -0
- package/lib/agy/models.d.ts +49 -0
- package/lib/agy/models.js +203 -0
- package/lib/agy/overrides.d.ts +17 -0
- package/lib/agy/overrides.js +73 -0
- package/lib/agy/parser.d.ts +102 -0
- package/lib/agy/parser.js +492 -0
- package/lib/agy/persistent-transport.d.ts +101 -0
- package/lib/agy/persistent-transport.js +753 -0
- package/lib/agy/process.d.ts +144 -0
- package/lib/agy/process.js +452 -0
- package/lib/agy/redact.d.ts +3 -0
- package/lib/agy/redact.js +24 -0
- package/lib/agy/stream-protocol.d.ts +50 -0
- package/lib/agy/stream-protocol.js +156 -0
- package/lib/agy/windows-launcher.d.ts +21 -0
- package/lib/agy/windows-launcher.js +76 -0
- package/lib/diagnostics.d.ts +78 -0
- package/lib/diagnostics.js +162 -0
- package/lib/doctor.d.ts +138 -0
- package/lib/doctor.js +923 -0
- package/lib/dsh/context.d.ts +133 -0
- package/lib/dsh/context.js +352 -0
- package/lib/dsh-home.d.ts +6 -0
- package/lib/dsh-home.js +11 -0
- package/lib/index.d.ts +43 -0
- package/lib/index.js +256 -0
- package/lib/mcp/agy-registration.d.ts +70 -0
- package/lib/mcp/agy-registration.js +245 -0
- package/lib/mcp/bridge.d.ts +73 -0
- package/lib/mcp/bridge.js +295 -0
- package/lib/mcp/endpoint.d.ts +41 -0
- package/lib/mcp/endpoint.js +142 -0
- package/lib/mcp/protocol.d.ts +107 -0
- package/lib/mcp/protocol.js +197 -0
- package/lib/mcp/tool-projection.d.ts +64 -0
- package/lib/mcp/tool-projection.js +168 -0
- package/lib/provider/agy.d.ts +153 -0
- package/lib/provider/agy.js +2159 -0
- package/lib/provider/config.d.ts +226 -0
- package/lib/provider/config.js +294 -0
- package/lib/provider/error-codes.d.ts +22 -0
- package/lib/provider/error-codes.js +29 -0
- package/lib/provider/image-bridge.d.ts +60 -0
- package/lib/provider/image-bridge.js +290 -0
- package/lib/provider/mock.d.ts +15 -0
- package/lib/provider/mock.js +78 -0
- package/lib/provider/prompt-budget.d.ts +45 -0
- package/lib/provider/prompt-budget.js +596 -0
- package/lib/provider/serialize.d.ts +15 -0
- package/lib/provider/serialize.js +82 -0
- package/lib/provider/tool-protocol.d.ts +80 -0
- package/lib/provider/tool-protocol.js +848 -0
- package/lib/provider/tool-schema-file.d.ts +11 -0
- package/lib/provider/tool-schema-file.js +27 -0
- package/lib/session/file-store.d.ts +56 -0
- package/lib/session/file-store.js +186 -0
- package/lib/session/store.d.ts +49 -0
- package/lib/session/store.js +62 -0
- package/lib/web/router.d.ts +19 -0
- package/lib/web/router.js +906 -0
- package/package.json +112 -4
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export type ProcessTermination = 'completed' | 'non-zero' | 'signaled' | 'aborted' | 'timeout' | 'idle-timeout' | 'output-limit';
|
|
2
|
+
/**
|
|
3
|
+
* How `runProcess` retires the stdin pipe after writing the request frame.
|
|
4
|
+
*
|
|
5
|
+
* `'close'` ends stdin immediately (the historical behaviour). AGY's print mode
|
|
6
|
+
* reads that EOF as "there is no next turn", shuts its store manager down and
|
|
7
|
+
* cancels the turn still being generated — measured as
|
|
8
|
+
* `Print mode: stream input closed after 1 turn(s)` followed by
|
|
9
|
+
* `CORTEX_STEP_STATUS_CANCELED` on every step, which surfaces to the user as
|
|
10
|
+
* text that appears and is then rolled back.
|
|
11
|
+
*
|
|
12
|
+
* `'hold'` keeps the pipe open and closes it only when the run is over (a
|
|
13
|
+
* `result` line arrived, or the process exited). Nothing in AGY's stream-json
|
|
14
|
+
* contract requires a follow-up frame, so holding costs nothing but the
|
|
15
|
+
* lifetime of one pipe — and `idleTimeoutMs`/`timeoutMs` still kill a run that
|
|
16
|
+
* goes silent, so a turn can never hang forever on an unclosed pipe.
|
|
17
|
+
*/
|
|
18
|
+
export type AgyStdinMode = 'close' | 'hold';
|
|
19
|
+
/** Maps a termination to the code the retry classifier sees, so a silence kill
|
|
20
|
+
* and a wall-clock kill are distinguishable in telemetry without changing the
|
|
21
|
+
* DSH-facing code (`TIMEOUT` either way). */
|
|
22
|
+
export declare const AGY_REASONING_EFFORTS: readonly ["low", "medium", "high"];
|
|
23
|
+
export type AgyReasoningEffort = typeof AGY_REASONING_EFFORTS[number];
|
|
24
|
+
export declare const AGY_EXECUTION_MODES: readonly ["plan", "accept-edits"];
|
|
25
|
+
export type AgyExecutionMode = typeof AGY_EXECUTION_MODES[number];
|
|
26
|
+
export declare function isAgyReasoningEffort(value: unknown): value is AgyReasoningEffort;
|
|
27
|
+
export declare function isAgyExecutionMode(value: unknown): value is AgyExecutionMode;
|
|
28
|
+
export declare class AgyProcessError extends Error {
|
|
29
|
+
readonly code: 'ABORTED' | 'SPAWN_FAILED' | 'INPUT_FAILED' | 'INPUT_TOO_LARGE' | 'OUTPUT_HANDLER_FAILED' | 'OUTPUT_LIMIT';
|
|
30
|
+
readonly diagnostic?: AgyProcessDiagnostic | undefined;
|
|
31
|
+
constructor(message: string, code: 'ABORTED' | 'SPAWN_FAILED' | 'INPUT_FAILED' | 'INPUT_TOO_LARGE' | 'OUTPUT_HANDLER_FAILED' | 'OUTPUT_LIMIT', options?: ErrorOptions, diagnostic?: AgyProcessDiagnostic | undefined);
|
|
32
|
+
}
|
|
33
|
+
export type AgyProcessDiagnosticStage = 'prepare' | 'spawn' | 'stdin' | 'stdout-handler';
|
|
34
|
+
/**
|
|
35
|
+
* Safe process-failure metadata; never contains the prompt, the response, or paths.
|
|
36
|
+
*
|
|
37
|
+
* `stderrSummary` is the one deliberate exception on the stderr side. When AGY
|
|
38
|
+
* dies before the first stdin write, the EOF is only a symptom: the cause is a
|
|
39
|
+
* line AGY already printed to stderr (an eligibility failure, a rejected model,
|
|
40
|
+
* a broken upstream connection). Without that line every such failure reads as
|
|
41
|
+
* the same uninformative `(Error:EOF stage=stdin)`. It is therefore carried as a
|
|
42
|
+
* single redacted, capped line, and only on the stdin stage.
|
|
43
|
+
*/
|
|
44
|
+
export interface AgyProcessDiagnostic {
|
|
45
|
+
readonly stage: AgyProcessDiagnosticStage;
|
|
46
|
+
readonly errorName?: string;
|
|
47
|
+
readonly errorCode?: string;
|
|
48
|
+
readonly lineNumber?: number;
|
|
49
|
+
readonly lineLength?: number;
|
|
50
|
+
readonly lineHash?: string;
|
|
51
|
+
readonly stdoutLineCount?: number;
|
|
52
|
+
readonly stdoutBytes?: number;
|
|
53
|
+
readonly stderrBytes?: number;
|
|
54
|
+
readonly stderrSummary?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Pick the most informative stderr line for a failed launch: AGY writes its
|
|
58
|
+
* fatal reason last, after progress chatter, so the last non-empty line wins.
|
|
59
|
+
*/
|
|
60
|
+
export declare function stderrSummaryOf(stderr: string): string | undefined;
|
|
61
|
+
/** Find process diagnostics through the LlmError -> cause chain. */
|
|
62
|
+
export declare function agyProcessDiagnosticOf(error: unknown): AgyProcessDiagnostic | undefined;
|
|
63
|
+
export interface ProcessRequest {
|
|
64
|
+
executable: string;
|
|
65
|
+
args: readonly string[];
|
|
66
|
+
/** Optional stdin payload written once before the stream is closed. */
|
|
67
|
+
stdin?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Whether to end stdin right after writing the payload (default) or keep it
|
|
70
|
+
* open until the run finishes. See `AgyStdinMode`.
|
|
71
|
+
*/
|
|
72
|
+
stdinMode?: AgyStdinMode;
|
|
73
|
+
cwd?: string;
|
|
74
|
+
env?: NodeJS.ProcessEnv;
|
|
75
|
+
/** Hard wall-clock ceiling for the whole run. Omitted means unbounded. */
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Inactivity budget: the run is killed when no stdout/stderr line arrives for
|
|
79
|
+
* this long. Re-armed on every delivered line, so a turn that keeps producing
|
|
80
|
+
* output can run past `timeoutMs` without being cut off. Omitted means only
|
|
81
|
+
* the wall-clock ceiling applies.
|
|
82
|
+
*/
|
|
83
|
+
idleTimeoutMs?: number;
|
|
84
|
+
/** Maximum captured stdout bytes; omitted means unlimited. */
|
|
85
|
+
maxStdoutBytes?: number;
|
|
86
|
+
/** Maximum captured stderr bytes; omitted means unlimited. */
|
|
87
|
+
maxStderrBytes?: number;
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
/** Called synchronously for each complete stdout line as it arrives. */
|
|
90
|
+
onStdoutLine?: (line: string) => void;
|
|
91
|
+
/** Use the Windows no-console bridge for a console-subsystem executable. */
|
|
92
|
+
windowsNoConsole?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface ProcessResult {
|
|
95
|
+
exitCode: number | null;
|
|
96
|
+
signal: NodeJS.Signals | null;
|
|
97
|
+
termination: ProcessTermination;
|
|
98
|
+
stdoutLines: readonly string[];
|
|
99
|
+
stderr: string;
|
|
100
|
+
durationMs: number;
|
|
101
|
+
/** Epoch ms of the last stdout/stderr chunk; used to report an idle kill. */
|
|
102
|
+
lastOutputAtMs?: number;
|
|
103
|
+
}
|
|
104
|
+
export interface AgyRequest extends Omit<ProcessRequest, 'executable' | 'args'> {
|
|
105
|
+
executable?: string;
|
|
106
|
+
prompt: string;
|
|
107
|
+
agent?: string;
|
|
108
|
+
model?: string;
|
|
109
|
+
conversation?: string;
|
|
110
|
+
reasoningEffort?: AgyReasoningEffort;
|
|
111
|
+
/** Explicit AGY directories made available to the selected Agent. */
|
|
112
|
+
addDirs?: readonly string[];
|
|
113
|
+
/** Bounded AGY execution mode selected by an Agent preset. */
|
|
114
|
+
mode?: AgyExecutionMode;
|
|
115
|
+
/** Disable slash commands for bundled non-interactive presets. */
|
|
116
|
+
disableSlashCommands?: boolean;
|
|
117
|
+
/** Absolute temporary JSON Schema path passed to AGY's final-result validator. */
|
|
118
|
+
jsonSchemaPath?: string;
|
|
119
|
+
/** Maximum UTF-8 bytes allowed for one encoded stream-json user frame. */
|
|
120
|
+
maxInputFrameBytes?: number;
|
|
121
|
+
/**
|
|
122
|
+
* How long to keep the request pipe open. AGY's print mode treats an
|
|
123
|
+
* immediate EOF as "no next turn" and cancels the turn it is still
|
|
124
|
+
* generating, so the adapter holds the pipe by default; see `AgyStdinMode`.
|
|
125
|
+
*/
|
|
126
|
+
stdinMode?: AgyStdinMode;
|
|
127
|
+
}
|
|
128
|
+
export declare function defaultAgyCommand(): 'agy.exe' | 'agy';
|
|
129
|
+
/**
|
|
130
|
+
* Resolve an explicit AGY path, AGY_PATH/AGY_EXECUTABLE, or an executable on
|
|
131
|
+
* PATH. Returning the bare command in the final fallback lets spawn produce
|
|
132
|
+
* the platform-native diagnostic if PATH changes between discovery and run.
|
|
133
|
+
*/
|
|
134
|
+
export declare function resolveAgyExecutable(explicit?: string): string;
|
|
135
|
+
/** Build AGY's stdin stream-json argv without invoking a shell. */
|
|
136
|
+
export declare function buildAgyArgs(request: Pick<AgyRequest, 'prompt' | 'agent' | 'model' | 'conversation' | 'reasoningEffort' | 'addDirs' | 'mode' | 'disableSlashCommands' | 'jsonSchemaPath'>): string[];
|
|
137
|
+
/**
|
|
138
|
+
* Run a process with piped output and deterministic lifecycle handling.
|
|
139
|
+
* stdout is delivered line-by-line while the process is running; the result
|
|
140
|
+
* also retains the lines for tests and adapters that do not need live output.
|
|
141
|
+
*/
|
|
142
|
+
export declare function runProcess(request: ProcessRequest): Promise<ProcessResult>;
|
|
143
|
+
export declare function runAgyProcess(request: AgyRequest): Promise<ProcessResult>;
|
|
144
|
+
//# sourceMappingURL=process.d.ts.map
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { delimiter, join, resolve } from 'node:path';
|
|
5
|
+
import { redactText } from './redact.js';
|
|
6
|
+
import { AgyStreamProtocolError, encodeAgyUserMessage } from './stream-protocol.js';
|
|
7
|
+
import { buildWindowsNoConsoleLaunch } from './windows-launcher.js';
|
|
8
|
+
/** Maps a termination to the code the retry classifier sees, so a silence kill
|
|
9
|
+
* and a wall-clock kill are distinguishable in telemetry without changing the
|
|
10
|
+
* DSH-facing code (`TIMEOUT` either way). */
|
|
11
|
+
export const AGY_REASONING_EFFORTS = ['low', 'medium', 'high'];
|
|
12
|
+
export const AGY_EXECUTION_MODES = ['plan', 'accept-edits'];
|
|
13
|
+
export function isAgyReasoningEffort(value) {
|
|
14
|
+
return typeof value === 'string'
|
|
15
|
+
&& AGY_REASONING_EFFORTS.includes(value);
|
|
16
|
+
}
|
|
17
|
+
export function isAgyExecutionMode(value) {
|
|
18
|
+
return typeof value === 'string'
|
|
19
|
+
&& AGY_EXECUTION_MODES.includes(value);
|
|
20
|
+
}
|
|
21
|
+
export class AgyProcessError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
diagnostic;
|
|
24
|
+
constructor(message, code, options, diagnostic) {
|
|
25
|
+
super(message, options);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.diagnostic = diagnostic;
|
|
28
|
+
this.name = 'AgyProcessError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Longest stderr excerpt carried on a stdin failure. */
|
|
32
|
+
const STDERR_SUMMARY_MAX_LENGTH = 200;
|
|
33
|
+
/**
|
|
34
|
+
* Pick the most informative stderr line for a failed launch: AGY writes its
|
|
35
|
+
* fatal reason last, after progress chatter, so the last non-empty line wins.
|
|
36
|
+
*/
|
|
37
|
+
export function stderrSummaryOf(stderr) {
|
|
38
|
+
const lines = stderr.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0);
|
|
39
|
+
const chosen = lines.at(-1);
|
|
40
|
+
if (chosen === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
return redactText(chosen, STDERR_SUMMARY_MAX_LENGTH);
|
|
43
|
+
}
|
|
44
|
+
function errorNameOf(error) {
|
|
45
|
+
return error instanceof Error && error.name.length > 0 ? error.name.slice(0, 128) : undefined;
|
|
46
|
+
}
|
|
47
|
+
function errorCodeOf(error) {
|
|
48
|
+
if (typeof error !== 'object' || error === null)
|
|
49
|
+
return undefined;
|
|
50
|
+
const code = error.code;
|
|
51
|
+
return typeof code === 'string' && code.length > 0 ? code.slice(0, 128) : undefined;
|
|
52
|
+
}
|
|
53
|
+
function integerFieldOf(error, key) {
|
|
54
|
+
if (typeof error !== 'object' || error === null)
|
|
55
|
+
return undefined;
|
|
56
|
+
const value = error[key];
|
|
57
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1 ? value : undefined;
|
|
58
|
+
}
|
|
59
|
+
function shortLineHash(line) {
|
|
60
|
+
return createHash('sha256').update(line, 'utf8').digest('hex').slice(0, 16);
|
|
61
|
+
}
|
|
62
|
+
function processDiagnosticFor(error, stage, details = {}) {
|
|
63
|
+
const line = details.line;
|
|
64
|
+
const errorName = errorNameOf(error);
|
|
65
|
+
const errorCode = errorCodeOf(error);
|
|
66
|
+
const lineNumber = integerFieldOf(error, 'lineNumber');
|
|
67
|
+
return {
|
|
68
|
+
stage,
|
|
69
|
+
...(errorName === undefined ? {} : { errorName }),
|
|
70
|
+
...(errorCode === undefined ? {} : { errorCode }),
|
|
71
|
+
...(lineNumber === undefined ? {} : { lineNumber }),
|
|
72
|
+
...(line === undefined ? {} : {
|
|
73
|
+
lineLength: line.length,
|
|
74
|
+
lineHash: shortLineHash(line),
|
|
75
|
+
}),
|
|
76
|
+
...(details.stdoutLineCount === undefined ? {} : { stdoutLineCount: details.stdoutLineCount }),
|
|
77
|
+
...(details.stdoutBytes === undefined ? {} : { stdoutBytes: details.stdoutBytes }),
|
|
78
|
+
...(details.stderrBytes === undefined ? {} : { stderrBytes: details.stderrBytes }),
|
|
79
|
+
...(details.stderrSummary === undefined ? {} : { stderrSummary: details.stderrSummary }),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function diagnosticLabel(diagnostic) {
|
|
83
|
+
if (diagnostic === undefined)
|
|
84
|
+
return '';
|
|
85
|
+
const cause = [diagnostic.errorName, diagnostic.errorCode].filter(Boolean).join(':');
|
|
86
|
+
const line = diagnostic.lineNumber === undefined ? '' : ` line=${diagnostic.lineNumber}`;
|
|
87
|
+
const length = diagnostic.lineLength === undefined ? '' : ` length=${diagnostic.lineLength}`;
|
|
88
|
+
const stage = `stage=${diagnostic.stage}`;
|
|
89
|
+
const stderr = diagnostic.stderrSummary === undefined ? '' : ` stderr=${JSON.stringify(diagnostic.stderrSummary)}`;
|
|
90
|
+
return ` (${[cause || undefined, stage, line.trim(), length.trim(), stderr.trim()].filter(Boolean).join(' ')})`;
|
|
91
|
+
}
|
|
92
|
+
/** Find process diagnostics through the LlmError -> cause chain. */
|
|
93
|
+
export function agyProcessDiagnosticOf(error) {
|
|
94
|
+
let current = error;
|
|
95
|
+
for (let depth = 0; depth < 4 && current !== undefined; depth += 1) {
|
|
96
|
+
if (current instanceof AgyProcessError && current.diagnostic !== undefined)
|
|
97
|
+
return current.diagnostic;
|
|
98
|
+
if (!(current instanceof Error))
|
|
99
|
+
return undefined;
|
|
100
|
+
current = current.cause;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
export function defaultAgyCommand() {
|
|
105
|
+
return process.platform === 'win32' ? 'agy.exe' : 'agy';
|
|
106
|
+
}
|
|
107
|
+
function terminateProcessTree(child) {
|
|
108
|
+
const pid = child.pid;
|
|
109
|
+
if (pid === undefined) {
|
|
110
|
+
child.kill();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (process.platform === 'win32') {
|
|
114
|
+
const killer = spawn('taskkill', ['/pid', String(pid), '/t', '/f'], {
|
|
115
|
+
shell: false,
|
|
116
|
+
windowsHide: true,
|
|
117
|
+
stdio: 'ignore',
|
|
118
|
+
});
|
|
119
|
+
killer.once('error', () => child.kill());
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
process.kill(-pid, 'SIGKILL');
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
child.kill('SIGKILL');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolve an explicit AGY path, AGY_PATH/AGY_EXECUTABLE, or an executable on
|
|
131
|
+
* PATH. Returning the bare command in the final fallback lets spawn produce
|
|
132
|
+
* the platform-native diagnostic if PATH changes between discovery and run.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveAgyExecutable(explicit) {
|
|
135
|
+
const configured = explicit?.trim()
|
|
136
|
+
|| process.env.AGY_PATH?.trim()
|
|
137
|
+
|| process.env.AGY_EXECUTABLE?.trim();
|
|
138
|
+
if (configured !== undefined && configured.length > 0)
|
|
139
|
+
return resolve(configured);
|
|
140
|
+
const command = defaultAgyCommand();
|
|
141
|
+
const pathEntries = (process.env.PATH ?? '').split(delimiter).filter(Boolean);
|
|
142
|
+
const suffixes = process.platform === 'win32'
|
|
143
|
+
? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
|
|
144
|
+
: [''];
|
|
145
|
+
for (const directory of pathEntries) {
|
|
146
|
+
const direct = join(directory, command);
|
|
147
|
+
if (existsSync(direct))
|
|
148
|
+
return direct;
|
|
149
|
+
if (process.platform === 'win32' && !command.includes('.')) {
|
|
150
|
+
for (const suffix of suffixes) {
|
|
151
|
+
const candidate = join(directory, `${command}${suffix.toLowerCase()}`);
|
|
152
|
+
if (existsSync(candidate))
|
|
153
|
+
return candidate;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return command;
|
|
158
|
+
}
|
|
159
|
+
/** Build AGY's stdin stream-json argv without invoking a shell. */
|
|
160
|
+
export function buildAgyArgs(request) {
|
|
161
|
+
if (request.reasoningEffort !== undefined && !isAgyReasoningEffort(request.reasoningEffort)) {
|
|
162
|
+
throw new TypeError('AGY reasoning effort must be one of: low, medium, high');
|
|
163
|
+
}
|
|
164
|
+
if (request.mode !== undefined && !isAgyExecutionMode(request.mode)) {
|
|
165
|
+
throw new TypeError('AGY execution mode must be one of: plan, accept-edits');
|
|
166
|
+
}
|
|
167
|
+
if (request.addDirs?.some(directory => typeof directory !== 'string' || directory.trim().length === 0)) {
|
|
168
|
+
throw new TypeError('AGY add-dir entries must be non-empty paths');
|
|
169
|
+
}
|
|
170
|
+
if (request.jsonSchemaPath !== undefined && request.jsonSchemaPath.trim().length === 0) {
|
|
171
|
+
throw new TypeError('AGY json-schema path must be non-empty');
|
|
172
|
+
}
|
|
173
|
+
return [
|
|
174
|
+
'-p', '',
|
|
175
|
+
...(request.agent === undefined ? [] : ['--agent', request.agent]),
|
|
176
|
+
...(request.model === undefined ? [] : ['--model', request.model]),
|
|
177
|
+
...(request.conversation === undefined ? [] : ['--conversation', request.conversation]),
|
|
178
|
+
...(request.reasoningEffort === undefined ? [] : ['--effort', request.reasoningEffort]),
|
|
179
|
+
...(request.addDirs?.flatMap(directory => ['--add-dir', directory]) ?? []),
|
|
180
|
+
...(request.mode === undefined ? [] : ['--mode', request.mode]),
|
|
181
|
+
...(request.disableSlashCommands === true ? ['--disable-slash-commands'] : []),
|
|
182
|
+
...(request.jsonSchemaPath === undefined ? [] : ['--json-schema', request.jsonSchemaPath]),
|
|
183
|
+
'--input-format', 'stream-json',
|
|
184
|
+
'--output-format', 'stream-json',
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
function splitLines(buffer, final) {
|
|
188
|
+
const parts = buffer.split(/\r?\n/);
|
|
189
|
+
const remainder = parts.pop() ?? '';
|
|
190
|
+
if (final && remainder.length > 0) {
|
|
191
|
+
parts.push(remainder.replace(/\r$/, ''));
|
|
192
|
+
return { lines: parts, remainder: '' };
|
|
193
|
+
}
|
|
194
|
+
return { lines: parts, remainder };
|
|
195
|
+
}
|
|
196
|
+
function byteLength(value) {
|
|
197
|
+
return Buffer.byteLength(value, 'utf8');
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Run a process with piped output and deterministic lifecycle handling.
|
|
201
|
+
* stdout is delivered line-by-line while the process is running; the result
|
|
202
|
+
* also retains the lines for tests and adapters that do not need live output.
|
|
203
|
+
*/
|
|
204
|
+
export function runProcess(request) {
|
|
205
|
+
if (request.signal?.aborted) {
|
|
206
|
+
return Promise.reject(new AgyProcessError('AGY process aborted before start', 'ABORTED'));
|
|
207
|
+
}
|
|
208
|
+
const startedAt = Date.now();
|
|
209
|
+
let launch;
|
|
210
|
+
try {
|
|
211
|
+
launch = request.windowsNoConsole === true
|
|
212
|
+
? buildWindowsNoConsoleLaunch(request.executable, request.args)
|
|
213
|
+
: { executable: request.executable, args: request.args };
|
|
214
|
+
if (process.platform === 'win32' && request.windowsNoConsole === true && !existsSync(launch.executable)) {
|
|
215
|
+
throw new Error('Bundled Windows AGY launcher is missing');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
220
|
+
const diagnostic = processDiagnosticFor(cause, 'prepare');
|
|
221
|
+
return Promise.reject(new AgyProcessError(`Unable to prepare AGY launcher: ${cause.message}${diagnosticLabel(diagnostic)}`, 'SPAWN_FAILED', { cause }, diagnostic));
|
|
222
|
+
}
|
|
223
|
+
let child;
|
|
224
|
+
try {
|
|
225
|
+
child = spawn(launch.executable, [...launch.args], {
|
|
226
|
+
cwd: request.cwd,
|
|
227
|
+
env: request.env === undefined ? process.env : { ...process.env, ...request.env },
|
|
228
|
+
shell: false,
|
|
229
|
+
detached: process.platform !== 'win32',
|
|
230
|
+
windowsHide: true,
|
|
231
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
236
|
+
const code = cause.code ?? 'unknown';
|
|
237
|
+
const diagnostic = processDiagnosticFor(cause, 'spawn');
|
|
238
|
+
return Promise.reject(new AgyProcessError(`Unable to start AGY executable (${code})${diagnosticLabel(diagnostic)}`, 'SPAWN_FAILED', { cause }, diagnostic));
|
|
239
|
+
}
|
|
240
|
+
return new Promise((resolveResult, rejectResult) => {
|
|
241
|
+
let stdoutBuffer = '';
|
|
242
|
+
let stderr = '';
|
|
243
|
+
let stdoutBytes = 0;
|
|
244
|
+
let stderrBytes = 0;
|
|
245
|
+
const stdoutLines = [];
|
|
246
|
+
let termination;
|
|
247
|
+
let outputHandlerError;
|
|
248
|
+
let outputHandlerDiagnostic;
|
|
249
|
+
let inputError;
|
|
250
|
+
let settled = false;
|
|
251
|
+
let lastOutputAtMs = startedAt;
|
|
252
|
+
let turnClosed = false;
|
|
253
|
+
const deliver = (line) => {
|
|
254
|
+
stdoutLines.push(line);
|
|
255
|
+
if (!turnClosed && resultSeen(line)) {
|
|
256
|
+
turnClosed = true;
|
|
257
|
+
endStdin();
|
|
258
|
+
}
|
|
259
|
+
if (request.onStdoutLine === undefined || outputHandlerError !== undefined)
|
|
260
|
+
return;
|
|
261
|
+
try {
|
|
262
|
+
request.onStdoutLine(line);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
outputHandlerError = error;
|
|
266
|
+
outputHandlerDiagnostic = processDiagnosticFor(error, 'stdout-handler', {
|
|
267
|
+
line,
|
|
268
|
+
stdoutLineCount: stdoutLines.length,
|
|
269
|
+
stdoutBytes,
|
|
270
|
+
stderrBytes,
|
|
271
|
+
});
|
|
272
|
+
termination = 'aborted';
|
|
273
|
+
terminateProcessTree(child);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const flushStdout = (final) => {
|
|
277
|
+
const split = splitLines(stdoutBuffer, final);
|
|
278
|
+
stdoutBuffer = split.remainder;
|
|
279
|
+
for (const line of split.lines)
|
|
280
|
+
deliver(line);
|
|
281
|
+
};
|
|
282
|
+
const kill = (reason) => {
|
|
283
|
+
if (termination === undefined)
|
|
284
|
+
termination = reason;
|
|
285
|
+
if (!child.killed)
|
|
286
|
+
terminateProcessTree(child);
|
|
287
|
+
};
|
|
288
|
+
// `hold` keeps the request pipe open until the turn is over. Ending it
|
|
289
|
+
// early is read by AGY as "no next turn" and cancels the running one.
|
|
290
|
+
const stdinMode = request.stdinMode ?? 'close';
|
|
291
|
+
let stdinClosed = false;
|
|
292
|
+
const endStdin = () => {
|
|
293
|
+
if (stdinClosed || request.stdin === undefined)
|
|
294
|
+
return;
|
|
295
|
+
stdinClosed = true;
|
|
296
|
+
child.stdin.end();
|
|
297
|
+
};
|
|
298
|
+
const resultSeen = (line) => {
|
|
299
|
+
try {
|
|
300
|
+
const parsed = JSON.parse(line);
|
|
301
|
+
return typeof parsed === 'object'
|
|
302
|
+
&& parsed !== null
|
|
303
|
+
&& parsed.event === 'result';
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const abortListener = () => kill('aborted');
|
|
310
|
+
request.signal?.addEventListener('abort', abortListener, { once: true });
|
|
311
|
+
const timeout = request.timeoutMs !== undefined && request.timeoutMs > 0
|
|
312
|
+
? setTimeout(() => kill('timeout'), request.timeoutMs)
|
|
313
|
+
: undefined;
|
|
314
|
+
// Idle watchdog: re-armed on every stdout/stderr chunk, so long reasoning
|
|
315
|
+
// or a long tool step is not mistaken for a hung process. A turn that keeps
|
|
316
|
+
// producing output outlives `timeoutMs`; only real silence trips it.
|
|
317
|
+
const idleBudgetMs = request.idleTimeoutMs !== undefined && request.idleTimeoutMs > 0
|
|
318
|
+
? request.idleTimeoutMs
|
|
319
|
+
: undefined;
|
|
320
|
+
let idle;
|
|
321
|
+
const armIdle = () => {
|
|
322
|
+
if (idleBudgetMs === undefined || settled)
|
|
323
|
+
return;
|
|
324
|
+
if (idle !== undefined)
|
|
325
|
+
clearTimeout(idle);
|
|
326
|
+
idle = setTimeout(() => kill('idle-timeout'), idleBudgetMs);
|
|
327
|
+
};
|
|
328
|
+
armIdle();
|
|
329
|
+
// AGY's own headless default is an unbounded turn, and its `--print-timeout`
|
|
330
|
+
// is passed separately; a fixed wall clock would cut off a healthy turn, so
|
|
331
|
+
// it is opt-in via `timeoutMs` and never implied by the idle budget.
|
|
332
|
+
child.stdout.setEncoding('utf8');
|
|
333
|
+
child.stderr.setEncoding('utf8');
|
|
334
|
+
child.stdout.on('data', (chunk) => {
|
|
335
|
+
armIdle();
|
|
336
|
+
lastOutputAtMs = Date.now();
|
|
337
|
+
stdoutBytes += byteLength(chunk);
|
|
338
|
+
if (request.maxStdoutBytes !== undefined && stdoutBytes > request.maxStdoutBytes) {
|
|
339
|
+
termination = 'output-limit';
|
|
340
|
+
terminateProcessTree(child);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
stdoutBuffer += chunk;
|
|
344
|
+
flushStdout(false);
|
|
345
|
+
});
|
|
346
|
+
child.stdout.on('end', () => {
|
|
347
|
+
if (termination !== 'output-limit')
|
|
348
|
+
flushStdout(true);
|
|
349
|
+
// A final `result` line is the contract's "turn is over" signal; the
|
|
350
|
+
// process is expected to exit on its own, but a held pipe gives it a
|
|
351
|
+
// clean shutdown cue rather than an EOF it may read as a new turn.
|
|
352
|
+
if (stdinMode === 'hold')
|
|
353
|
+
endStdin();
|
|
354
|
+
});
|
|
355
|
+
child.stderr.on('data', (chunk) => {
|
|
356
|
+
armIdle();
|
|
357
|
+
lastOutputAtMs = Date.now();
|
|
358
|
+
stderrBytes += byteLength(chunk);
|
|
359
|
+
if (request.maxStderrBytes !== undefined && stderrBytes > request.maxStderrBytes) {
|
|
360
|
+
termination = 'output-limit';
|
|
361
|
+
terminateProcessTree(child);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
stderr += chunk;
|
|
365
|
+
});
|
|
366
|
+
if (request.stdin !== undefined) {
|
|
367
|
+
child.stdin.once('error', error => {
|
|
368
|
+
if (settled || inputError !== undefined)
|
|
369
|
+
return;
|
|
370
|
+
inputError = error;
|
|
371
|
+
kill('aborted');
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
child.once('error', (error) => {
|
|
375
|
+
if (settled)
|
|
376
|
+
return;
|
|
377
|
+
settled = true;
|
|
378
|
+
if (timeout !== undefined)
|
|
379
|
+
clearTimeout(timeout);
|
|
380
|
+
if (idle !== undefined)
|
|
381
|
+
clearTimeout(idle);
|
|
382
|
+
request.signal?.removeEventListener('abort', abortListener);
|
|
383
|
+
rejectResult(new AgyProcessError(`Unable to start AGY executable (${error.code ?? 'unknown'})${diagnosticLabel(processDiagnosticFor(error, 'spawn'))}`, 'SPAWN_FAILED', { cause: error }, processDiagnosticFor(error, 'spawn')));
|
|
384
|
+
});
|
|
385
|
+
child.once('close', (exitCode, signal) => {
|
|
386
|
+
if (settled)
|
|
387
|
+
return;
|
|
388
|
+
settled = true;
|
|
389
|
+
endStdin();
|
|
390
|
+
if (timeout !== undefined)
|
|
391
|
+
clearTimeout(timeout);
|
|
392
|
+
if (idle !== undefined)
|
|
393
|
+
clearTimeout(idle);
|
|
394
|
+
request.signal?.removeEventListener('abort', abortListener);
|
|
395
|
+
if (termination !== 'output-limit')
|
|
396
|
+
flushStdout(true);
|
|
397
|
+
if (outputHandlerError !== undefined) {
|
|
398
|
+
rejectResult(new AgyProcessError(`AGY stdout handler failed${diagnosticLabel(outputHandlerDiagnostic)}`, 'OUTPUT_HANDLER_FAILED', { cause: outputHandlerError }, outputHandlerDiagnostic));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (inputError !== undefined) {
|
|
402
|
+
const stderrSummary = stderrSummaryOf(stderr);
|
|
403
|
+
const diagnostic = processDiagnosticFor(inputError, 'stdin', {
|
|
404
|
+
stdoutLineCount: stdoutLines.length,
|
|
405
|
+
stdoutBytes,
|
|
406
|
+
stderrBytes,
|
|
407
|
+
...(stderrSummary === undefined ? {} : { stderrSummary }),
|
|
408
|
+
});
|
|
409
|
+
rejectResult(new AgyProcessError(`Unable to write AGY stream-json input${diagnosticLabel(diagnostic)}`, 'INPUT_FAILED', { cause: inputError instanceof Error ? inputError : new Error(String(inputError)) }, diagnostic));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const finalTermination = termination
|
|
413
|
+
?? (exitCode === 0 ? 'completed' : signal === null ? 'non-zero' : 'signaled');
|
|
414
|
+
resolveResult({
|
|
415
|
+
exitCode,
|
|
416
|
+
signal,
|
|
417
|
+
termination: finalTermination,
|
|
418
|
+
stdoutLines,
|
|
419
|
+
stderr,
|
|
420
|
+
durationMs: Date.now() - startedAt,
|
|
421
|
+
lastOutputAtMs,
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
if (request.stdin !== undefined) {
|
|
425
|
+
child.stdin.write(request.stdin);
|
|
426
|
+
// `hold` leaves the pipe open until `deliver` sees AGY's closing `result`
|
|
427
|
+
// line (or the process exits, which closes it anyway). Ending it here is
|
|
428
|
+
// what made AGY cancel a turn it was still generating.
|
|
429
|
+
if (stdinMode === 'close')
|
|
430
|
+
endStdin();
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
export function runAgyProcess(request) {
|
|
435
|
+
const executable = resolveAgyExecutable(request.executable);
|
|
436
|
+
let stdin;
|
|
437
|
+
try {
|
|
438
|
+
stdin = encodeAgyUserMessage(request.prompt, request.maxInputFrameBytes);
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
const tooLarge = error instanceof AgyStreamProtocolError && error.code === 'FRAME_TOO_LARGE';
|
|
442
|
+
return Promise.reject(new AgyProcessError(error instanceof Error ? error.message : 'Unable to encode AGY stream-json input', tooLarge ? 'INPUT_TOO_LARGE' : 'INPUT_FAILED', { cause: error instanceof Error ? error : new Error(String(error)) }));
|
|
443
|
+
}
|
|
444
|
+
return runProcess({
|
|
445
|
+
...request,
|
|
446
|
+
executable,
|
|
447
|
+
args: buildAgyArgs(request),
|
|
448
|
+
stdin,
|
|
449
|
+
windowsNoConsole: true,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Key part allows the surrounding quotes JSON produces: `"token": "v"` must
|
|
2
|
+
// match even though the separator sits one character after the sensitive word.
|
|
3
|
+
const SENSITIVE_ASSIGNMENT = /["']?(authorization|bearer|access[_-]?token|refresh[_-]?token|token|api[_-]?key|password|secret)["']?\s*[:=]\s*(?:bearer\s+)?(?:"[^"]*"|'[^']*'|[^\s,;}]+)/gi;
|
|
4
|
+
const SENSITIVE_QUERY = /([?&](?:access[_-]?token|refresh[_-]?token|api[_-]?key|token|password|secret)=)[^&#\s]+/gi;
|
|
5
|
+
// Bare token shapes that carry no key at all: the sk- keys this workspace's
|
|
6
|
+
// own commit discipline greps for, and credentials in a URL userinfo section.
|
|
7
|
+
const BARE_SK_TOKEN = /\bsk-[A-Za-z0-9]{20,}\b/g;
|
|
8
|
+
const URL_USERINFO = /[a-z][a-z0-9+.-]*:\/\/[^/?#\s]+:[^/?#\s]+@/gi;
|
|
9
|
+
const WINDOWS_USER_PATH = /[A-Za-z]:\\Users\\[^\s"'`,;)]*/g;
|
|
10
|
+
const UNIX_USER_PATH = /\/(?:Users|home)\/[^\s"'`,;)]*/g;
|
|
11
|
+
const ENV_ASSIGNMENT = /\b[A-Z][A-Z0-9_]{2,}=(?:"[^"]*"|'[^']*'|[^\s,;]+)/g;
|
|
12
|
+
/** Redact common credentials, environment assignments, and user-home prefixes. */
|
|
13
|
+
export function redactText(value, maxLength = 1_000) {
|
|
14
|
+
return value
|
|
15
|
+
.replace(SENSITIVE_ASSIGNMENT, '$1=[REDACTED]')
|
|
16
|
+
.replace(SENSITIVE_QUERY, '$1[REDACTED]')
|
|
17
|
+
.replace(BARE_SK_TOKEN, '[REDACTED]')
|
|
18
|
+
.replace(URL_USERINFO, '[REDACTED]@')
|
|
19
|
+
.replace(ENV_ASSIGNMENT, '[ENV]=[REDACTED]')
|
|
20
|
+
.replace(WINDOWS_USER_PATH, '<user-path>')
|
|
21
|
+
.replace(UNIX_USER_PATH, '<user-path>')
|
|
22
|
+
.slice(0, maxLength);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small, transport-neutral helpers for AGY's stdin stream-json mode.
|
|
3
|
+
*
|
|
4
|
+
* Real AGY 1.1.15 stream-json input is event-driven. Factual envelope sampled
|
|
5
|
+
* in V8-M1 (see findings.md 2026-08-20, .tmp/v8-m1/three-turn.log):
|
|
6
|
+
* input: {"event":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]}} per NDJSON line
|
|
7
|
+
* output: {"event":"init","conversation_id":"...","init":{...}} then
|
|
8
|
+
* {"event":"step_update",...} then {"event":"result",...} per turn.
|
|
9
|
+
* The generic encodeAgyStreamInput is retained for forward compatibility, but
|
|
10
|
+
* callers should use the typed encodeAgyUserMessage helper.
|
|
11
|
+
*/
|
|
12
|
+
export type AgyProtocolValue = Record<string, unknown> | readonly unknown[] | string | number | boolean | null;
|
|
13
|
+
export declare class AgyStreamProtocolError extends Error {
|
|
14
|
+
readonly code: 'INVALID_INPUT' | 'FRAME_TOO_LARGE' | 'INVALID_OUTPUT';
|
|
15
|
+
constructor(message: string, code: 'INVALID_INPUT' | 'FRAME_TOO_LARGE' | 'INVALID_OUTPUT', options?: ErrorOptions);
|
|
16
|
+
}
|
|
17
|
+
/** Typed input for AGY 1.1.15 stream-json: one user turn per NDJSON line. */
|
|
18
|
+
export interface AgyStreamUserMessage {
|
|
19
|
+
readonly event: "user";
|
|
20
|
+
readonly message: {
|
|
21
|
+
readonly role: "user";
|
|
22
|
+
readonly content: readonly {
|
|
23
|
+
readonly type: "text";
|
|
24
|
+
readonly text: string;
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Encode one stdin NDJSON message without invoking a shell. */
|
|
29
|
+
export declare function encodeAgyStreamInput(message: unknown, maxFrameBytes?: number): string;
|
|
30
|
+
/** Return the exact UTF-8 size of one encoded AGY user frame. */
|
|
31
|
+
export declare function getAgyUserMessageByteLength(text: string): number;
|
|
32
|
+
/** Encode one AGY stream-json user turn with the factual V8-M1 envelope. */
|
|
33
|
+
export declare function encodeAgyUserMessage(text: string, maxFrameBytes?: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* Replace all free-form strings and identifiers while retaining enough
|
|
36
|
+
* structure to replay parser behavior. Stable protocol enum fields remain.
|
|
37
|
+
*/
|
|
38
|
+
export declare function sanitizeAgyProtocolValue(value: unknown, key?: string, depth?: number): unknown;
|
|
39
|
+
export interface AgyProtocolRecordSummary {
|
|
40
|
+
readonly topLevelKeys: readonly string[];
|
|
41
|
+
readonly event: string | null;
|
|
42
|
+
readonly shapePaths: readonly string[];
|
|
43
|
+
readonly valueKinds: readonly string[];
|
|
44
|
+
readonly stringLengths: Readonly<Record<string, number>>;
|
|
45
|
+
}
|
|
46
|
+
/** Summarize one decoded output record without returning its payload. */
|
|
47
|
+
export declare function summarizeAgyProtocolValue(value: unknown): AgyProtocolRecordSummary;
|
|
48
|
+
/** Convert one decoded output record into a safe fixture value. */
|
|
49
|
+
export declare function sanitizeAgyProtocolRecord(value: unknown): unknown;
|
|
50
|
+
//# sourceMappingURL=stream-protocol.d.ts.map
|