@apolloyh/apollo-agent 0.1.1 → 0.1.3
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/.env.example +2 -1
- package/README.md +1 -1
- package/dist/brand.d.ts +2 -2
- package/dist/brand.js +2 -2
- package/dist/config.d.ts +9 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -9
- package/dist/index.js +18 -2
- package/dist/runtime/permissions.d.ts.map +1 -1
- package/dist/runtime/permissions.js +17 -0
- package/dist/runtime/prompt-builder.js +1 -1
- package/dist/runtime/tool-policy.d.ts.map +1 -1
- package/dist/runtime/tool-policy.js +2 -1
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +52 -0
- package/dist/trace.js +3 -1
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -11,7 +11,8 @@ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME=glm-5.2
|
|
|
11
11
|
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME=glm-5.2
|
|
12
12
|
|
|
13
13
|
# Optional
|
|
14
|
-
#
|
|
14
|
+
# GLM-5 supports up to 128K output tokens; lower this for providers with smaller output limits.
|
|
15
|
+
# ANTHROPIC_MAX_TOKENS=131072
|
|
15
16
|
# ANTHROPIC_TIMEOUT_MS=300000
|
|
16
17
|
# ANTHROPIC_CACHE_TTL=5m
|
|
17
18
|
# AGENT_SENDMAIL_PATH=/usr/sbin/sendmail
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ apollo -r # pick a saved project session
|
|
|
33
33
|
apollo -r <session-id>
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Apollo
|
|
36
|
+
Apollo loads project `.apollo/.env`, project `.env`, then user-level `~/.apollo/.env`; use `--env-file <path>` to override discovery.
|
|
37
37
|
|
|
38
38
|
Optional project configuration lives at `.apollo/config.json`; Apollo also reads the legacy
|
|
39
39
|
`agent.config.json` during migration. Copy `.apollo/config.example.json` to get started.
|
package/dist/brand.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** Product identity — single source of truth for display strings. */
|
|
2
2
|
export declare const PRODUCT: {
|
|
3
3
|
readonly name: "Apollo";
|
|
4
|
-
readonly version: "0.1.
|
|
4
|
+
readonly version: "0.1.3";
|
|
5
5
|
readonly tagline: "Your personal agent — answer first, act when needed.";
|
|
6
|
-
readonly packageName: "apollo-agent";
|
|
6
|
+
readonly packageName: "@apolloyh/apollo-agent";
|
|
7
7
|
readonly userAgent: "ApolloAgent/0.1";
|
|
8
8
|
readonly prompt: "apollo";
|
|
9
9
|
};
|
package/dist/brand.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** Product identity — single source of truth for display strings. */
|
|
2
2
|
export const PRODUCT = {
|
|
3
3
|
name: "Apollo",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.3",
|
|
5
5
|
tagline: "Your personal agent — answer first, act when needed.",
|
|
6
|
-
packageName: "apollo-agent",
|
|
6
|
+
packageName: "@apolloyh/apollo-agent",
|
|
7
7
|
userAgent: "ApolloAgent/0.1",
|
|
8
8
|
prompt: "apollo",
|
|
9
9
|
};
|
package/dist/config.d.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import type { AgentConfig, LlmConfig, SubagentConfig } from "./types.js";
|
|
2
2
|
/** Default subagents — Claude Code–style Task roles. */
|
|
3
3
|
export declare const DEFAULT_AGENTS: SubagentConfig[];
|
|
4
|
+
export declare const MISSING_AUTH_TOKEN_MESSAGE = "Missing ANTHROPIC_AUTH_TOKEN. Add it to ~/.apollo/.env, .apollo/.env, .env, or your shell, then restart Apollo.";
|
|
5
|
+
export declare function ensureUserEnvFile(): {
|
|
6
|
+
path: string;
|
|
7
|
+
created: boolean;
|
|
8
|
+
};
|
|
4
9
|
export declare function loadAgentConfig(configPath?: string): Promise<AgentConfig>;
|
|
5
10
|
/**
|
|
6
|
-
* Load an explicit env file, or discover
|
|
11
|
+
* Load an explicit env file, or discover project env then ~/.apollo/.env.
|
|
7
12
|
* By default **overrides** existing shell vars so local agent config wins.
|
|
8
13
|
*/
|
|
9
14
|
export declare function loadEnvFile(envPath?: string, options?: {
|
|
10
15
|
override?: boolean;
|
|
11
16
|
}): boolean;
|
|
12
|
-
export declare function loadLlmConfig(
|
|
17
|
+
export declare function loadLlmConfig(options?: {
|
|
18
|
+
allowMissingAuth?: boolean;
|
|
19
|
+
}): LlmConfig;
|
|
13
20
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE,wDAAwD;AACxD,eAAO,MAAM,cAAc,EAAE,cAAc,EA4B1C,CAAC;AAsBF,eAAO,MAAM,0BAA0B,oHAC4E,CAAC;AAWpH,wBAAgB,iBAAiB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAatE;AAED,wBAAsB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0B/E;AAiBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAwB3F;AAYD,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAkCrF"}
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
/** Default subagents — Claude Code–style Task roles. */
|
|
5
6
|
export const DEFAULT_AGENTS = [
|
|
@@ -47,6 +48,31 @@ const defaultConfig = {
|
|
|
47
48
|
};
|
|
48
49
|
const DEFAULT_CONFIG_PATH = ".apollo/config.json";
|
|
49
50
|
const LEGACY_CONFIG_PATH = "agent.config.json";
|
|
51
|
+
export const MISSING_AUTH_TOKEN_MESSAGE = "Missing ANTHROPIC_AUTH_TOKEN. Add it to ~/.apollo/.env, .apollo/.env, .env, or your shell, then restart Apollo.";
|
|
52
|
+
const USER_ENV_TEMPLATE = `# Apollo uses Anthropic-compatible environment variable names.
|
|
53
|
+
# Add your provider token below, then restart Apollo.
|
|
54
|
+
ANTHROPIC_AUTH_TOKEN=
|
|
55
|
+
|
|
56
|
+
# Optional provider settings:
|
|
57
|
+
# ANTHROPIC_BASE_URL=https://api.anthropic.com
|
|
58
|
+
# ANTHROPIC_MODEL=claude-haiku-4-5
|
|
59
|
+
`;
|
|
60
|
+
export function ensureUserEnvFile() {
|
|
61
|
+
const directory = path.join(os.homedir(), ".apollo");
|
|
62
|
+
const envPath = path.join(directory, ".env");
|
|
63
|
+
if (fsSync.existsSync(envPath))
|
|
64
|
+
return { path: envPath, created: false };
|
|
65
|
+
fsSync.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
66
|
+
try {
|
|
67
|
+
fsSync.writeFileSync(envPath, USER_ENV_TEMPLATE, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
68
|
+
return { path: envPath, created: true };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code === "EEXIST")
|
|
72
|
+
return { path: envPath, created: false };
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
50
76
|
export async function loadAgentConfig(configPath) {
|
|
51
77
|
const discoveredPath = configPath ?? [DEFAULT_CONFIG_PATH, LEGACY_CONFIG_PATH].find(fsSync.existsSync);
|
|
52
78
|
if (!discoveredPath) {
|
|
@@ -84,17 +110,18 @@ function normalizeAgents(list) {
|
|
|
84
110
|
}));
|
|
85
111
|
}
|
|
86
112
|
/**
|
|
87
|
-
* Load an explicit env file, or discover
|
|
113
|
+
* Load an explicit env file, or discover project env then ~/.apollo/.env.
|
|
88
114
|
* By default **overrides** existing shell vars so local agent config wins.
|
|
89
115
|
*/
|
|
90
116
|
export function loadEnvFile(envPath, options = {}) {
|
|
91
|
-
const
|
|
117
|
+
const userEnvPath = path.join(os.homedir(), ".apollo", ".env");
|
|
118
|
+
const selectedPath = envPath ?? [".apollo/.env", ".env", userEnvPath].find(fsSync.existsSync);
|
|
92
119
|
if (!selectedPath)
|
|
93
120
|
return false;
|
|
94
121
|
const resolvedPath = path.resolve(selectedPath);
|
|
95
122
|
if (!fsSync.existsSync(resolvedPath))
|
|
96
123
|
return false;
|
|
97
|
-
const override = options.override ??
|
|
124
|
+
const override = options.override ?? (envPath !== undefined || path.resolve(selectedPath) !== userEnvPath);
|
|
98
125
|
const raw = fsSync.readFileSync(resolvedPath, "utf8");
|
|
99
126
|
for (const line of raw.split(/\r?\n/)) {
|
|
100
127
|
const trimmed = line.trim();
|
|
@@ -120,7 +147,7 @@ function unwrapEnvValue(value) {
|
|
|
120
147
|
}
|
|
121
148
|
return value;
|
|
122
149
|
}
|
|
123
|
-
export function loadLlmConfig() {
|
|
150
|
+
export function loadLlmConfig(options = {}) {
|
|
124
151
|
const authToken = process.env.ANTHROPIC_AUTH_TOKEN;
|
|
125
152
|
const baseUrl = process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
|
|
126
153
|
const model = process.env.ANTHROPIC_MODEL ||
|
|
@@ -128,14 +155,14 @@ export function loadLlmConfig() {
|
|
|
128
155
|
process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME ||
|
|
129
156
|
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME ||
|
|
130
157
|
"claude-haiku-4-5";
|
|
131
|
-
if (!authToken)
|
|
132
|
-
throw new Error(
|
|
133
|
-
}
|
|
158
|
+
if (!authToken && !options.allowMissingAuth)
|
|
159
|
+
throw new Error(MISSING_AUTH_TOKEN_MESSAGE);
|
|
134
160
|
const cacheTtl = process.env.ANTHROPIC_CACHE_TTL ?? "5m";
|
|
135
161
|
if (cacheTtl !== "5m" && cacheTtl !== "1h") {
|
|
136
162
|
throw new Error("ANTHROPIC_CACHE_TTL must be 5m or 1h.");
|
|
137
163
|
}
|
|
138
|
-
const
|
|
164
|
+
const providerDefaultMaxTokens = /(?:^glm-|bigmodel\.cn)/i.test(`${model} ${baseUrl}`) ? 128 * 1024 : 4096;
|
|
165
|
+
const maxTokens = positiveNumberEnv("ANTHROPIC_MAX_TOKENS", providerDefaultMaxTokens, true);
|
|
139
166
|
const timeoutMs = positiveNumberEnv("ANTHROPIC_TIMEOUT_MS", 300000, true);
|
|
140
167
|
let parsedBaseUrl;
|
|
141
168
|
try {
|
|
@@ -148,7 +175,7 @@ export function loadLlmConfig() {
|
|
|
148
175
|
throw new Error("ANTHROPIC_BASE_URL must be a valid HTTP or HTTPS URL.");
|
|
149
176
|
}
|
|
150
177
|
return {
|
|
151
|
-
authToken,
|
|
178
|
+
authToken: authToken ?? "",
|
|
152
179
|
baseUrl,
|
|
153
180
|
model,
|
|
154
181
|
maxTokens,
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { stdin, stdout } from "node:process";
|
|
|
3
3
|
import readline from "node:readline/promises";
|
|
4
4
|
import { ASCII_LOGO, PRODUCT } from "./brand.js";
|
|
5
5
|
import { parseArgs } from "./cli-args.js";
|
|
6
|
-
import { loadAgentConfig, loadEnvFile, loadLlmConfig } from "./config.js";
|
|
6
|
+
import { ensureUserEnvFile, loadAgentConfig, loadEnvFile, loadLlmConfig, MISSING_AUTH_TOKEN_MESSAGE, } from "./config.js";
|
|
7
7
|
import { pickSpinnerVerb } from "./constants/spinner-verbs.js";
|
|
8
8
|
import { QueryEngine } from "./runtime/query-engine.js";
|
|
9
9
|
import { createCliApprovalProvider } from "./runtime/permissions.js";
|
|
@@ -56,6 +56,7 @@ function filterSlashCommands(buffer) {
|
|
|
56
56
|
}
|
|
57
57
|
async function main() {
|
|
58
58
|
const args = parseArgs(process.argv.slice(2));
|
|
59
|
+
const userEnv = ensureUserEnvFile();
|
|
59
60
|
loadEnvFile(args.envPath);
|
|
60
61
|
const task = args.task ?? (stdin.isTTY ? undefined : (await readStdin()).trim());
|
|
61
62
|
if (!task && !stdin.isTTY) {
|
|
@@ -64,8 +65,8 @@ async function main() {
|
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
const agentConfig = await loadAgentConfig(args.configPath);
|
|
67
|
-
const llmConfig = loadLlmConfig();
|
|
68
68
|
const interactiveMode = !task && stdin.isTTY;
|
|
69
|
+
const llmConfig = loadLlmConfig({ allowMissingAuth: interactiveMode });
|
|
69
70
|
const yolo = args.yolo;
|
|
70
71
|
const thoughtFold = new ThoughtFoldManager({
|
|
71
72
|
color: args.color,
|
|
@@ -235,6 +236,9 @@ async function main() {
|
|
|
235
236
|
quiet: !traceOptions.verbose,
|
|
236
237
|
yolo: yolo || agentConfig.permissions.mode === "unrestricted",
|
|
237
238
|
}));
|
|
239
|
+
if (userEnv.created) {
|
|
240
|
+
stdout.write(`\nCreated ${shortenHome(userEnv.path)}. Add ANTHROPIC_AUTH_TOKEN there, then restart Apollo.\n`);
|
|
241
|
+
}
|
|
238
242
|
const approvalProvider = createCliApprovalProvider(agentConfig, traceSink, () => yolo, {
|
|
239
243
|
beforePrompt: () => {
|
|
240
244
|
stopLiveStatus("none");
|
|
@@ -310,6 +314,8 @@ async function main() {
|
|
|
310
314
|
` ${PRODUCT.name} v${PRODUCT.version}`,
|
|
311
315
|
` model ${llmConfig.model}`,
|
|
312
316
|
` base ${llmConfig.baseUrl}`,
|
|
317
|
+
` output max ${llmConfig.maxTokens.toLocaleString()} tokens`,
|
|
318
|
+
` context ${agentConfig.context.maxChars.toLocaleString()} chars`,
|
|
313
319
|
` cwd ${shortenHome(agentConfig.workspaceRoot)}`,
|
|
314
320
|
` messages ${runtime.conversationSize()}`,
|
|
315
321
|
` tokens ${usage.input_tokens.toLocaleString()} input / ${usage.output_tokens.toLocaleString()} output`,
|
|
@@ -530,6 +536,8 @@ async function main() {
|
|
|
530
536
|
activeVerb = pickSpinnerVerb(text);
|
|
531
537
|
startPinnedCliInput(traceOptions.color);
|
|
532
538
|
try {
|
|
539
|
+
if (!llmConfig.authToken)
|
|
540
|
+
throw new Error(MISSING_AUTH_TOKEN_MESSAGE);
|
|
533
541
|
const result = await runWithRenderer(renderer, () => runtime.submitMessage(text));
|
|
534
542
|
stopLiveStatus("success");
|
|
535
543
|
if (!traceOptions.stream)
|
|
@@ -828,11 +836,17 @@ function formatToolCallStatus(tool, input) {
|
|
|
828
836
|
const lines = input.content ? input.content.split(/\r?\n/).length : 0;
|
|
829
837
|
return `${label}${detail} · ${lines} ${lines === 1 ? "line" : "lines"}`;
|
|
830
838
|
}
|
|
839
|
+
if (tool === "edit_file" && typeof input.new_string === "string") {
|
|
840
|
+
const lines = input.new_string ? input.new_string.split(/\r?\n/).length : 0;
|
|
841
|
+
return `${label}${detail} · ${lines} replacement ${lines === 1 ? "line" : "lines"}`;
|
|
842
|
+
}
|
|
831
843
|
return `${label}${detail}`;
|
|
832
844
|
}
|
|
833
845
|
function formatToolStatusName(tool) {
|
|
834
846
|
if (tool === "write_file")
|
|
835
847
|
return "Write";
|
|
848
|
+
if (tool === "edit_file")
|
|
849
|
+
return "Edit";
|
|
836
850
|
if (tool === "shell_exec")
|
|
837
851
|
return "Bash";
|
|
838
852
|
if (tool === "Task")
|
|
@@ -1686,6 +1700,8 @@ function formatRuntimeError(error, verbose) {
|
|
|
1686
1700
|
if (isAbortError(error))
|
|
1687
1701
|
return "Interrupted by user.";
|
|
1688
1702
|
const message = error instanceof Error ? error.message : String(error);
|
|
1703
|
+
if (message === MISSING_AUTH_TOKEN_MESSAGE)
|
|
1704
|
+
return `Apollo is not configured yet. ${message}`;
|
|
1689
1705
|
if (verbose && error instanceof Error)
|
|
1690
1706
|
return error.stack ?? message;
|
|
1691
1707
|
const bounded = message.length > 1200 ? `${message.slice(0, 1200)}...` : message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/runtime/permissions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7F,KAAK,kBAAkB,GAAG;IACxB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC5C,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,EACpC,OAAO,GAAE,kBAAuB,GAC/B,gBAAgB,CA8BlB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,UAAQ,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/runtime/permissions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7F,KAAK,kBAAkB,GAAG;IACxB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC5C,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,EACpC,OAAO,GAAE,kBAAuB,GAC/B,gBAAgB,CA8BlB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,UAAQ,GAAG,MAAM,CAa7G"}
|
|
@@ -39,6 +39,8 @@ export function createCliApprovalProvider(config, emit, assumeYes, options = {})
|
|
|
39
39
|
export function formatApprovalRequest(config, request, useColor = false) {
|
|
40
40
|
if (request.toolName === "write_file")
|
|
41
41
|
return formatWriteFileApproval(config, request, useColor);
|
|
42
|
+
if (request.toolName === "edit_file")
|
|
43
|
+
return formatEditFileApproval(config, request, useColor);
|
|
42
44
|
const inputSummary = stringify(compactApprovalInput(request.toolName, request.input), 800)
|
|
43
45
|
.split("\n")
|
|
44
46
|
.map((line) => `${paint("│", "yellow", useColor)} ${paint(line, "dim", useColor)}`)
|
|
@@ -50,6 +52,21 @@ export function formatApprovalRequest(config, request, useColor = false) {
|
|
|
50
52
|
paint("╰─", "yellow", useColor),
|
|
51
53
|
].join("\n");
|
|
52
54
|
}
|
|
55
|
+
function formatEditFileApproval(config, request, useColor) {
|
|
56
|
+
const targetPath = typeof request.input.path === "string" ? request.input.path : "(missing path)";
|
|
57
|
+
const resolvedPath = path.resolve(config.workspaceRoot, targetPath);
|
|
58
|
+
const relativePath = path.relative(config.workspaceRoot, resolvedPath);
|
|
59
|
+
const displayPath = relativePath && !relativePath.startsWith("..") ? relativePath : resolvedPath;
|
|
60
|
+
const oldString = typeof request.input.old_string === "string" ? request.input.old_string : "";
|
|
61
|
+
const newString = typeof request.input.new_string === "string" ? request.input.new_string : "";
|
|
62
|
+
return [
|
|
63
|
+
"",
|
|
64
|
+
`${paint("╭─", "yellow", useColor)} ${paint("Edit file", "yellowBold", useColor)} ${paint("· approval required", "dim", useColor)}`,
|
|
65
|
+
`${paint("│", "yellow", useColor)} ${paint(displayPath, "blueBold", useColor)}`,
|
|
66
|
+
`${paint("│", "yellow", useColor)} ${paint(`${oldString.split(/\r?\n/).length} → ${newString.split(/\r?\n/).length} lines`, "dim", useColor)}`,
|
|
67
|
+
paint("╰─", "yellow", useColor),
|
|
68
|
+
].join("\n");
|
|
69
|
+
}
|
|
53
70
|
function formatWriteFileApproval(config, request, useColor) {
|
|
54
71
|
const targetPath = typeof request.input.path === "string" ? request.input.path : "(missing path)";
|
|
55
72
|
const content = typeof request.input.content === "string" ? request.input.content : "";
|
|
@@ -72,7 +72,7 @@ ${prompt}`;
|
|
|
72
72
|
- For code or workspace tasks, read relevant files before proposing or making changes.
|
|
73
73
|
- Keep changes scoped to what the user asked. Do not add speculative features, broad refactors, or unnecessary abstractions.
|
|
74
74
|
- Prefer editing existing files over creating new files unless a new file is necessary.
|
|
75
|
-
- When calling tools, include every required input field every time.
|
|
75
|
+
- When calling tools, include every required input field every time. Prefer edit_file with path, old_string, and new_string for localized changes. Use write_file with both path and content only for new files or intentional full-file replacement.
|
|
76
76
|
- Verify meaningful changes when practical by running targeted tests, builds, or checks. If you cannot verify, say so.
|
|
77
77
|
- Report outcomes faithfully. If a check fails, include the relevant failure. Never claim success for checks you did not run.`;
|
|
78
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-policy.d.ts","sourceRoot":"","sources":["../../src/runtime/tool-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEjE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,aAAa,EAAE;IAEnD,kBAAkB,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"tool-policy.d.ts","sourceRoot":"","sources":["../../src/runtime/tool-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEjE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,aAAa,EAAE;IAEnD,kBAAkB,IAAI,MAAM;IAqC5B,mBAAmB,IAAI,MAAM;IAU7B;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM;IA2ClD,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM;CAU1D"}
|
|
@@ -11,9 +11,10 @@ export class ToolPolicy {
|
|
|
11
11
|
"Do not call tools just to make ordinary conversation, and do not claim a tool was used unless it was actually called.",
|
|
12
12
|
"If a tool call is denied, do not repeat the exact same call. Adjust your approach or ask the user for clarification.",
|
|
13
13
|
names.has("read_file") ? "Use read_file to inspect files instead of shell commands like cat, head, tail, or sed." : null,
|
|
14
|
-
names.has("read_file") || names.has("write_file") || names.has("list_files")
|
|
14
|
+
names.has("read_file") || names.has("write_file") || names.has("edit_file") || names.has("list_files")
|
|
15
15
|
? "If the user asks for a file or directory outside the workspace, still use the dedicated file tool. The runtime will request human approval for crossing the workspace boundary; do not refuse solely because the path is outside the workspace."
|
|
16
16
|
: null,
|
|
17
|
+
names.has("edit_file") ? "Prefer edit_file for localized changes to existing files so tool inputs stay small and the user receives a focused diff." : null,
|
|
17
18
|
names.has("write_file") ? "Use write_file for file creation or full-file replacement instead of shell redirection." : null,
|
|
18
19
|
names.has("shell_exec")
|
|
19
20
|
? "Reserve shell_exec for local system commands, tests, package scripts, or operations that do not have a dedicated tool. Do not use shell_exec with curl, wget, python, node, or other scripts to make HTTP requests."
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin.d.ts","sourceRoot":"","sources":["../../src/tools/builtin.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAc,cAAc,EAAwB,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5G,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAY7C,KAAK,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAGjG,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,KACZ,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"builtin.d.ts","sourceRoot":"","sources":["../../src/tools/builtin.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAc,cAAc,EAAwB,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5G,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAY7C,KAAK,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAGjG,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,KACZ,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,CAqkB3E;AAgLD,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,eAA2E,GACnF,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC,CAkB7C"}
|
package/dist/tools/builtin.js
CHANGED
|
@@ -101,6 +101,49 @@ export function createBuiltinRegistry(options) {
|
|
|
101
101
|
};
|
|
102
102
|
},
|
|
103
103
|
});
|
|
104
|
+
registry.register({
|
|
105
|
+
name: "edit_file",
|
|
106
|
+
description: "Replace an exact text block in an existing UTF-8 file. Prefer this for localized code changes because it sends less content and produces a focused diff.",
|
|
107
|
+
risk: "high",
|
|
108
|
+
input_schema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
path: { type: "string" },
|
|
112
|
+
old_string: { type: "string", description: "Exact text currently present in the file." },
|
|
113
|
+
new_string: { type: "string", description: "Replacement text." },
|
|
114
|
+
replace_all: { type: "boolean", description: "Replace every exact occurrence. Defaults to false." },
|
|
115
|
+
},
|
|
116
|
+
required: ["path", "old_string", "new_string"],
|
|
117
|
+
},
|
|
118
|
+
execute: async (input, context) => {
|
|
119
|
+
const targetPath = requiredString(input, "path");
|
|
120
|
+
const oldString = requiredString(input, "old_string");
|
|
121
|
+
const newString = input.new_string;
|
|
122
|
+
if (typeof newString !== "string")
|
|
123
|
+
throw new Error("Missing string input: new_string");
|
|
124
|
+
if (!oldString)
|
|
125
|
+
throw new Error("edit_file old_string must not be empty.");
|
|
126
|
+
const fullPath = await resolvePathWithBoundaryApproval(config, context, targetPath, "edit file");
|
|
127
|
+
const previous = await fs.readFile(fullPath, "utf8");
|
|
128
|
+
const occurrences = countOccurrences(previous, oldString);
|
|
129
|
+
if (occurrences === 0)
|
|
130
|
+
throw new Error(`edit_file could not find old_string in ${targetPath}. Read the file again and copy the exact current text.`);
|
|
131
|
+
const replaceAll = input.replace_all === true;
|
|
132
|
+
if (occurrences > 1 && !replaceAll) {
|
|
133
|
+
throw new Error(`edit_file found ${occurrences} matches in ${targetPath}. Include more surrounding text for a unique match or set replace_all=true.`);
|
|
134
|
+
}
|
|
135
|
+
const content = replaceAll
|
|
136
|
+
? previous.split(oldString).join(newString)
|
|
137
|
+
: previous.replace(oldString, newString);
|
|
138
|
+
await fs.writeFile(fullPath, content, "utf8");
|
|
139
|
+
const displayPath = path.relative(config.workspaceRoot, fullPath);
|
|
140
|
+
const diffable = previous.length <= FILE_DIFF_MAX_BYTES && content.length <= FILE_DIFF_MAX_BYTES;
|
|
141
|
+
return {
|
|
142
|
+
content: `Edited ${displayPath}`,
|
|
143
|
+
fileChange: diffable ? fileChangeDisplay(displayPath, previous, content) : undefined,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|
|
104
147
|
registry.register({
|
|
105
148
|
name: "shell_exec",
|
|
106
149
|
description: "Run a local shell command in the workspace for builds, tests, package scripts, or system inspection. Do not use for HTTP requests; use web_search or web_fetch instead.",
|
|
@@ -569,6 +612,15 @@ function visibleLines(content) {
|
|
|
569
612
|
lines.pop();
|
|
570
613
|
return lines;
|
|
571
614
|
}
|
|
615
|
+
function countOccurrences(content, search) {
|
|
616
|
+
let count = 0;
|
|
617
|
+
let offset = 0;
|
|
618
|
+
while ((offset = content.indexOf(search, offset)) >= 0) {
|
|
619
|
+
count += 1;
|
|
620
|
+
offset += search.length;
|
|
621
|
+
}
|
|
622
|
+
return count;
|
|
623
|
+
}
|
|
572
624
|
async function resolvePathWithBoundaryApproval(config, context, targetPath, operation) {
|
|
573
625
|
let boundaryDetail = "";
|
|
574
626
|
try {
|
package/dist/trace.js
CHANGED
|
@@ -565,7 +565,7 @@ function thoughtDetail(stopReason) {
|
|
|
565
565
|
return stopReason ? `stopped by ${stopReason}` : "model response received";
|
|
566
566
|
}
|
|
567
567
|
function formatInteractiveToolResult(tool, content, isError, input) {
|
|
568
|
-
const name = tool === "write_file" ? "Write" : tool === "shell_exec" ? "Bash" : toolLabel(tool);
|
|
568
|
+
const name = tool === "write_file" ? "Write" : tool === "edit_file" ? "Edit" : tool === "shell_exec" ? "Bash" : toolLabel(tool);
|
|
569
569
|
const key = tool === "list_files" ? "directory" : tool === "web_search" ? "query" : tool === "web_fetch" ? "url" : tool === "shell_exec" ? "command" : "path";
|
|
570
570
|
const rawDetail = input?.[key];
|
|
571
571
|
const detail = typeof rawDetail === "string" && rawDetail.trim()
|
|
@@ -578,6 +578,8 @@ function formatInteractiveToolResult(tool, content, isError, input) {
|
|
|
578
578
|
return `${label} failed · ${compactValue(content, 160)}`;
|
|
579
579
|
if (tool === "write_file" && content.startsWith("Wrote "))
|
|
580
580
|
return content;
|
|
581
|
+
if (tool === "edit_file" && content.startsWith("Edited "))
|
|
582
|
+
return content;
|
|
581
583
|
if (tool === "list_files")
|
|
582
584
|
return `${label} · ${content ? content.split(/\r?\n/).length : 0} entries`;
|
|
583
585
|
if (tool === "web_search") {
|
package/docs/sdk.md
CHANGED
|
@@ -13,7 +13,7 @@ Apollo SDK 用于把 Agent 能力嵌入 Node.js / TypeScript 系统。CLI 只是
|
|
|
13
13
|
当前使用本地 tarball:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install /Users/apollo/Code-YH/web-agent/agent/apolloyh-apollo-agent-0.1.
|
|
16
|
+
npm install /Users/apollo/Code-YH/web-agent/agent/apolloyh-apollo-agent-0.1.3.tgz
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
发布到 npm 后可改为:
|