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,66 @@
|
|
|
1
|
+
import { type AgentPreset, type AgentPresetId } from './agent-presets.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where the AGY CLI discovers agent templates.
|
|
4
|
+
*
|
|
5
|
+
* This is `gemini-root-config/agents`, not `~/.gemini/config/agents`: the CLI
|
|
6
|
+
* keeps a second, differently-populated `agents` directory under its data root
|
|
7
|
+
* and only scans this one (`agy agents` lists a template added here and ignores
|
|
8
|
+
* one removed from the other). `DSH_AGY_CONFIG_ROOT` mirrors
|
|
9
|
+
* {@link agyConfigRoot} in `mcp/agy-registration.ts`, so pointing AGY at
|
|
10
|
+
* another root moves both the MCP table and the templates together.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_AGENT_DIRECTORY: string;
|
|
13
|
+
export type AgentInstallAction = 'create' | 'unchanged' | 'conflict' | 'backup-and-create';
|
|
14
|
+
export declare class AgentInstallError extends Error {
|
|
15
|
+
readonly code: 'AGENT_TARGET_EXISTS' | 'AGENT_TARGET_NOT_FILE' | 'AGENT_INSTALL_FAILED';
|
|
16
|
+
constructor(message: string, code: 'AGENT_TARGET_EXISTS' | 'AGENT_TARGET_NOT_FILE' | 'AGENT_INSTALL_FAILED', options?: ErrorOptions);
|
|
17
|
+
}
|
|
18
|
+
export interface AgentInstallOptions {
|
|
19
|
+
preset: AgentPresetId | string;
|
|
20
|
+
directory?: string;
|
|
21
|
+
apply?: boolean;
|
|
22
|
+
backup?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface AgentInstallResult {
|
|
25
|
+
preset: AgentPresetId;
|
|
26
|
+
agentName: string;
|
|
27
|
+
targetPath: string;
|
|
28
|
+
applied: boolean;
|
|
29
|
+
action: AgentInstallAction;
|
|
30
|
+
backupPath?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Preview or explicitly install one bundled Agent template.
|
|
34
|
+
* Existing files are never overwritten unless both apply and backup are set.
|
|
35
|
+
*/
|
|
36
|
+
export declare function installAgentPreset(options: AgentInstallOptions): Promise<AgentInstallResult>;
|
|
37
|
+
export interface AgentDirectoryEntry {
|
|
38
|
+
id: AgentPresetId;
|
|
39
|
+
agentName: string;
|
|
40
|
+
fileName: string;
|
|
41
|
+
description: string;
|
|
42
|
+
tools: readonly string[];
|
|
43
|
+
writeAccess: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** One bundled template that is not present in the directory AGY scans. */
|
|
46
|
+
export interface MissingAgentTemplate {
|
|
47
|
+
readonly preset: AgentPresetId;
|
|
48
|
+
readonly agentName: string;
|
|
49
|
+
readonly targetPath: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Which bundled templates are absent from the AGY agent directory.
|
|
53
|
+
*
|
|
54
|
+
* AGY refuses an unknown profile before it accepts any input, exiting with
|
|
55
|
+
* `error: unknown agent "<name>" (available: ...)`; the adapter then sees a
|
|
56
|
+
* child that died while its stdin was being written and reports
|
|
57
|
+
* `AGY_INPUT_FAILED`, which names the wrong component. Checking the directory up
|
|
58
|
+
* front turns that into one actionable line.
|
|
59
|
+
*
|
|
60
|
+
* Only presence is checked, never the contents: a user who edited their copy of
|
|
61
|
+
* a template meant to, and overwriting it is not this function's business.
|
|
62
|
+
*/
|
|
63
|
+
export declare function findMissingAgentTemplates(presets: readonly AgentPreset[], directory?: string): Promise<readonly MissingAgentTemplate[]>;
|
|
64
|
+
export declare function describeAgentPresets(): readonly AgentDirectoryEntry[];
|
|
65
|
+
export declare function isAgentPreset(value: unknown): value is AgentPresetId;
|
|
66
|
+
//# sourceMappingURL=agent-installer.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { basename, join, resolve } from 'node:path';
|
|
5
|
+
import { getAgentPreset, listAgentPresets, readAgentPresetTemplate, requireAgentPreset, } from './agent-presets.js';
|
|
6
|
+
/**
|
|
7
|
+
* Where the AGY CLI discovers agent templates.
|
|
8
|
+
*
|
|
9
|
+
* This is `gemini-root-config/agents`, not `~/.gemini/config/agents`: the CLI
|
|
10
|
+
* keeps a second, differently-populated `agents` directory under its data root
|
|
11
|
+
* and only scans this one (`agy agents` lists a template added here and ignores
|
|
12
|
+
* one removed from the other). `DSH_AGY_CONFIG_ROOT` mirrors
|
|
13
|
+
* {@link agyConfigRoot} in `mcp/agy-registration.ts`, so pointing AGY at
|
|
14
|
+
* another root moves both the MCP table and the templates together.
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_AGENT_DIRECTORY = join(process.env.DSH_AGY_CONFIG_ROOT?.trim() || join(homedir(), '.gemini', 'antigravity-cli', 'gemini-root-config'), 'agents');
|
|
17
|
+
export class AgentInstallError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(message, code, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.name = 'AgentInstallError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function configuredAgentDirectory(directory) {
|
|
26
|
+
const selected = directory?.trim() || process.env.AGY_AGENT_DIR?.trim() || DEFAULT_AGENT_DIRECTORY;
|
|
27
|
+
return resolve(selected);
|
|
28
|
+
}
|
|
29
|
+
async function targetState(targetPath) {
|
|
30
|
+
try {
|
|
31
|
+
const stat = await lstat(targetPath);
|
|
32
|
+
if (!stat.isFile())
|
|
33
|
+
return { exists: true, file: false };
|
|
34
|
+
return { exists: true, file: true, content: await readFile(targetPath, 'utf8') };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
38
|
+
return { exists: false };
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function backupPathFor(targetPath) {
|
|
43
|
+
return `${targetPath}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}-${randomUUID()}`;
|
|
44
|
+
}
|
|
45
|
+
async function writeAtomic(targetPath, content) {
|
|
46
|
+
const directory = resolve(targetPath, '..');
|
|
47
|
+
const tempPath = join(directory, `.${basename(targetPath)}.${randomUUID()}.tmp`);
|
|
48
|
+
try {
|
|
49
|
+
await writeFile(tempPath, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
50
|
+
await rename(tempPath, targetPath);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
await unlink(tempPath).catch(() => undefined);
|
|
54
|
+
throw new AgentInstallError(`Could not install Agent template: ${error instanceof Error ? error.message : String(error)}`, 'AGENT_INSTALL_FAILED', { cause: error });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function resultFor(preset, targetPath, applied, action, backupPath) {
|
|
58
|
+
return {
|
|
59
|
+
preset: preset.id,
|
|
60
|
+
agentName: preset.agentName,
|
|
61
|
+
targetPath,
|
|
62
|
+
applied,
|
|
63
|
+
action,
|
|
64
|
+
...(backupPath === undefined ? {} : { backupPath }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Preview or explicitly install one bundled Agent template.
|
|
69
|
+
* Existing files are never overwritten unless both apply and backup are set.
|
|
70
|
+
*/
|
|
71
|
+
export async function installAgentPreset(options) {
|
|
72
|
+
const preset = requireAgentPreset(options.preset);
|
|
73
|
+
const directory = configuredAgentDirectory(options.directory);
|
|
74
|
+
const targetPath = join(directory, preset.fileName);
|
|
75
|
+
const content = await readAgentPresetTemplate(preset);
|
|
76
|
+
const state = await targetState(targetPath);
|
|
77
|
+
if (state.exists && !state.file) {
|
|
78
|
+
throw new AgentInstallError(`Agent target is not a regular file: ${targetPath}`, 'AGENT_TARGET_NOT_FILE');
|
|
79
|
+
}
|
|
80
|
+
if (state.exists && state.content === content) {
|
|
81
|
+
// Nothing was written, so `applied` must stay false even under --apply:
|
|
82
|
+
// the CLI would otherwise report "applied" for a no-op.
|
|
83
|
+
return resultFor(preset, targetPath, false, 'unchanged');
|
|
84
|
+
}
|
|
85
|
+
if (state.exists && options.apply !== true) {
|
|
86
|
+
return resultFor(preset, targetPath, false, 'conflict');
|
|
87
|
+
}
|
|
88
|
+
if (state.exists && options.backup !== true) {
|
|
89
|
+
throw new AgentInstallError(`Agent target already exists; preview first, then use --backup to preserve it: ${targetPath}`, 'AGENT_TARGET_EXISTS');
|
|
90
|
+
}
|
|
91
|
+
if (options.apply !== true) {
|
|
92
|
+
return resultFor(preset, targetPath, false, 'create');
|
|
93
|
+
}
|
|
94
|
+
await mkdir(directory, { recursive: true });
|
|
95
|
+
let backupPath;
|
|
96
|
+
if (state.exists) {
|
|
97
|
+
backupPath = backupPathFor(targetPath);
|
|
98
|
+
await rename(targetPath, backupPath);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
await writeAtomic(targetPath, content);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (backupPath !== undefined) {
|
|
105
|
+
await rename(backupPath, targetPath).catch(() => undefined);
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
return resultFor(preset, targetPath, true, backupPath === undefined ? 'create' : 'backup-and-create', backupPath);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Which bundled templates are absent from the AGY agent directory.
|
|
113
|
+
*
|
|
114
|
+
* AGY refuses an unknown profile before it accepts any input, exiting with
|
|
115
|
+
* `error: unknown agent "<name>" (available: ...)`; the adapter then sees a
|
|
116
|
+
* child that died while its stdin was being written and reports
|
|
117
|
+
* `AGY_INPUT_FAILED`, which names the wrong component. Checking the directory up
|
|
118
|
+
* front turns that into one actionable line.
|
|
119
|
+
*
|
|
120
|
+
* Only presence is checked, never the contents: a user who edited their copy of
|
|
121
|
+
* a template meant to, and overwriting it is not this function's business.
|
|
122
|
+
*/
|
|
123
|
+
export async function findMissingAgentTemplates(presets, directory) {
|
|
124
|
+
const root = configuredAgentDirectory(directory);
|
|
125
|
+
const missing = [];
|
|
126
|
+
for (const preset of presets) {
|
|
127
|
+
const targetPath = join(root, preset.fileName);
|
|
128
|
+
const state = await targetState(targetPath);
|
|
129
|
+
if (!state.exists || !state.file)
|
|
130
|
+
missing.push({ preset: preset.id, agentName: preset.agentName, targetPath });
|
|
131
|
+
}
|
|
132
|
+
return missing;
|
|
133
|
+
}
|
|
134
|
+
export function describeAgentPresets() {
|
|
135
|
+
return listAgentPresets().map(preset => ({
|
|
136
|
+
id: preset.id,
|
|
137
|
+
agentName: preset.agentName,
|
|
138
|
+
fileName: preset.fileName,
|
|
139
|
+
description: preset.description,
|
|
140
|
+
tools: preset.tools,
|
|
141
|
+
writeAccess: preset.writeAccess,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
export function isAgentPreset(value) {
|
|
145
|
+
return getAgentPreset(value) !== undefined;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=agent-installer.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare const AGENT_PRESET_IDS: readonly ["tool-free", "mcp-executor", "image-view", "read-only", "workspace-write"];
|
|
2
|
+
export type AgentPresetId = typeof AGENT_PRESET_IDS[number];
|
|
3
|
+
export type AgentExecutionMode = 'plan' | 'accept-edits';
|
|
4
|
+
export interface AgentPreset {
|
|
5
|
+
readonly id: AgentPresetId;
|
|
6
|
+
readonly agentName: string;
|
|
7
|
+
readonly fileName: string;
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly tools: readonly string[];
|
|
10
|
+
readonly writeAccess: boolean;
|
|
11
|
+
readonly mode: AgentExecutionMode | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function listAgentPresets(): readonly AgentPreset[];
|
|
14
|
+
export declare function getAgentPreset(value: unknown): AgentPreset | undefined;
|
|
15
|
+
export declare function requireAgentPreset(value: string): AgentPreset;
|
|
16
|
+
/**
|
|
17
|
+
* The Agent templates one request can reach, given the effective settings.
|
|
18
|
+
*
|
|
19
|
+
* AGY resolves `--agent` itself and exits with `unknown agent` when the named
|
|
20
|
+
* profile has no template on disk, so the set that must exist is derivable from
|
|
21
|
+
* the config without starting anything:
|
|
22
|
+
*
|
|
23
|
+
* - `dsh-owned` pins the executor preset, but an image-bearing request swaps in
|
|
24
|
+
* the image preset, so an enabled `imageInput: experimental` adds it;
|
|
25
|
+
* - every other tool policy uses the configured `agent` string verbatim. That
|
|
26
|
+
* string is matched against the bundled names first, because a user who typed
|
|
27
|
+
* `dsh-agy-read-only` is naming a bundled template and deserves the same
|
|
28
|
+
* presence check; an unmatched name is returned with no preset so callers can
|
|
29
|
+
* see it is the user's own template to keep in place.
|
|
30
|
+
*/
|
|
31
|
+
export declare function agentTemplatesForRequest(options: {
|
|
32
|
+
readonly toolPolicy: 'reject' | 'agy-owned' | 'dsh-owned';
|
|
33
|
+
readonly agentPreset?: AgentPresetId | undefined;
|
|
34
|
+
readonly agent?: string | undefined;
|
|
35
|
+
readonly imageInput?: 'off' | 'experimental' | undefined;
|
|
36
|
+
}): readonly {
|
|
37
|
+
readonly preset: AgentPreset | undefined;
|
|
38
|
+
readonly agentName: string;
|
|
39
|
+
}[];
|
|
40
|
+
/** Read a bundled template; the template file is included in the npm tarball. */
|
|
41
|
+
export declare function readAgentPresetTemplate(value: AgentPresetId | AgentPreset): Promise<string>;
|
|
42
|
+
//# sourceMappingURL=agent-presets.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
export const AGENT_PRESET_IDS = ['tool-free', 'mcp-executor', 'image-view', 'read-only', 'workspace-write'];
|
|
3
|
+
const PRESETS = Object.freeze({
|
|
4
|
+
'tool-free': Object.freeze({
|
|
5
|
+
id: 'tool-free',
|
|
6
|
+
agentName: 'dsh-agy-tool-free',
|
|
7
|
+
fileName: 'dsh-agy-tool-free.md',
|
|
8
|
+
description: 'Text-only reasoning with no AGY tools.',
|
|
9
|
+
tools: Object.freeze([]),
|
|
10
|
+
writeAccess: false,
|
|
11
|
+
mode: undefined,
|
|
12
|
+
}),
|
|
13
|
+
'mcp-executor': Object.freeze({
|
|
14
|
+
id: 'mcp-executor',
|
|
15
|
+
agentName: 'dsh-agy-mcp-executor',
|
|
16
|
+
fileName: 'dsh-agy-mcp-executor.md',
|
|
17
|
+
// `excludeDefaultComponents: true` in the template is what removes the
|
|
18
|
+
// built-in tools; `tools: []` alone does not. Measured against AGY 1.3.2:
|
|
19
|
+
// an otherwise identical template carrying only `tools: []` still exposed
|
|
20
|
+
// all 60 built-ins, and the model called `run_command`, which headless mode
|
|
21
|
+
// then soft-denied, producing an empty response. A tool that does not exist
|
|
22
|
+
// cannot be called, which is the only reliable way to keep AGY's own
|
|
23
|
+
// executors out of the loop — an agent template that merely asks the model
|
|
24
|
+
// not to use them is bypassed probabilistically.
|
|
25
|
+
description: 'All actions through the DSH MCP server; no AGY built-in tools.',
|
|
26
|
+
tools: Object.freeze([]),
|
|
27
|
+
writeAccess: false,
|
|
28
|
+
mode: undefined,
|
|
29
|
+
}),
|
|
30
|
+
'image-view': Object.freeze({
|
|
31
|
+
id: 'image-view',
|
|
32
|
+
agentName: 'dsh-agy-image-view',
|
|
33
|
+
fileName: 'dsh-agy-image-view.md',
|
|
34
|
+
description: 'Per-request image inspection with only view_file enabled.',
|
|
35
|
+
tools: Object.freeze(['view_file']),
|
|
36
|
+
writeAccess: false,
|
|
37
|
+
mode: 'plan',
|
|
38
|
+
}),
|
|
39
|
+
'read-only': Object.freeze({
|
|
40
|
+
id: 'read-only',
|
|
41
|
+
agentName: 'dsh-agy-read-only',
|
|
42
|
+
fileName: 'dsh-agy-read-only.md',
|
|
43
|
+
description: 'Read-only workspace inspection and search.',
|
|
44
|
+
tools: Object.freeze(['find_by_name', 'grep_search', 'view_file', 'list_dir']),
|
|
45
|
+
writeAccess: false,
|
|
46
|
+
mode: 'plan',
|
|
47
|
+
}),
|
|
48
|
+
'workspace-write': Object.freeze({
|
|
49
|
+
id: 'workspace-write',
|
|
50
|
+
agentName: 'dsh-agy-workspace-write',
|
|
51
|
+
fileName: 'dsh-agy-workspace-write.md',
|
|
52
|
+
description: 'Bounded file edits inside an explicitly configured workspace.',
|
|
53
|
+
tools: Object.freeze([
|
|
54
|
+
'find_by_name',
|
|
55
|
+
'grep_search',
|
|
56
|
+
'view_file',
|
|
57
|
+
'list_dir',
|
|
58
|
+
'multi_replace_file_content',
|
|
59
|
+
'replace_file_content',
|
|
60
|
+
'write_to_file',
|
|
61
|
+
]),
|
|
62
|
+
writeAccess: true,
|
|
63
|
+
mode: 'accept-edits',
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
export function listAgentPresets() {
|
|
67
|
+
return AGENT_PRESET_IDS.map(id => PRESETS[id]);
|
|
68
|
+
}
|
|
69
|
+
export function getAgentPreset(value) {
|
|
70
|
+
if (typeof value !== 'string')
|
|
71
|
+
return undefined;
|
|
72
|
+
return AGENT_PRESET_IDS.includes(value)
|
|
73
|
+
? PRESETS[value]
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
76
|
+
export function requireAgentPreset(value) {
|
|
77
|
+
const preset = getAgentPreset(value);
|
|
78
|
+
if (preset === undefined) {
|
|
79
|
+
throw new TypeError(`Unknown Agent preset: ${value}. Expected one of: ${AGENT_PRESET_IDS.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
return preset;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The Agent templates one request can reach, given the effective settings.
|
|
85
|
+
*
|
|
86
|
+
* AGY resolves `--agent` itself and exits with `unknown agent` when the named
|
|
87
|
+
* profile has no template on disk, so the set that must exist is derivable from
|
|
88
|
+
* the config without starting anything:
|
|
89
|
+
*
|
|
90
|
+
* - `dsh-owned` pins the executor preset, but an image-bearing request swaps in
|
|
91
|
+
* the image preset, so an enabled `imageInput: experimental` adds it;
|
|
92
|
+
* - every other tool policy uses the configured `agent` string verbatim. That
|
|
93
|
+
* string is matched against the bundled names first, because a user who typed
|
|
94
|
+
* `dsh-agy-read-only` is naming a bundled template and deserves the same
|
|
95
|
+
* presence check; an unmatched name is returned with no preset so callers can
|
|
96
|
+
* see it is the user's own template to keep in place.
|
|
97
|
+
*/
|
|
98
|
+
export function agentTemplatesForRequest(options) {
|
|
99
|
+
const names = [];
|
|
100
|
+
const push = (preset, agentName) => {
|
|
101
|
+
if (agentName === undefined || names.some(entry => entry.agentName === agentName))
|
|
102
|
+
return;
|
|
103
|
+
names.push({ preset, agentName });
|
|
104
|
+
};
|
|
105
|
+
if (options.toolPolicy === 'dsh-owned') {
|
|
106
|
+
const executor = getAgentPreset('mcp-executor');
|
|
107
|
+
push(executor, executor?.agentName);
|
|
108
|
+
if (options.imageInput === 'experimental') {
|
|
109
|
+
const image = getAgentPreset('image-view');
|
|
110
|
+
push(image, image?.agentName);
|
|
111
|
+
}
|
|
112
|
+
return names;
|
|
113
|
+
}
|
|
114
|
+
const byPresetId = options.agentPreset === undefined ? undefined : getAgentPreset(options.agentPreset);
|
|
115
|
+
if (byPresetId !== undefined) {
|
|
116
|
+
push(byPresetId, byPresetId.agentName);
|
|
117
|
+
return names;
|
|
118
|
+
}
|
|
119
|
+
const byName = options.agent === undefined
|
|
120
|
+
? undefined
|
|
121
|
+
: listAgentPresets().find(preset => preset.agentName === options.agent || preset.id === options.agent);
|
|
122
|
+
push(byName, options.agent);
|
|
123
|
+
return names;
|
|
124
|
+
}
|
|
125
|
+
/** Read a bundled template; the template file is included in the npm tarball. */
|
|
126
|
+
export async function readAgentPresetTemplate(value) {
|
|
127
|
+
const preset = typeof value === 'string' ? requireAgentPreset(value) : value;
|
|
128
|
+
return readFile(new URL(`../agents/${preset.fileName}`, import.meta.url), 'utf8');
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=agent-presets.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AgentInstallError, describeAgentPresets, installAgentPreset, } from './agent-installer.js';
|
|
2
|
+
import { AGENT_PRESET_IDS } from './agent-presets.js';
|
|
3
|
+
function optionValue(args, name) {
|
|
4
|
+
const index = args.indexOf(name);
|
|
5
|
+
if (index === -1)
|
|
6
|
+
return undefined;
|
|
7
|
+
const value = args[index + 1];
|
|
8
|
+
if (value === undefined || value.startsWith('--'))
|
|
9
|
+
return undefined;
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export function formatAgentsHelp() {
|
|
13
|
+
return `Usage: dsh-agy-cli agents <command> [options]
|
|
14
|
+
|
|
15
|
+
Quota-free Agent template management. Preview is the default; no files are changed without --apply.
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
list List bundled Agent presets
|
|
19
|
+
install <preset> Preview or install one preset
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--dir <path> Agent directory (default: $AGY_AGENT_DIR or the AGY config root's agents/)
|
|
23
|
+
--apply Write the template; omitted means preview only
|
|
24
|
+
--backup Move an existing file aside before --apply
|
|
25
|
+
--json Output structured JSON
|
|
26
|
+
-h, --help Show help information
|
|
27
|
+
|
|
28
|
+
Presets: ${AGENT_PRESET_IDS.join(', ')}
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
npx dsh-agy-cli agents list
|
|
32
|
+
npx dsh-agy-cli agents install read-only --dir ~/.gemini/antigravity-cli/gemini-root-config/agents
|
|
33
|
+
npx dsh-agy-cli agents install workspace-write --apply --backup
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
function formatInstallResult(result) {
|
|
37
|
+
// "unchanged" is neither applied nor a preview: the file already holds the
|
|
38
|
+
// target content, so saying either would mislead the operator.
|
|
39
|
+
const mode = result.action === 'unchanged' ? 'unchanged' : result.applied ? 'applied' : 'preview';
|
|
40
|
+
const lines = [
|
|
41
|
+
`mode: ${mode}`,
|
|
42
|
+
`preset: ${result.preset}`,
|
|
43
|
+
`agent: ${result.agentName}`,
|
|
44
|
+
`target: ${result.targetPath}`,
|
|
45
|
+
`action: ${result.action}`,
|
|
46
|
+
];
|
|
47
|
+
if (result.backupPath !== undefined)
|
|
48
|
+
lines.push(`backup: ${result.backupPath}`);
|
|
49
|
+
if (!result.applied && result.action === 'create')
|
|
50
|
+
lines.push('next: rerun with --apply to write the file');
|
|
51
|
+
if (!result.applied && result.action === 'conflict')
|
|
52
|
+
lines.push('next: rerun with --apply --backup to preserve and replace the file');
|
|
53
|
+
return lines.join('\n');
|
|
54
|
+
}
|
|
55
|
+
export async function runAgentsCli(argv, stdout = text => process.stdout.write(text), stderr = text => process.stderr.write(text)) {
|
|
56
|
+
const args = [...argv];
|
|
57
|
+
if (args[0] === 'agents' || args[0] === 'agent')
|
|
58
|
+
args.shift();
|
|
59
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
60
|
+
stdout(`${formatAgentsHelp()}\n`);
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
const command = args.shift() ?? 'list';
|
|
64
|
+
const jsonOutput = args.includes('--json');
|
|
65
|
+
if (command === 'list') {
|
|
66
|
+
const entries = describeAgentPresets();
|
|
67
|
+
if (jsonOutput)
|
|
68
|
+
stdout(`${JSON.stringify({ quotaUsed: false, presets: entries }, null, 2)}\n`);
|
|
69
|
+
else {
|
|
70
|
+
stdout(`${entries.map(entry => `${entry.id}: ${entry.agentName} [${entry.tools.join(', ') || 'no tools'}]`).join('\n')}\n`);
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (command !== 'install') {
|
|
75
|
+
stderr(`Unknown agents command: ${command}. Use --help for usage.\n`);
|
|
76
|
+
return 2;
|
|
77
|
+
}
|
|
78
|
+
const preset = args.shift();
|
|
79
|
+
if (preset === undefined) {
|
|
80
|
+
stderr(`Missing Agent preset. Expected one of: ${AGENT_PRESET_IDS.join(', ')}\n`);
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const directory = optionValue(args, '--dir');
|
|
85
|
+
const result = await installAgentPreset({
|
|
86
|
+
preset,
|
|
87
|
+
...(directory === undefined ? {} : { directory }),
|
|
88
|
+
apply: args.includes('--apply'),
|
|
89
|
+
backup: args.includes('--backup'),
|
|
90
|
+
});
|
|
91
|
+
if (jsonOutput)
|
|
92
|
+
stdout(`${JSON.stringify({ quotaUsed: false, ...result }, null, 2)}\n`);
|
|
93
|
+
else
|
|
94
|
+
stdout(`${formatInstallResult(result)}\n`);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
if (jsonOutput) {
|
|
100
|
+
stdout(`${JSON.stringify({ quotaUsed: false, ok: false, error: {
|
|
101
|
+
code: error instanceof AgentInstallError ? error.code : 'AGENT_INSTALL_FAILED',
|
|
102
|
+
message,
|
|
103
|
+
} }, null, 2)}\n`);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
stderr(`Error installing Agent template: ${message}\n`);
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=agents-cli.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ProcessRequest, type ProcessResult } from './process.js';
|
|
2
|
+
export interface AgyDiagnosticOptions {
|
|
3
|
+
executable?: string;
|
|
4
|
+
expectedAgent?: string;
|
|
5
|
+
minimumVersion?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
runCommand?: AgyDiagnosticCommand;
|
|
8
|
+
}
|
|
9
|
+
export type AgyDiagnosticCommand = (request: ProcessRequest) => Promise<ProcessResult>;
|
|
10
|
+
export interface AgyDiagnosticResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
/** Safe source label; never contains the resolved user filesystem path. */
|
|
13
|
+
executable: string;
|
|
14
|
+
executableSource: 'explicit' | 'environment' | 'path';
|
|
15
|
+
version: string | undefined;
|
|
16
|
+
agents: readonly string[];
|
|
17
|
+
expectedAgent: string | undefined;
|
|
18
|
+
minimumVersion: string | undefined;
|
|
19
|
+
versionSupported: boolean;
|
|
20
|
+
agentAvailable: boolean;
|
|
21
|
+
errors: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function parseAgyVersion(output: string): string | undefined;
|
|
24
|
+
export declare function isAgyVersionAtLeast(actual: string, minimum: string): boolean;
|
|
25
|
+
/** Parse the plain one-agent-per-line output of `agy agents`. */
|
|
26
|
+
export declare function parseAgyAgents(output: string): string[];
|
|
27
|
+
/** Check the local AGY executable without spending model quota or invoking tools. */
|
|
28
|
+
export declare function diagnoseAgy(options?: AgyDiagnosticOptions): Promise<AgyDiagnosticResult>;
|
|
29
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { AgyProcessError, resolveAgyExecutable, runProcess } from './process.js';
|
|
2
|
+
import { redactText } from './redact.js';
|
|
3
|
+
function parseVersionValue(value) {
|
|
4
|
+
const match = /(?:^|\D)(\d+)\.(\d+)\.(\d+)(?:$|\D)/.exec(value);
|
|
5
|
+
if (match === null)
|
|
6
|
+
return undefined;
|
|
7
|
+
const major = Number(match[1]);
|
|
8
|
+
const minor = Number(match[2]);
|
|
9
|
+
const patch = Number(match[3]);
|
|
10
|
+
if (![major, minor, patch].every(Number.isSafeInteger))
|
|
11
|
+
return undefined;
|
|
12
|
+
return { major, minor, patch, text: `${major}.${minor}.${patch}` };
|
|
13
|
+
}
|
|
14
|
+
export function parseAgyVersion(output) {
|
|
15
|
+
return parseVersionValue(output)?.text;
|
|
16
|
+
}
|
|
17
|
+
export function isAgyVersionAtLeast(actual, minimum) {
|
|
18
|
+
const actualVersion = parseVersionValue(actual);
|
|
19
|
+
const minimumVersion = parseVersionValue(minimum);
|
|
20
|
+
if (actualVersion === undefined || minimumVersion === undefined)
|
|
21
|
+
return false;
|
|
22
|
+
if (actualVersion.major !== minimumVersion.major)
|
|
23
|
+
return actualVersion.major > minimumVersion.major;
|
|
24
|
+
if (actualVersion.minor !== minimumVersion.minor)
|
|
25
|
+
return actualVersion.minor > minimumVersion.minor;
|
|
26
|
+
return actualVersion.patch >= minimumVersion.patch;
|
|
27
|
+
}
|
|
28
|
+
/** Parse the plain one-agent-per-line output of `agy agents`. */
|
|
29
|
+
export function parseAgyAgents(output) {
|
|
30
|
+
return [...new Set(output
|
|
31
|
+
.split(/\r?\n/)
|
|
32
|
+
.map(line => line.trim())
|
|
33
|
+
.filter(line => /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(line)))];
|
|
34
|
+
}
|
|
35
|
+
function commandRequest(executable, args, timeoutMs) {
|
|
36
|
+
return timeoutMs === undefined
|
|
37
|
+
? { executable, args, windowsNoConsole: true }
|
|
38
|
+
: { executable, args, timeoutMs, windowsNoConsole: true };
|
|
39
|
+
}
|
|
40
|
+
function commandFailure(label, result) {
|
|
41
|
+
if (result.termination === 'completed' && result.exitCode === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
return `${label} failed (${result.termination}, exit code ${result.exitCode ?? 'unknown'})`;
|
|
44
|
+
}
|
|
45
|
+
function executableSource(options) {
|
|
46
|
+
if (options.executable?.trim())
|
|
47
|
+
return 'explicit';
|
|
48
|
+
if (process.env.AGY_PATH?.trim() || process.env.AGY_EXECUTABLE?.trim())
|
|
49
|
+
return 'environment';
|
|
50
|
+
return 'path';
|
|
51
|
+
}
|
|
52
|
+
async function runDiagnosticCommand(label, executable, args, timeoutMs, runCommand) {
|
|
53
|
+
try {
|
|
54
|
+
const result = await runCommand(commandRequest(executable, args, timeoutMs));
|
|
55
|
+
return { result, error: commandFailure(label, result) };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof AgyProcessError) {
|
|
59
|
+
return { result: undefined, error: redactText(error.message, 512) };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
result: undefined,
|
|
63
|
+
error: `${label} failed: ${redactText(error instanceof Error ? error.message : String(error), 512)}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Check the local AGY executable without spending model quota or invoking tools. */
|
|
68
|
+
export async function diagnoseAgy(options = {}) {
|
|
69
|
+
const executable = resolveAgyExecutable(options.executable);
|
|
70
|
+
const source = executableSource(options);
|
|
71
|
+
const runCommand = options.runCommand ?? runProcess;
|
|
72
|
+
const versionCheck = await runDiagnosticCommand('agy --version', executable, ['--version'], options.timeoutMs, runCommand);
|
|
73
|
+
const agentsCheck = await runDiagnosticCommand('agy agents', executable, ['agents'], options.timeoutMs, runCommand);
|
|
74
|
+
const versionOutput = versionCheck.result === undefined
|
|
75
|
+
? ''
|
|
76
|
+
: versionCheck.result.stdoutLines.join('\n');
|
|
77
|
+
const version = parseAgyVersion(versionOutput);
|
|
78
|
+
const agentsOutput = agentsCheck.result === undefined
|
|
79
|
+
? ''
|
|
80
|
+
: agentsCheck.result.stdoutLines.join('\n');
|
|
81
|
+
const agents = parseAgyAgents(agentsOutput);
|
|
82
|
+
const versionSupported = options.minimumVersion === undefined
|
|
83
|
+
? version !== undefined
|
|
84
|
+
: version !== undefined && isAgyVersionAtLeast(version, options.minimumVersion);
|
|
85
|
+
const agentAvailable = options.expectedAgent === undefined || agents.includes(options.expectedAgent);
|
|
86
|
+
const errors = [
|
|
87
|
+
...(versionCheck.error === undefined ? [] : [versionCheck.error]),
|
|
88
|
+
...(agentsCheck.error === undefined ? [] : [agentsCheck.error]),
|
|
89
|
+
...(version === undefined ? ['Unable to parse AGY version from --version output'] : []),
|
|
90
|
+
...(options.minimumVersion !== undefined && !versionSupported
|
|
91
|
+
? [`AGY version ${version ?? 'unknown'} is below the required minimum ${options.minimumVersion}`]
|
|
92
|
+
: []),
|
|
93
|
+
...(options.expectedAgent !== undefined && !agentAvailable
|
|
94
|
+
? [`AGY Agent "${redactText(options.expectedAgent, 128)}" was not found`] : []),
|
|
95
|
+
];
|
|
96
|
+
return {
|
|
97
|
+
ok: errors.length === 0,
|
|
98
|
+
executable: source,
|
|
99
|
+
executableSource: source,
|
|
100
|
+
version,
|
|
101
|
+
agents,
|
|
102
|
+
expectedAgent: options.expectedAgent,
|
|
103
|
+
minimumVersion: options.minimumVersion,
|
|
104
|
+
versionSupported,
|
|
105
|
+
agentAvailable,
|
|
106
|
+
errors,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
/** Stable provider-neutral codes emitted after classifying AGY text/status details. */
|
|
3
|
+
export type AgyClassifiedErrorCode = typeof CONTEXT_WINDOW_EXCEEDED_CODE | typeof QUOTA_EXCEEDED_CODE | 'AUTH' | 'RATE_LIMIT' | 'MODEL_NOT_FOUND' | 'AGY_AGENT_MISSING' | 'AGY_STATUS' | 'AGY_EXIT' | 'TRANSPORT';
|
|
4
|
+
/**
|
|
5
|
+
* Classify observed AGY status/stderr/error-event detail. This is deliberately
|
|
6
|
+
* conservative: unknown text keeps the caller-supplied fallback code.
|
|
7
|
+
*
|
|
8
|
+
* Note the ordering is load-bearing for 429s. AGY emits
|
|
9
|
+
* `RESOURCE_EXHAUSTED (code 429)` for both a drained 5h/weekly bucket and a
|
|
10
|
+
* transient burst limit; only the drained form names a reset far in the future.
|
|
11
|
+
* The drained form is reported as RATE_LIMIT so the bounded retry in
|
|
12
|
+
* `dsh-llm-retry` can re-issue the turn — live probes showed the bucket sits on
|
|
13
|
+
* a boundary value where 1 in 5 attempts succeeds (probe_retry_efficacy.mjs:
|
|
14
|
+
* 5/5 rounds recovered, 2-3 attempts each), while a 92-96h reset means retrying
|
|
15
|
+
* beyond a few attempts is pure token burn.
|
|
16
|
+
*/
|
|
17
|
+
export declare function classifyAgyFailure(detail: string, fallback: string): string;
|
|
18
|
+
/** Keep user-facing failure text bounded and redacted; structured logs use only the code. */
|
|
19
|
+
export declare function safeAgyFailureMessage(prefix: string, detail: string | undefined): string;
|
|
20
|
+
//# sourceMappingURL=errors.d.ts.map
|