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,156 @@
|
|
|
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 class AgyStreamProtocolError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, code, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = 'AgyStreamProtocolError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function byteLength(value) {
|
|
21
|
+
return Buffer.byteLength(value, 'utf8');
|
|
22
|
+
}
|
|
23
|
+
/** Encode one stdin NDJSON message without invoking a shell. */
|
|
24
|
+
export function encodeAgyStreamInput(message, maxFrameBytes = 256 * 1024) {
|
|
25
|
+
if (message === undefined || typeof message === 'function' || typeof message === 'symbol') {
|
|
26
|
+
throw new AgyStreamProtocolError('AGY stream-json input must be JSON serializable', 'INVALID_INPUT');
|
|
27
|
+
}
|
|
28
|
+
let line;
|
|
29
|
+
try {
|
|
30
|
+
line = JSON.stringify(message);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new AgyStreamProtocolError('AGY stream-json input could not be encoded', 'INVALID_INPUT', { cause: error });
|
|
34
|
+
}
|
|
35
|
+
const encoded = `${line}\n`;
|
|
36
|
+
if (byteLength(encoded) > maxFrameBytes) {
|
|
37
|
+
throw new AgyStreamProtocolError(`AGY stream-json input exceeds the configured frame limit (${byteLength(encoded)} > ${maxFrameBytes} bytes)`, 'FRAME_TOO_LARGE');
|
|
38
|
+
}
|
|
39
|
+
return encoded;
|
|
40
|
+
}
|
|
41
|
+
function userMessagePayload(text) {
|
|
42
|
+
return {
|
|
43
|
+
event: 'user',
|
|
44
|
+
message: { role: 'user', content: [{ type: 'text', text }] },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Return the exact UTF-8 size of one encoded AGY user frame. */
|
|
48
|
+
export function getAgyUserMessageByteLength(text) {
|
|
49
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
50
|
+
throw new AgyStreamProtocolError('AGY stream-json user text must be a non-empty string', 'INVALID_INPUT');
|
|
51
|
+
}
|
|
52
|
+
const line = JSON.stringify(userMessagePayload(text));
|
|
53
|
+
return byteLength(`${line}\n`);
|
|
54
|
+
}
|
|
55
|
+
/** Encode one AGY stream-json user turn with the factual V8-M1 envelope. */
|
|
56
|
+
export function encodeAgyUserMessage(text, maxFrameBytes = 256 * 1024) {
|
|
57
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
58
|
+
throw new AgyStreamProtocolError("AGY stream-json user text must be a non-empty string", "INVALID_INPUT");
|
|
59
|
+
}
|
|
60
|
+
const payload = userMessagePayload(text);
|
|
61
|
+
return encodeAgyStreamInput(payload, maxFrameBytes);
|
|
62
|
+
}
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
function safeEnumKey(key) {
|
|
67
|
+
return key === 'event'
|
|
68
|
+
|| key === 'status'
|
|
69
|
+
|| key === 'subtype'
|
|
70
|
+
|| key === 'step_type'
|
|
71
|
+
|| key === 'state'
|
|
72
|
+
|| key === 'kind'
|
|
73
|
+
|| key === 'type'
|
|
74
|
+
|| key === 'role';
|
|
75
|
+
}
|
|
76
|
+
function safeEnum(value) {
|
|
77
|
+
return /^[A-Za-z0-9_.:-]{1,64}$/.test(value) ? value : '<enum>';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Replace all free-form strings and identifiers while retaining enough
|
|
81
|
+
* structure to replay parser behavior. Stable protocol enum fields remain.
|
|
82
|
+
*/
|
|
83
|
+
export function sanitizeAgyProtocolValue(value, key = '', depth = 0) {
|
|
84
|
+
if (depth > 8)
|
|
85
|
+
return '<depth-limit>';
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
if (safeEnumKey(key))
|
|
88
|
+
return safeEnum(value);
|
|
89
|
+
return `<string:${value.length}>`;
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === 'number')
|
|
92
|
+
return key.toLowerCase().includes('usage') ? 0 : 0;
|
|
93
|
+
if (typeof value === 'boolean' || value === null)
|
|
94
|
+
return value;
|
|
95
|
+
if (Array.isArray(value))
|
|
96
|
+
return value.slice(0, 32).map(item => sanitizeAgyProtocolValue(item, '', depth + 1));
|
|
97
|
+
if (isRecord(value)) {
|
|
98
|
+
const entries = Object.entries(value).slice(0, 128);
|
|
99
|
+
return Object.fromEntries(entries.map(([entryKey, entryValue]) => [
|
|
100
|
+
entryKey,
|
|
101
|
+
sanitizeAgyProtocolValue(entryValue, entryKey, depth + 1),
|
|
102
|
+
]));
|
|
103
|
+
}
|
|
104
|
+
return '<unsupported>';
|
|
105
|
+
}
|
|
106
|
+
function collectShape(value, path, paths, kinds, stringLengths, depth = 0) {
|
|
107
|
+
if (depth > 8 || paths.size >= 256)
|
|
108
|
+
return;
|
|
109
|
+
if (value === null) {
|
|
110
|
+
kinds.add('null');
|
|
111
|
+
paths.add(path || '$');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
kinds.add('array');
|
|
116
|
+
paths.add(`${path || '$'}[]`);
|
|
117
|
+
for (const item of value.slice(0, 16))
|
|
118
|
+
collectShape(item, `${path || '$'}[]`, paths, kinds, stringLengths, depth + 1);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'object') {
|
|
122
|
+
kinds.add('object');
|
|
123
|
+
for (const [key, nested] of Object.entries(value).slice(0, 128)) {
|
|
124
|
+
const nestedPath = path ? `${path}.${key}` : key;
|
|
125
|
+
paths.add(nestedPath);
|
|
126
|
+
collectShape(nested, nestedPath, paths, kinds, stringLengths, depth + 1);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
kinds.add(typeof value);
|
|
131
|
+
paths.add(path || '$');
|
|
132
|
+
if (typeof value === 'string')
|
|
133
|
+
stringLengths[path || '$'] = value.length;
|
|
134
|
+
}
|
|
135
|
+
/** Summarize one decoded output record without returning its payload. */
|
|
136
|
+
export function summarizeAgyProtocolValue(value) {
|
|
137
|
+
const paths = new Set();
|
|
138
|
+
const kinds = new Set();
|
|
139
|
+
const stringLengths = {};
|
|
140
|
+
collectShape(value, '', paths, kinds, stringLengths);
|
|
141
|
+
const event = isRecord(value) && typeof value.event === 'string'
|
|
142
|
+
? safeEnum(value.event)
|
|
143
|
+
: null;
|
|
144
|
+
return {
|
|
145
|
+
topLevelKeys: isRecord(value) ? Object.keys(value).sort() : [],
|
|
146
|
+
event,
|
|
147
|
+
shapePaths: [...paths].sort(),
|
|
148
|
+
valueKinds: [...kinds].sort(),
|
|
149
|
+
stringLengths: Object.freeze({ ...stringLengths }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** Convert one decoded output record into a safe fixture value. */
|
|
153
|
+
export function sanitizeAgyProtocolRecord(value) {
|
|
154
|
+
return sanitizeAgyProtocolValue(value);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=stream-protocol.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AgyLaunchSpec {
|
|
2
|
+
readonly executable: string;
|
|
3
|
+
readonly args: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
export type WindowsLauncherArchitecture = 'x64' | 'arm64';
|
|
6
|
+
export declare class AgyWindowsLauncherError extends Error {
|
|
7
|
+
readonly code: 'UNSUPPORTED_ARCH' | 'MISSING';
|
|
8
|
+
constructor(message: string, code: 'UNSUPPORTED_ARCH' | 'MISSING');
|
|
9
|
+
}
|
|
10
|
+
export declare function bundledWindowsLauncherPath(targetPlatform?: NodeJS.Platform, targetArch?: string): string | undefined;
|
|
11
|
+
/** Quote one argument using the CommandLineToArgvW/MSVC convention. */
|
|
12
|
+
export declare function quoteWindowsArgument(value: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Route a Windows console-subsystem AGY executable through the bundled
|
|
15
|
+
* GUI-subsystem launcher. The child command line is UTF-8/base64 encoded so
|
|
16
|
+
* the launcher itself receives only a short, secret-free argument and can
|
|
17
|
+
* pass the original stdin/stdout/stderr handles through unchanged.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildWindowsNoConsoleLaunch(executable: string, args: readonly string[], targetPlatform?: NodeJS.Platform, targetArch?: string): AgyLaunchSpec;
|
|
20
|
+
export declare function isWindowsLauncherAvailable(targetPlatform?: NodeJS.Platform, targetArch?: string): boolean;
|
|
21
|
+
//# sourceMappingURL=windows-launcher.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
export class AgyWindowsLauncherError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = 'AgyWindowsLauncherError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function launcherArchitecture(arch) {
|
|
12
|
+
if (arch === 'x64')
|
|
13
|
+
return 'x64';
|
|
14
|
+
if (arch === 'arm64')
|
|
15
|
+
return 'arm64';
|
|
16
|
+
throw new AgyWindowsLauncherError(`No bundled Windows AGY launcher is available for architecture ${arch}`, 'UNSUPPORTED_ARCH');
|
|
17
|
+
}
|
|
18
|
+
export function bundledWindowsLauncherPath(targetPlatform = process.platform, targetArch = process.arch) {
|
|
19
|
+
if (targetPlatform !== 'win32')
|
|
20
|
+
return undefined;
|
|
21
|
+
const architecture = launcherArchitecture(targetArch);
|
|
22
|
+
return fileURLToPath(new URL(`../../bin/win32-${architecture}/agy-launcher.exe`, import.meta.url));
|
|
23
|
+
}
|
|
24
|
+
/** Quote one argument using the CommandLineToArgvW/MSVC convention. */
|
|
25
|
+
export function quoteWindowsArgument(value) {
|
|
26
|
+
if (value.length === 0)
|
|
27
|
+
return '""';
|
|
28
|
+
if (!/[\s"]/.test(value))
|
|
29
|
+
return value;
|
|
30
|
+
let output = '"';
|
|
31
|
+
let backslashes = 0;
|
|
32
|
+
for (const character of value) {
|
|
33
|
+
if (character === '\\') {
|
|
34
|
+
backslashes += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (character === '"') {
|
|
38
|
+
output += '\\'.repeat(backslashes * 2 + 1);
|
|
39
|
+
output += '"';
|
|
40
|
+
backslashes = 0;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
output += '\\'.repeat(backslashes);
|
|
44
|
+
output += character;
|
|
45
|
+
backslashes = 0;
|
|
46
|
+
}
|
|
47
|
+
output += '\\'.repeat(backslashes * 2);
|
|
48
|
+
return `${output}"`;
|
|
49
|
+
}
|
|
50
|
+
function childCommandLine(executable, args) {
|
|
51
|
+
return [executable, ...args].map(quoteWindowsArgument).join(' ');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Route a Windows console-subsystem AGY executable through the bundled
|
|
55
|
+
* GUI-subsystem launcher. The child command line is UTF-8/base64 encoded so
|
|
56
|
+
* the launcher itself receives only a short, secret-free argument and can
|
|
57
|
+
* pass the original stdin/stdout/stderr handles through unchanged.
|
|
58
|
+
*/
|
|
59
|
+
export function buildWindowsNoConsoleLaunch(executable, args, targetPlatform = process.platform, targetArch = process.arch) {
|
|
60
|
+
if (targetPlatform !== 'win32')
|
|
61
|
+
return { executable, args: [...args] };
|
|
62
|
+
const launcher = bundledWindowsLauncherPath(targetPlatform, targetArch);
|
|
63
|
+
if (launcher === undefined)
|
|
64
|
+
throw new AgyWindowsLauncherError('Windows launcher is unavailable', 'MISSING');
|
|
65
|
+
const command = childCommandLine(executable, args);
|
|
66
|
+
const encodedCommand = Buffer.from(command, 'utf8').toString('base64');
|
|
67
|
+
return {
|
|
68
|
+
executable: launcher,
|
|
69
|
+
args: ['--command-base64', encodedCommand],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function isWindowsLauncherAvailable(targetPlatform = process.platform, targetArch = process.arch) {
|
|
73
|
+
const launcher = bundledWindowsLauncherPath(targetPlatform, targetArch);
|
|
74
|
+
return launcher !== undefined && existsSync(launcher);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=windows-launcher.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type AgyDiagnosticCommand, type AgyDiagnosticResult } from './agy/diagnostics.js';
|
|
2
|
+
import { type AgyModelDiscoveryCommand, type ModelDiscoverySource } from './agy/models.js';
|
|
3
|
+
import type { ModelDiscoveryErrorCode } from './provider/error-codes.js';
|
|
4
|
+
import { type Config, type ToolPolicy } from './provider/config.js';
|
|
5
|
+
export interface DiagnosticModel {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string | null;
|
|
9
|
+
inputModalities: readonly ('text' | 'image')[];
|
|
10
|
+
contextWindow: number | null;
|
|
11
|
+
}
|
|
12
|
+
export type DiagnosticComponent = 'node' | 'bundle' | 'agy' | 'models' | 'config' | 'profile';
|
|
13
|
+
export interface DiagnosticIssue {
|
|
14
|
+
component: DiagnosticComponent;
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ProviderDiagnosticOptions {
|
|
19
|
+
config?: Config;
|
|
20
|
+
executable?: string;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
runCommand?: AgyDiagnosticCommand;
|
|
23
|
+
runModelDiscovery?: AgyModelDiscoveryCommand;
|
|
24
|
+
pluginVersion?: string | null;
|
|
25
|
+
dshCliVersion?: string | null;
|
|
26
|
+
dshLlmVersion?: string | null;
|
|
27
|
+
bundlePatchPresent?: boolean;
|
|
28
|
+
nodeVersion?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ProviderDiagnosticResult {
|
|
31
|
+
schemaVersion: 2;
|
|
32
|
+
ok: boolean;
|
|
33
|
+
quotaUsed: false;
|
|
34
|
+
plugin: {
|
|
35
|
+
name: string;
|
|
36
|
+
version: string | null;
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
};
|
|
39
|
+
node: {
|
|
40
|
+
version: string;
|
|
41
|
+
major: number | null;
|
|
42
|
+
supported: boolean;
|
|
43
|
+
};
|
|
44
|
+
dsh: {
|
|
45
|
+
cliVersion: string | null;
|
|
46
|
+
llmContractVersion: string | null;
|
|
47
|
+
bundlePatchPresent: boolean;
|
|
48
|
+
};
|
|
49
|
+
configuration: {
|
|
50
|
+
provider: string;
|
|
51
|
+
agent: string;
|
|
52
|
+
defaultModel: string;
|
|
53
|
+
modelDiscovery: 'auto' | 'off';
|
|
54
|
+
toolPolicy: ToolPolicy;
|
|
55
|
+
sessionMode: 'resume' | 'full';
|
|
56
|
+
enabled: boolean;
|
|
57
|
+
transport: string;
|
|
58
|
+
persistentIdleTtlMs: number;
|
|
59
|
+
persistentReadyTimeoutMs: number;
|
|
60
|
+
persistentFallback: string;
|
|
61
|
+
};
|
|
62
|
+
models: readonly DiagnosticModel[];
|
|
63
|
+
modelCatalog: {
|
|
64
|
+
source: 'static' | ModelDiscoverySource;
|
|
65
|
+
stale: boolean;
|
|
66
|
+
warning: string | null;
|
|
67
|
+
warningCode: ModelDiscoveryErrorCode | null;
|
|
68
|
+
};
|
|
69
|
+
agy: AgyDiagnosticResult;
|
|
70
|
+
errors: readonly DiagnosticIssue[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build the machine-readable, quota-free diagnostic contract for the plugin.
|
|
74
|
+
* It deliberately exposes only safe labels and metadata, never AGY paths,
|
|
75
|
+
* environment values, credentials, prompts, or session content.
|
|
76
|
+
*/
|
|
77
|
+
export declare function diagnoseProvider(options?: ProviderDiagnosticOptions): Promise<ProviderDiagnosticResult>;
|
|
78
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { diagnoseAgy, } from './agy/diagnostics.js';
|
|
5
|
+
import { AgyModelDiscovery, } from './agy/models.js';
|
|
6
|
+
import { redactText } from './agy/redact.js';
|
|
7
|
+
import { Config as ConfigSchema, configuredModels, readConfigFlag, DEFAULT_AGENT, } from './provider/config.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
function loadPackageMetadata() {
|
|
10
|
+
try {
|
|
11
|
+
return require('../package.json');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const packageMetadata = loadPackageMetadata();
|
|
18
|
+
function modelDiagnostic(model, imageInput) {
|
|
19
|
+
return {
|
|
20
|
+
id: model.id,
|
|
21
|
+
name: model.name ?? model.id,
|
|
22
|
+
description: model.description ?? null,
|
|
23
|
+
inputModalities: imageInput === 'experimental' ? ['text', 'image'] : ['text'],
|
|
24
|
+
contextWindow: model.contextWindow ?? null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function issueCode(message) {
|
|
28
|
+
if (/below the required minimum/i.test(message))
|
|
29
|
+
return 'AGY_VERSION_UNSUPPORTED';
|
|
30
|
+
if (/version from --version/i.test(message))
|
|
31
|
+
return 'AGY_VERSION_UNREADABLE';
|
|
32
|
+
if (/Agent .* was not found/i.test(message))
|
|
33
|
+
return 'AGY_AGENT_MISSING';
|
|
34
|
+
if (/agents failed/i.test(message))
|
|
35
|
+
return 'AGY_AGENT_CHECK_FAILED';
|
|
36
|
+
if (/--version failed/i.test(message))
|
|
37
|
+
return 'AGY_VERSION_CHECK_FAILED';
|
|
38
|
+
return 'AGY_DIAGNOSTIC_FAILED';
|
|
39
|
+
}
|
|
40
|
+
function nodeMajorOf(version) {
|
|
41
|
+
const match = /^(\d+)/.exec(version);
|
|
42
|
+
if (match === null)
|
|
43
|
+
return null;
|
|
44
|
+
const major = Number(match[1]);
|
|
45
|
+
return Number.isSafeInteger(major) ? major : null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the machine-readable, quota-free diagnostic contract for the plugin.
|
|
49
|
+
* It deliberately exposes only safe labels and metadata, never AGY paths,
|
|
50
|
+
* environment values, credentials, prompts, or session content.
|
|
51
|
+
*/
|
|
52
|
+
export async function diagnoseProvider(options = {}) {
|
|
53
|
+
const config = ConfigSchema(options.config ?? {});
|
|
54
|
+
const nodeVersion = options.nodeVersion ?? process.versions.node;
|
|
55
|
+
const nodeMajor = nodeMajorOf(nodeVersion);
|
|
56
|
+
const nodeSupported = nodeMajor !== null && nodeMajor >= 20;
|
|
57
|
+
const bundlePatchPresent = options.bundlePatchPresent
|
|
58
|
+
?? existsSync(fileURLToPath(new URL('../cordis.patch.yml', import.meta.url)));
|
|
59
|
+
const executable = options.executable ?? config.agyPath;
|
|
60
|
+
const configuredCatalog = configuredModels(config);
|
|
61
|
+
const modelDiscoveryCommand = options.runModelDiscovery ?? options.runCommand;
|
|
62
|
+
const discovery = config.modelDiscovery === 'off'
|
|
63
|
+
? undefined
|
|
64
|
+
: new AgyModelDiscovery({
|
|
65
|
+
...(executable === undefined || executable.trim().length === 0 ? {} : { executable }),
|
|
66
|
+
...(config.modelDiscoveryTtlMs === undefined ? {} : { ttlMs: config.modelDiscoveryTtlMs }),
|
|
67
|
+
...(config.modelDiscoveryTimeoutMs === undefined ? {} : { timeoutMs: config.modelDiscoveryTimeoutMs }),
|
|
68
|
+
...(config.maxOutputBytes === undefined ? {} : { maxOutputBytes: config.maxOutputBytes }),
|
|
69
|
+
...(modelDiscoveryCommand === undefined ? {} : { runCommand: modelDiscoveryCommand }),
|
|
70
|
+
});
|
|
71
|
+
const modelCatalog = discovery === undefined
|
|
72
|
+
? { models: configuredCatalog, source: 'static', stale: false }
|
|
73
|
+
: await discovery.discover(configuredCatalog);
|
|
74
|
+
const models = modelCatalog.models.map(model => modelDiagnostic(model, config.imageInput));
|
|
75
|
+
const agyOptions = {
|
|
76
|
+
...(executable === undefined || executable.trim().length === 0 ? {} : { executable }),
|
|
77
|
+
expectedAgent: config.agent ?? DEFAULT_AGENT,
|
|
78
|
+
minimumVersion: config.minimumAgyVersion ?? '1.1.15',
|
|
79
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
80
|
+
...(options.runCommand === undefined ? {} : { runCommand: options.runCommand }),
|
|
81
|
+
};
|
|
82
|
+
const agy = await diagnoseAgy(agyOptions);
|
|
83
|
+
const errors = [];
|
|
84
|
+
if (!nodeSupported) {
|
|
85
|
+
errors.push({
|
|
86
|
+
component: 'node',
|
|
87
|
+
code: 'NODE_UNSUPPORTED',
|
|
88
|
+
message: `Node.js ${nodeVersion} is below the required major version 20`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (!bundlePatchPresent) {
|
|
92
|
+
errors.push({
|
|
93
|
+
component: 'bundle',
|
|
94
|
+
code: 'BUNDLE_PATCH_MISSING',
|
|
95
|
+
message: 'dsh.bundle.patch and cordis.patch.yml could not be verified',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (models.length === 0) {
|
|
99
|
+
errors.push({
|
|
100
|
+
component: 'models',
|
|
101
|
+
code: 'MODEL_CATALOG_EMPTY',
|
|
102
|
+
message: 'No effective model is configured',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const message of agy.errors) {
|
|
106
|
+
errors.push({
|
|
107
|
+
component: 'agy',
|
|
108
|
+
code: issueCode(message),
|
|
109
|
+
message: redactText(message, 512),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const metadata = packageMetadata;
|
|
113
|
+
const llmContractVersion = options.dshLlmVersion
|
|
114
|
+
?? metadata.devDependencies?.['@deepseek-ai/dsh-llm']
|
|
115
|
+
?? metadata.peerDependencies?.['@deepseek-ai/dsh-llm']
|
|
116
|
+
?? null;
|
|
117
|
+
return {
|
|
118
|
+
schemaVersion: 2,
|
|
119
|
+
ok: errors.length === 0,
|
|
120
|
+
quotaUsed: false,
|
|
121
|
+
plugin: {
|
|
122
|
+
name: metadata.name ?? 'dsh-agy-cli',
|
|
123
|
+
version: options.pluginVersion ?? metadata.version ?? null,
|
|
124
|
+
enabled: readConfigFlag(config.enabled),
|
|
125
|
+
},
|
|
126
|
+
node: {
|
|
127
|
+
version: nodeVersion,
|
|
128
|
+
major: nodeMajor,
|
|
129
|
+
supported: nodeSupported,
|
|
130
|
+
},
|
|
131
|
+
dsh: {
|
|
132
|
+
cliVersion: options.dshCliVersion ?? process.env.DSH_CLI_VERSION ?? null,
|
|
133
|
+
llmContractVersion,
|
|
134
|
+
bundlePatchPresent,
|
|
135
|
+
},
|
|
136
|
+
configuration: {
|
|
137
|
+
provider: config.provider ?? 'agy',
|
|
138
|
+
agent: config.agent ?? DEFAULT_AGENT,
|
|
139
|
+
defaultModel: config.model ?? models[0]?.id ?? 'unknown',
|
|
140
|
+
modelDiscovery: config.modelDiscovery === 'off' ? 'off' : 'auto',
|
|
141
|
+
toolPolicy: config.toolPolicy === 'agy-owned'
|
|
142
|
+
? 'agy-owned'
|
|
143
|
+
: config.toolPolicy === 'dsh-owned' ? 'dsh-owned' : 'reject',
|
|
144
|
+
sessionMode: config.sessionMode === 'resume' ? 'resume' : 'full',
|
|
145
|
+
enabled: readConfigFlag(config.enabled),
|
|
146
|
+
transport: config.transport ?? 'one-shot',
|
|
147
|
+
persistentIdleTtlMs: config.persistentIdleTtlMs ?? 30000,
|
|
148
|
+
persistentReadyTimeoutMs: config.persistentReadyTimeoutMs ?? 10000,
|
|
149
|
+
persistentFallback: config.persistentFallback ?? 'before-accept',
|
|
150
|
+
},
|
|
151
|
+
models,
|
|
152
|
+
modelCatalog: {
|
|
153
|
+
source: modelCatalog.source,
|
|
154
|
+
stale: modelCatalog.stale,
|
|
155
|
+
warning: modelCatalog.warning ?? null,
|
|
156
|
+
warningCode: modelCatalog.warningCode ?? null,
|
|
157
|
+
},
|
|
158
|
+
agy,
|
|
159
|
+
errors,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=diagnostics.js.map
|
package/lib/doctor.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { type DiagnosticIssue, type ProviderDiagnosticOptions, type ProviderDiagnosticResult } from './diagnostics.js';
|
|
2
|
+
import { type ToolPolicy } from './provider/config.js';
|
|
3
|
+
export interface ProfileDiagnosticOptions {
|
|
4
|
+
profileName: string;
|
|
5
|
+
dshHome?: string | undefined;
|
|
6
|
+
dshBin?: string | undefined;
|
|
7
|
+
timeoutMs?: number | undefined;
|
|
8
|
+
runCommand?: ((executable: string, args: readonly string[], options?: {
|
|
9
|
+
timeoutMs?: number | undefined;
|
|
10
|
+
cwd?: string | undefined;
|
|
11
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
exitCode: number | null;
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
timedOut: boolean;
|
|
17
|
+
}>) | undefined;
|
|
18
|
+
}
|
|
19
|
+
export type ProfileDumpStatus = 'not-run' | 'ok' | 'unavailable' | 'timeout' | 'nonzero' | 'parse-error';
|
|
20
|
+
export interface EffectivePurposeRoute {
|
|
21
|
+
configured: boolean;
|
|
22
|
+
complete: boolean | null;
|
|
23
|
+
model: string | null;
|
|
24
|
+
agent: string | null;
|
|
25
|
+
reasoningEffort: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface EffectiveDshContext {
|
|
28
|
+
source: 'profile-dump';
|
|
29
|
+
session: {
|
|
30
|
+
state: 'not-probed';
|
|
31
|
+
required: boolean;
|
|
32
|
+
};
|
|
33
|
+
workspace: {
|
|
34
|
+
state: 'not-probed';
|
|
35
|
+
configured: boolean;
|
|
36
|
+
};
|
|
37
|
+
permission: {
|
|
38
|
+
state: 'not-probed';
|
|
39
|
+
preset: null;
|
|
40
|
+
};
|
|
41
|
+
approval: {
|
|
42
|
+
state: 'not-probed';
|
|
43
|
+
policy: null;
|
|
44
|
+
};
|
|
45
|
+
services: {
|
|
46
|
+
sessions: 'not-probed';
|
|
47
|
+
workspaceRegistry: 'not-probed';
|
|
48
|
+
sandboxPolicy: 'not-probed';
|
|
49
|
+
permissionPresets: 'not-probed';
|
|
50
|
+
approval: 'not-probed';
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export interface EffectiveBridgeCapability {
|
|
54
|
+
owner: 'none' | 'agy' | 'dsh' | 'unknown';
|
|
55
|
+
enabled: boolean | null;
|
|
56
|
+
agent: string | null;
|
|
57
|
+
internalTools: 'empty' | 'present' | 'unknown';
|
|
58
|
+
schemaCapability: 'unsupported' | 'agy-owned' | 'prompt-contract-v1' | 'unknown';
|
|
59
|
+
legacyConfig: {
|
|
60
|
+
agyOwned: boolean;
|
|
61
|
+
workspaceRootConfigured: boolean;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export interface EffectiveProfileConfig {
|
|
65
|
+
dumpStatus: ProfileDumpStatus;
|
|
66
|
+
provider: string | null;
|
|
67
|
+
model: string | null;
|
|
68
|
+
agent: string | null;
|
|
69
|
+
agentPreset: string | null;
|
|
70
|
+
sessionMode: string | null;
|
|
71
|
+
retryPolicy: {
|
|
72
|
+
maxRetries: number | null;
|
|
73
|
+
retryableCodes: readonly string[];
|
|
74
|
+
} | null;
|
|
75
|
+
purposeRoutes: {
|
|
76
|
+
compaction: EffectivePurposeRoute | null;
|
|
77
|
+
sessionTitle: EffectivePurposeRoute | null;
|
|
78
|
+
};
|
|
79
|
+
workspaceRootStatus: 'configured' | 'missing' | 'not-directory' | 'unknown';
|
|
80
|
+
workspaceSource: 'dsh-session-cwd' | 'config-legacy' | 'unknown';
|
|
81
|
+
visibleModels: {
|
|
82
|
+
raw: string | null;
|
|
83
|
+
count: number | null;
|
|
84
|
+
filtered: boolean;
|
|
85
|
+
};
|
|
86
|
+
modelEffortSplit: {
|
|
87
|
+
baseModel: string | null;
|
|
88
|
+
suffixDetected: boolean;
|
|
89
|
+
normalized: boolean;
|
|
90
|
+
};
|
|
91
|
+
imageInput: string | null;
|
|
92
|
+
modelCapability: {
|
|
93
|
+
inputModalities: readonly ('text' | 'image')[];
|
|
94
|
+
imageBridge: 'off' | 'experimental' | 'unknown';
|
|
95
|
+
};
|
|
96
|
+
agentCapability: {
|
|
97
|
+
knownPreset: boolean | null;
|
|
98
|
+
frontmatterValid: boolean | null;
|
|
99
|
+
tools: readonly string[];
|
|
100
|
+
viewFile: boolean | null;
|
|
101
|
+
writeTools: boolean | null;
|
|
102
|
+
commandExecutionPolicy: string | null;
|
|
103
|
+
mcpServersEmpty: boolean | null;
|
|
104
|
+
subagent: boolean | null;
|
|
105
|
+
};
|
|
106
|
+
dshContext: EffectiveDshContext;
|
|
107
|
+
bridgeCapability: EffectiveBridgeCapability;
|
|
108
|
+
}
|
|
109
|
+
export interface ProfileDiagnosticResult {
|
|
110
|
+
profileSchemaVersion: 4;
|
|
111
|
+
name: string;
|
|
112
|
+
dshHomePresent: boolean;
|
|
113
|
+
profilePresent: boolean;
|
|
114
|
+
packageInstalled: boolean;
|
|
115
|
+
bundleDeclared: boolean;
|
|
116
|
+
bundleEnabled: boolean | null;
|
|
117
|
+
toolPolicy: ToolPolicy | string | null;
|
|
118
|
+
effectiveProvider: string | null;
|
|
119
|
+
effectiveModel: string | null;
|
|
120
|
+
effective: EffectiveProfileConfig;
|
|
121
|
+
repairSuggestions: readonly string[];
|
|
122
|
+
issues: DiagnosticIssue[];
|
|
123
|
+
}
|
|
124
|
+
export interface DoctorOptions extends ProviderDiagnosticOptions {
|
|
125
|
+
profile?: string | undefined;
|
|
126
|
+
dshHome?: string | undefined;
|
|
127
|
+
dshBin?: string | undefined;
|
|
128
|
+
runProfileCommand?: ProfileDiagnosticOptions['runCommand'] | undefined;
|
|
129
|
+
}
|
|
130
|
+
export interface DoctorResult extends ProviderDiagnosticResult {
|
|
131
|
+
profile?: ProfileDiagnosticResult | undefined;
|
|
132
|
+
}
|
|
133
|
+
export declare function diagnoseProfile(options: ProfileDiagnosticOptions): Promise<ProfileDiagnosticResult>;
|
|
134
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorResult>;
|
|
135
|
+
export declare function formatDoctorHuman(result: DoctorResult): string;
|
|
136
|
+
export declare function formatDoctorHelp(): string;
|
|
137
|
+
export declare function runDoctorCli(argv: readonly string[], stdout?: (text: string) => void, stderr?: (text: string) => void): Promise<number>;
|
|
138
|
+
//# sourceMappingURL=doctor.d.ts.map
|