@apolloyh/apollo-agent 0.1.2 → 0.1.4
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/dist/brand.d.ts +1 -1
- package/dist/brand.js +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/index.js +8 -0
- 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/skills/skills.d.ts.map +1 -1
- package/dist/skills/skills.js +1 -6
- 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 -2
- package/src/skills/tech-research-skill/SKILL.md +0 -200
- package/src/skills/tech-research-skill/agents/openai.yaml +0 -4
- package/src/skills/tech-research-skill/repo-analyze +0 -435
- package/src/skills/tech-research-skill/repo-fetch +0 -310
- package/src/skills/tech-research-skill/report-generate +0 -254
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/dist/brand.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
readonly packageName: "@apolloyh/apollo-agent";
|
|
7
7
|
readonly userAgent: "ApolloAgent/0.1";
|
package/dist/brand.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
packageName: "@apolloyh/apollo-agent",
|
|
7
7
|
userAgent: "ApolloAgent/0.1",
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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
|
@@ -161,7 +161,8 @@ export function loadLlmConfig(options = {}) {
|
|
|
161
161
|
if (cacheTtl !== "5m" && cacheTtl !== "1h") {
|
|
162
162
|
throw new Error("ANTHROPIC_CACHE_TTL must be 5m or 1h.");
|
|
163
163
|
}
|
|
164
|
-
const
|
|
164
|
+
const providerDefaultMaxTokens = /(?:^glm-|bigmodel\.cn)/i.test(`${model} ${baseUrl}`) ? 128 * 1024 : 4096;
|
|
165
|
+
const maxTokens = positiveNumberEnv("ANTHROPIC_MAX_TOKENS", providerDefaultMaxTokens, true);
|
|
165
166
|
const timeoutMs = positiveNumberEnv("ANTHROPIC_TIMEOUT_MS", 300000, true);
|
|
166
167
|
let parsedBaseUrl;
|
|
167
168
|
try {
|
package/dist/index.js
CHANGED
|
@@ -314,6 +314,8 @@ async function main() {
|
|
|
314
314
|
` ${PRODUCT.name} v${PRODUCT.version}`,
|
|
315
315
|
` model ${llmConfig.model}`,
|
|
316
316
|
` base ${llmConfig.baseUrl}`,
|
|
317
|
+
` output max ${llmConfig.maxTokens.toLocaleString()} tokens`,
|
|
318
|
+
` context ${agentConfig.context.maxChars.toLocaleString()} chars`,
|
|
317
319
|
` cwd ${shortenHome(agentConfig.workspaceRoot)}`,
|
|
318
320
|
` messages ${runtime.conversationSize()}`,
|
|
319
321
|
` tokens ${usage.input_tokens.toLocaleString()} input / ${usage.output_tokens.toLocaleString()} output`,
|
|
@@ -834,11 +836,17 @@ function formatToolCallStatus(tool, input) {
|
|
|
834
836
|
const lines = input.content ? input.content.split(/\r?\n/).length : 0;
|
|
835
837
|
return `${label}${detail} · ${lines} ${lines === 1 ? "line" : "lines"}`;
|
|
836
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
|
+
}
|
|
837
843
|
return `${label}${detail}`;
|
|
838
844
|
}
|
|
839
845
|
function formatToolStatusName(tool) {
|
|
840
846
|
if (tool === "write_file")
|
|
841
847
|
return "Write";
|
|
848
|
+
if (tool === "edit_file")
|
|
849
|
+
return "Edit";
|
|
842
850
|
if (tool === "shell_exec")
|
|
843
851
|
return "Bash";
|
|
844
852
|
if (tool === "Task")
|
|
@@ -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":"skills.d.ts","sourceRoot":"","sources":["../../src/skills/skills.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/skills/skills.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;gBAE7B,MAAM,EAAE,WAAW;IAEhD,OAAO,CAAC,SAAS;IAMX,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAoBtF,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY/E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAuCvG,OAAO,CAAC,OAAO;YAKD,WAAW;CAuB1B"}
|
package/dist/skills/skills.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { safeRealResolve, truncate } from "../utils.js";
|
|
5
4
|
export class SkillManager {
|
|
6
5
|
config;
|
|
@@ -10,11 +9,7 @@ export class SkillManager {
|
|
|
10
9
|
this.config = config;
|
|
11
10
|
}
|
|
12
11
|
skillDirs() {
|
|
13
|
-
|
|
14
|
-
return Array.from(new Set([
|
|
15
|
-
...this.config.skills.directories.map((dir) => path.resolve(this.config.workspaceRoot, dir)),
|
|
16
|
-
bundled,
|
|
17
|
-
]));
|
|
12
|
+
return Array.from(new Set(this.config.skills.directories.map((dir) => path.resolve(this.config.workspaceRoot, dir))));
|
|
18
13
|
}
|
|
19
14
|
async search(query) {
|
|
20
15
|
const results = [];
|
|
@@ -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 后可改为:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apolloyh/apollo-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Apollo — your personal agent. Answer first, act when needed. Claude Code–style Task subagents.",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
|
-
"src/skills/tech-research-skill",
|
|
24
23
|
"README.md",
|
|
25
24
|
"docs/sdk.md",
|
|
26
25
|
".apollo/config.example.json",
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: tech-research-skill
|
|
3
|
-
description: "技术方向、行业领域、产品赛道、GitHub 仓库和开源项目的调查、研究、竞品分析、趋势分析和报告生成。Use when the user asks to investigate, research, evaluate, compare, analyze, score, summarize, or report on a technology direction, market/domain niche, AI infra, agent, RAG, LLM, devtool, data, security, web3, GitHub repository, GitHub URL, or open-source project. Handles GitHub repo search, repo health, issues/commits, implementation patterns, competitor landscape, market trend research, evidence sources, Markdown report output, and downloadable report links."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Tech Research Skill
|
|
7
|
-
|
|
8
|
-
Use this skill for both:
|
|
9
|
-
|
|
10
|
-
- **Direction research**: investigate a technology direction, industry/domain, product category, or open-source theme.
|
|
11
|
-
- **Repository research**: investigate one or more GitHub repositories or open-source projects.
|
|
12
|
-
|
|
13
|
-
Default to this skill for prompts like "调查数字人方向", "分析 RAG agent 赛道", "研究某 GitHub 项目", "竞品分析", "趋势分析", "开源项目尽调", or "生成研究报告".
|
|
14
|
-
|
|
15
|
-
## Tool Location
|
|
16
|
-
|
|
17
|
-
The shell tools live in the same directory as this `SKILL.md`.
|
|
18
|
-
|
|
19
|
-
Set the tool directory from the loaded skill path:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
TECH_RESEARCH_SKILL_DIR="$(dirname "$SKILL_PATH")"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
If the current shell is already running from this skill directory, use:
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
TECH_RESEARCH_SKILL_DIR="."
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Do not use absolute paths. Do not run exploratory `find`, `ls`, or `echo $CLAUDE_SKILL_DIR` just to locate these tools. If path context is uncertain, call `skill_read` for `tech-research-skill` and derive the directory from the returned path.
|
|
32
|
-
|
|
33
|
-
Tool role labels:
|
|
34
|
-
|
|
35
|
-
- `repo_search`: `"$TECH_RESEARCH_SKILL_DIR/repo-fetch" --search "<query>" --per-page 10`
|
|
36
|
-
- `repo_fetch`: `"$TECH_RESEARCH_SKILL_DIR/repo-fetch" owner/repo --include issues,commits`
|
|
37
|
-
- `repo_analyze`: `"$TECH_RESEARCH_SKILL_DIR/repo-analyze"` reads `repo-fetch` JSON from stdin and emits deterministic local health signals. It does not call an LLM, does not use network, and does not require model credentials.
|
|
38
|
-
- `repo_report_generate`: `"$TECH_RESEARCH_SKILL_DIR/report-generate" --template investment --format markdown --output <report>.md` renders `repo-analyze` JSON into Markdown or HTML. It does not call an LLM.
|
|
39
|
-
|
|
40
|
-
These role labels are not separate executable filenames. The executable scripts are `repo-fetch`, `repo-analyze`, and `report-generate`.
|
|
41
|
-
|
|
42
|
-
The outer agent is responsible for the actual judgment: repository health interpretation, implementation-pattern analysis, competitor analysis, market/trend synthesis, opportunity mapping, and final recommendation. Treat `repo-analyze` output as structured evidence, not as the final answer.
|
|
43
|
-
|
|
44
|
-
## GitHub API Token
|
|
45
|
-
|
|
46
|
-
`repo-fetch` uses the GitHub REST API. Unauthenticated requests are rate-limited quickly, so prefer a local token.
|
|
47
|
-
|
|
48
|
-
Supported environment variables:
|
|
49
|
-
|
|
50
|
-
```sh
|
|
51
|
-
GITHUB_TOKEN=<github personal access token>
|
|
52
|
-
GH_TOKEN=<github personal access token>
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`repo-fetch` also loads a local `.env` file from this skill directory. That `.env` file is ignored by git and may contain:
|
|
56
|
-
|
|
57
|
-
```sh
|
|
58
|
-
GITHUB_TOKEN=<github personal access token>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Never hardcode a GitHub token into `repo-fetch`, `SKILL.md`, reports, examples, command output, or committed files. If a token appears in chat/logs/source, tell the user to revoke and rotate it.
|
|
62
|
-
|
|
63
|
-
## Workflow A: Technology Direction Research
|
|
64
|
-
|
|
65
|
-
Use this workflow when the user provides a field, technology direction, product category, or market/domain.
|
|
66
|
-
|
|
67
|
-
### Turn Budget Rules
|
|
68
|
-
|
|
69
|
-
Finish with a report instead of continuing to search. For a normal direction investigation:
|
|
70
|
-
|
|
71
|
-
- Run at most 4 GitHub search queries.
|
|
72
|
-
- Fetch details for at most 4 representative repositories.
|
|
73
|
-
- Fetch/search at most 5 web sources.
|
|
74
|
-
- If GitHub or web search quality is poor twice, stop searching and write the report with limitations.
|
|
75
|
-
- Do not run `date`, `ls docs`, or other housekeeping commands before writing the report.
|
|
76
|
-
- Prefer a concise evidence-backed report over exhaustive searching.
|
|
77
|
-
- Once there is enough evidence for a directional judgment, immediately write the Markdown report to the repository-root `docs/` directory. If the current working directory is `agent`, use `../docs/<filename>.md`; if the current working directory is this skill directory, use `../../../../docs/<filename>.md`. Do not write `docs/<filename>.md` from the `agent` directory, because that creates `agent/docs` and the frontend will not treat it as the canonical report location.
|
|
78
|
-
|
|
79
|
-
1. Frame the topic:
|
|
80
|
-
- Normalize the topic name.
|
|
81
|
-
- Identify adjacent keywords and English/Chinese search terms.
|
|
82
|
-
- Decide whether the user needs build advice, investment analysis, competitor analysis, learning research, or a general trend report.
|
|
83
|
-
|
|
84
|
-
2. Search GitHub first:
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
"$TECH_RESEARCH_SKILL_DIR/repo-fetch" --search "<topic keywords> stars:>100" --per-page 10
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Select 5-10 representative repositories:
|
|
91
|
-
|
|
92
|
-
- Most-starred or widely used projects.
|
|
93
|
-
- Recently active challengers.
|
|
94
|
-
- Different implementation approaches.
|
|
95
|
-
- Commercially relevant SDKs, frameworks, or infrastructure.
|
|
96
|
-
- Niche projects that reveal emerging use cases.
|
|
97
|
-
|
|
98
|
-
Do not rely only on stars. Prefer repos with recent commits, active issues/PRs, releases, real users, and clear positioning.
|
|
99
|
-
|
|
100
|
-
3. Analyze representative repositories:
|
|
101
|
-
|
|
102
|
-
```sh
|
|
103
|
-
"$TECH_RESEARCH_SKILL_DIR/repo-fetch" owner/repo --include issues,commits \
|
|
104
|
-
| "$TECH_RESEARCH_SKILL_DIR/repo-analyze"
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
`repo-analyze` only computes local deterministic signals from the fetched GitHub data. The agent must read the JSON and perform the higher-level health analysis itself.
|
|
108
|
-
|
|
109
|
-
4. Search the web for trend evidence:
|
|
110
|
-
- Use current authoritative sources: official docs, standards bodies, foundation pages, vendor announcements, market reports, funding/news coverage, developer surveys, ecosystem reports, benchmarks, regulation/compliance updates.
|
|
111
|
-
- Collect source URLs and dates.
|
|
112
|
-
- Distinguish observed evidence from inference.
|
|
113
|
-
|
|
114
|
-
5. Produce a report with this structure:
|
|
115
|
-
- **Executive Judgment**: high/medium/low potential and why.
|
|
116
|
-
- **GitHub Landscape**: repo table, implementation approaches, health, risks.
|
|
117
|
-
- **Competitor Analysis**: direct open-source, commercial products, infrastructure platforms, substitutes.
|
|
118
|
-
- **Trend and Market Signals**: drivers, tailwinds, headwinds, 6-18 month outlook.
|
|
119
|
-
- **Opportunity Map**: underserved users, product wedges, technical differentiation, distribution/community angles.
|
|
120
|
-
- **Recommendation**: build/watch/avoid, MVP scope, validation experiments, what to monitor.
|
|
121
|
-
- **Evidence Sources**: final section with every repo, GitHub query, issue/commit dataset, and web page used.
|
|
122
|
-
|
|
123
|
-
## Workflow B: GitHub Repository Research
|
|
124
|
-
|
|
125
|
-
Use this workflow when the user gives a GitHub URL, `owner/repo`, or asks for open-source project health.
|
|
126
|
-
|
|
127
|
-
Convert GitHub URLs to `owner/repo`.
|
|
128
|
-
|
|
129
|
-
Fetch and analyze:
|
|
130
|
-
|
|
131
|
-
```sh
|
|
132
|
-
"$TECH_RESEARCH_SKILL_DIR/repo-fetch" owner/repo --include issues,commits \
|
|
133
|
-
| "$TECH_RESEARCH_SKILL_DIR/repo-analyze" \
|
|
134
|
-
| "$TECH_RESEARCH_SKILL_DIR/report-generate" --template weekly --format markdown
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
For investment or opportunity analysis:
|
|
138
|
-
|
|
139
|
-
```sh
|
|
140
|
-
"$TECH_RESEARCH_SKILL_DIR/repo-fetch" owner/repo --include issues,commits \
|
|
141
|
-
| "$TECH_RESEARCH_SKILL_DIR/repo-analyze" \
|
|
142
|
-
| "$TECH_RESEARCH_SKILL_DIR/report-generate" --template investment --format markdown
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The generated repository report is a starting template. For user-facing research, the agent should expand it with its own analysis, web evidence, competitor context, and a final evidence-source section.
|
|
146
|
-
|
|
147
|
-
## Report Output
|
|
148
|
-
|
|
149
|
-
Always write final user-facing reports as Markdown under the repository root `docs/` directory, not under `agent/docs`.
|
|
150
|
-
|
|
151
|
-
Canonical report locations by current working directory:
|
|
152
|
-
|
|
153
|
-
```sh
|
|
154
|
-
# If cwd is the agent root:
|
|
155
|
-
DOCS_DIR="../docs"
|
|
156
|
-
|
|
157
|
-
# If cwd is this skill directory:
|
|
158
|
-
DOCS_DIR="../../../../docs"
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Before writing, choose the path from the actual cwd. Never use `docs/<filename>.md` when cwd is `agent`.
|
|
162
|
-
|
|
163
|
-
Use a lowercase slug plus timestamp when useful:
|
|
164
|
-
|
|
165
|
-
```text
|
|
166
|
-
../docs/digital-human-research-20260611-153000.md
|
|
167
|
-
../docs/repo-health-owner-repo-20260611-153000.md
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Do not put an absolute path in the report instructions.
|
|
171
|
-
|
|
172
|
-
Use the dedicated file-writing tool when available. Do not use shell redirection or here-docs for writing the report if a `write_file` tool exists.
|
|
173
|
-
|
|
174
|
-
Use a simple stable filename when no timestamp is available, such as `../docs/digital-human-research.md` from the agent root. It is better to overwrite a topic report than to spend a turn checking the clock or listing directories.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
Every final report must end with:
|
|
178
|
-
|
|
179
|
-
```md
|
|
180
|
-
## Evidence Sources
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Include:
|
|
184
|
-
|
|
185
|
-
- GitHub repositories used as evidence.
|
|
186
|
-
- GitHub search queries that shaped repo selection.
|
|
187
|
-
- Issue/commit datasets used.
|
|
188
|
-
- Web pages, articles, docs, market reports, announcements, and search results used for trend or competitor claims.
|
|
189
|
-
- Source names, URLs, and publication/access dates when available.
|
|
190
|
-
|
|
191
|
-
Do not put download links inside the Markdown report file, and do not manually add download links in the web frontend final answer. The frontend chat route appends report links after the assistant message once it detects the generated file. If a manual link is ever needed outside the frontend, the `file` query parameter must be only the basename in `docs`, not a path.
|
|
192
|
-
|
|
193
|
-
## Quality Bar
|
|
194
|
-
|
|
195
|
-
- Use real repositories and real external sources; do not invent competitors or metrics.
|
|
196
|
-
- Cite GitHub repos and web sources with links.
|
|
197
|
-
- Prefer evidence-backed judgment over generic commentary.
|
|
198
|
-
- Separate facts from inference.
|
|
199
|
-
- Call out uncertainty, missing credentials, GitHub API limits, failed fetches, and weak evidence.
|
|
200
|
-
- If the user asks in Chinese, answer in Chinese unless they request otherwise.
|