@apolloyh/apollo-agent 0.1.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.
Files changed (103) hide show
  1. package/.apollo/config.example.json +37 -0
  2. package/.env.example +17 -0
  3. package/README.md +62 -0
  4. package/dist/brand.d.ts +17 -0
  5. package/dist/brand.d.ts.map +1 -0
  6. package/dist/brand.js +23 -0
  7. package/dist/cli-args.d.ts +13 -0
  8. package/dist/cli-args.d.ts.map +1 -0
  9. package/dist/cli-args.js +50 -0
  10. package/dist/config.d.ts +13 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +190 -0
  13. package/dist/constants/spinner-verbs.d.ts +7 -0
  14. package/dist/constants/spinner-verbs.d.ts.map +1 -0
  15. package/dist/constants/spinner-verbs.js +21 -0
  16. package/dist/coordinator/workflow.d.ts +20 -0
  17. package/dist/coordinator/workflow.d.ts.map +1 -0
  18. package/dist/coordinator/workflow.js +120 -0
  19. package/dist/goal/goal-mode.d.ts +26 -0
  20. package/dist/goal/goal-mode.d.ts.map +1 -0
  21. package/dist/goal/goal-mode.js +141 -0
  22. package/dist/hooks/runner.d.ts +43 -0
  23. package/dist/hooks/runner.d.ts.map +1 -0
  24. package/dist/hooks/runner.js +94 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +1716 -0
  28. package/dist/llm/anthropic.d.ts +46 -0
  29. package/dist/llm/anthropic.d.ts.map +1 -0
  30. package/dist/llm/anthropic.js +278 -0
  31. package/dist/mcp/manager.d.ts +14 -0
  32. package/dist/mcp/manager.d.ts.map +1 -0
  33. package/dist/mcp/manager.js +93 -0
  34. package/dist/memory/memdir.d.ts +19 -0
  35. package/dist/memory/memdir.d.ts.map +1 -0
  36. package/dist/memory/memdir.js +96 -0
  37. package/dist/plan/plan-mode.d.ts +14 -0
  38. package/dist/plan/plan-mode.d.ts.map +1 -0
  39. package/dist/plan/plan-mode.js +90 -0
  40. package/dist/project-instructions.d.ts +8 -0
  41. package/dist/project-instructions.d.ts.map +1 -0
  42. package/dist/project-instructions.js +33 -0
  43. package/dist/runtime/compaction.d.ts +5 -0
  44. package/dist/runtime/compaction.d.ts.map +1 -0
  45. package/dist/runtime/compaction.js +37 -0
  46. package/dist/runtime/context.d.ts +25 -0
  47. package/dist/runtime/context.d.ts.map +1 -0
  48. package/dist/runtime/context.js +75 -0
  49. package/dist/runtime/permissions.d.ts +11 -0
  50. package/dist/runtime/permissions.d.ts.map +1 -0
  51. package/dist/runtime/permissions.js +119 -0
  52. package/dist/runtime/prompt-builder.d.ts +27 -0
  53. package/dist/runtime/prompt-builder.d.ts.map +1 -0
  54. package/dist/runtime/prompt-builder.js +112 -0
  55. package/dist/runtime/query-engine.d.ts +136 -0
  56. package/dist/runtime/query-engine.d.ts.map +1 -0
  57. package/dist/runtime/query-engine.js +1182 -0
  58. package/dist/runtime/tool-policy.d.ts +13 -0
  59. package/dist/runtime/tool-policy.d.ts.map +1 -0
  60. package/dist/runtime/tool-policy.js +106 -0
  61. package/dist/sdk.d.ts +33 -0
  62. package/dist/sdk.d.ts.map +1 -0
  63. package/dist/sdk.js +41 -0
  64. package/dist/session/resume-ui.d.ts +17 -0
  65. package/dist/session/resume-ui.d.ts.map +1 -0
  66. package/dist/session/resume-ui.js +64 -0
  67. package/dist/session/store.d.ts +66 -0
  68. package/dist/session/store.d.ts.map +1 -0
  69. package/dist/session/store.js +120 -0
  70. package/dist/skillify/skillify.d.ts +12 -0
  71. package/dist/skillify/skillify.d.ts.map +1 -0
  72. package/dist/skillify/skillify.js +52 -0
  73. package/dist/skills/skills.d.ts +26 -0
  74. package/dist/skills/skills.d.ts.map +1 -0
  75. package/dist/skills/skills.js +195 -0
  76. package/dist/status-ui.d.ts +37 -0
  77. package/dist/status-ui.d.ts.map +1 -0
  78. package/dist/status-ui.js +130 -0
  79. package/dist/thought-fold.d.ts +48 -0
  80. package/dist/thought-fold.d.ts.map +1 -0
  81. package/dist/thought-fold.js +156 -0
  82. package/dist/tools/builtin.d.ts +27 -0
  83. package/dist/tools/builtin.d.ts.map +1 -0
  84. package/dist/tools/builtin.js +898 -0
  85. package/dist/tools/registry.d.ts +17 -0
  86. package/dist/tools/registry.d.ts.map +1 -0
  87. package/dist/tools/registry.js +86 -0
  88. package/dist/trace.d.ts +68 -0
  89. package/dist/trace.d.ts.map +1 -0
  90. package/dist/trace.js +600 -0
  91. package/dist/types.d.ts +202 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +1 -0
  94. package/dist/utils.d.ts +8 -0
  95. package/dist/utils.d.ts.map +1 -0
  96. package/dist/utils.js +78 -0
  97. package/docs/sdk.md +259 -0
  98. package/package.json +53 -0
  99. package/src/skills/tech-research-skill/SKILL.md +200 -0
  100. package/src/skills/tech-research-skill/agents/openai.yaml +4 -0
  101. package/src/skills/tech-research-skill/repo-analyze +435 -0
  102. package/src/skills/tech-research-skill/repo-fetch +310 -0
  103. package/src/skills/tech-research-skill/report-generate +254 -0
@@ -0,0 +1,37 @@
1
+ {
2
+ "workspaceRoot": ".",
3
+ "systemPrompt": "",
4
+ "maxTurns": 40,
5
+ "context": {
6
+ "maxChars": 262144,
7
+ "summaryChars": 12000
8
+ },
9
+ "permissions": {
10
+ "mode": "ask",
11
+ "autoApproveReadOnly": true
12
+ },
13
+ "skills": {
14
+ "directories": ["./skills"]
15
+ },
16
+ "mcpServers": {},
17
+ "agents": [
18
+ {
19
+ "name": "general-purpose",
20
+ "description": "Complex multi-step work when the path is unclear: research, investigation, or mixed read/write tasks that need isolation from the main conversation.",
21
+ "tools": ["*"],
22
+ "prompt": "You are a general-purpose subagent. Search broadly, investigate thoroughly, and return a concise report with key findings and evidence. Do not address the end user — report back to the main agent."
23
+ },
24
+ {
25
+ "name": "Explore",
26
+ "description": "Fast read-only exploration: find files, search code, answer where/how questions without making edits.",
27
+ "tools": ["list_files", "read_file", "git_status", "git_diff", "web_search", "web_fetch", "skill_search", "skill_read"],
28
+ "prompt": "You are an Explore subagent (read-only). Map the codebase quickly. Prefer list_files / read_file. Do not modify files. Return key paths and findings."
29
+ },
30
+ {
31
+ "name": "Plan",
32
+ "description": "Design an implementation plan without writing code: steps, critical files, risks, and trade-offs.",
33
+ "tools": ["list_files", "read_file", "git_status", "git_diff", "web_search", "web_fetch", "skill_search", "skill_read"],
34
+ "prompt": "You are a Plan subagent. Produce a clear implementation plan, not code changes. Return goal, steps, critical files, risks, and verification."
35
+ }
36
+ ]
37
+ }
package/.env.example ADDED
@@ -0,0 +1,17 @@
1
+ # Copy to .apollo/.env (preferred) or .env and fill in. Both are gitignored.
2
+
3
+ ANTHROPIC_AUTH_TOKEN=replace_with_your_token
4
+ ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic
5
+ EXA_API_KEY=replace_with_your_exa_key
6
+
7
+ # Primary model (Apollo reads ANTHROPIC_MODEL first, then HAIKU fallbacks)
8
+ ANTHROPIC_MODEL=glm-5.2
9
+ ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.2
10
+ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME=glm-5.2
11
+ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME=glm-5.2
12
+
13
+ # Optional
14
+ # ANTHROPIC_MAX_TOKENS=4096
15
+ # ANTHROPIC_TIMEOUT_MS=300000
16
+ # ANTHROPIC_CACHE_TTL=5m
17
+ # AGENT_SENDMAIL_PATH=/usr/sbin/sendmail
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Apollo
2
+
3
+ Your **personal** agent runtime: answer first, act when needed.
4
+
5
+ Terminal text wordmark (no icon / no triangle):
6
+
7
+ ```
8
+ █████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗
9
+ ██╔══██╗██╔══██╗██╔═══██╗██║ ██║ ██╔═══██╗
10
+ ███████║██████╔╝██║ ██║██║ ██║ ██║ ██║
11
+ ██╔══██║██╔═══╝ ██║ ██║██║ ██║ ██║ ██║
12
+ ██║ ██║██║ ╚██████╔╝███████╗███████╗╚██████╔╝
13
+ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝
14
+ ```
15
+
16
+ → reads as **A P O L L O**. Defined in `src/brand.ts`.
17
+
18
+ ## Features
19
+
20
+ - Ordinary questions → answer directly, no tools
21
+ - Complex work → **Task** subagents (`Explore` / `Plan` / `general-purpose`)
22
+ - Skills / MCP / files / shell / web on demand
23
+ - Waiting UI: blinking `◆`
24
+
25
+ ## Run
26
+
27
+ ```bash
28
+ npm install -g @apolloyh/apollo-agent
29
+ apollo
30
+ apollo --yolo # auto-approve tools
31
+ apollo -c # continue the latest project session
32
+ apollo -r # pick a saved project session
33
+ apollo -r <session-id>
34
+ ```
35
+
36
+ Apollo automatically loads `.apollo/.env`, then falls back to `.env`; use `--env-file <path>` to override discovery.
37
+
38
+ Optional project configuration lives at `.apollo/config.json`; Apollo also reads the legacy
39
+ `agent.config.json` during migration. Copy `.apollo/config.example.json` to get started.
40
+ Set `systemPrompt` there to append stable custom system instructions; restart Apollo to reload them.
41
+
42
+ Commands: `/help` · `/skills` · `/agents` · `/sessions` · `/resume` · `/verbose` · `/stream` · `/clear` · `/exit`
43
+
44
+ ## SDK
45
+
46
+ ```ts
47
+ import { createQueryEngine } from "@apolloyh/apollo-agent";
48
+
49
+ const engine = await createQueryEngine({
50
+ configPath: ".apollo/config.json",
51
+ assumeYes: true,
52
+ onEvent(event) { /* stream / task events */ },
53
+ });
54
+
55
+ try {
56
+ await engine.submitMessage("your task");
57
+ } finally {
58
+ await engine.close();
59
+ }
60
+ ```
61
+
62
+ See the [SDK integration guide](docs/sdk.md) and [requirements](docs/requirements.md).
@@ -0,0 +1,17 @@
1
+ /** Product identity — single source of truth for display strings. */
2
+ export declare const PRODUCT: {
3
+ readonly name: "Apollo";
4
+ readonly version: "0.1.0";
5
+ readonly tagline: "Your personal agent — answer first, act when needed.";
6
+ readonly packageName: "apollo-agent";
7
+ readonly userAgent: "ApolloAgent/0.1";
8
+ readonly prompt: "apollo";
9
+ };
10
+ /**
11
+ * Terminal text wordmark only.
12
+ * No triangle / icon mark — just clear block letters: A P O L L O
13
+ * (ANSI Shadow style, easy to read at a glance)
14
+ */
15
+ export declare const ASCII_LOGO: readonly string[];
16
+ export declare const ASCII_WORDMARK: readonly string[];
17
+ //# sourceMappingURL=brand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,eAAO,MAAM,OAAO;;;;;;;CAOV,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EAOvC,CAAC;AAEF,eAAO,MAAM,cAAc,mBAAa,CAAC"}
package/dist/brand.js ADDED
@@ -0,0 +1,23 @@
1
+ /** Product identity — single source of truth for display strings. */
2
+ export const PRODUCT = {
3
+ name: "Apollo",
4
+ version: "0.1.0",
5
+ tagline: "Your personal agent — answer first, act when needed.",
6
+ packageName: "apollo-agent",
7
+ userAgent: "ApolloAgent/0.1",
8
+ prompt: "apollo",
9
+ };
10
+ /**
11
+ * Terminal text wordmark only.
12
+ * No triangle / icon mark — just clear block letters: A P O L L O
13
+ * (ANSI Shadow style, easy to read at a glance)
14
+ */
15
+ export const ASCII_LOGO = [
16
+ " █████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ",
17
+ " ██╔══██╗██╔══██╗██╔═══██╗██║ ██║ ██╔═══██╗",
18
+ " ███████║██████╔╝██║ ██║██║ ██║ ██║ ██║",
19
+ " ██╔══██║██╔═══╝ ██║ ██║██║ ██║ ██║ ██║",
20
+ " ██║ ██║██║ ╚██████╔╝███████╗███████╗╚██████╔╝",
21
+ " ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ",
22
+ ];
23
+ export const ASCII_WORDMARK = ASCII_LOGO;
@@ -0,0 +1,13 @@
1
+ export type CliArgs = {
2
+ configPath?: string;
3
+ envPath?: string;
4
+ task?: string;
5
+ continueSession: boolean;
6
+ resumeSession?: true | string;
7
+ verbose: boolean;
8
+ yolo: boolean;
9
+ stream: boolean;
10
+ color: boolean;
11
+ };
12
+ export declare function parseArgs(argv: string[], isColorTerminal?: boolean): CliArgs;
13
+ //# sourceMappingURL=cli-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,eAAe,UAAwD,GAAG,OAAO,CAgC1H"}
@@ -0,0 +1,50 @@
1
+ export function parseArgs(argv, isColorTerminal = Boolean(process.stdout.isTTY && process.stderr.isTTY)) {
2
+ const result = {
3
+ continueSession: false,
4
+ verbose: false,
5
+ yolo: false,
6
+ stream: true,
7
+ color: isColorTerminal,
8
+ };
9
+ const taskParts = [];
10
+ for (let index = 0; index < argv.length; index += 1) {
11
+ const arg = argv[index];
12
+ if (arg === "--config")
13
+ result.configPath = requiredValue(argv, ++index, "--config");
14
+ else if (arg === "--env-file")
15
+ result.envPath = requiredValue(argv, ++index, "--env-file");
16
+ else if (arg === "--continue" || arg === "-c")
17
+ result.continueSession = true;
18
+ else if (arg === "--resume" || arg === "-r") {
19
+ const candidate = argv[index + 1];
20
+ // Session IDs are `s-<base36>-<base36>`; anything else is the task, not an ID.
21
+ result.resumeSession = /^s-[a-z0-9]+-[a-z0-9]+$/.test(candidate ?? "") ? argv[++index] : true;
22
+ }
23
+ else if (arg === "--verbose")
24
+ result.verbose = true;
25
+ else if (arg === "--quiet")
26
+ result.verbose = false;
27
+ else if (arg === "--yolo")
28
+ result.yolo = true;
29
+ else if (arg === "--yes")
30
+ throw new Error("--yes has been replaced by --yolo");
31
+ else if (arg === "--no-stream")
32
+ result.stream = false;
33
+ else if (arg === "--no-color")
34
+ result.color = false;
35
+ else
36
+ taskParts.push(arg);
37
+ }
38
+ if (result.continueSession && result.resumeSession) {
39
+ throw new Error("--continue and --resume cannot be used together");
40
+ }
41
+ if (taskParts.length > 0)
42
+ result.task = taskParts.join(" ");
43
+ return result;
44
+ }
45
+ function requiredValue(argv, index, flag) {
46
+ const value = argv[index];
47
+ if (!value || value.startsWith("--"))
48
+ throw new Error(`${flag} requires a value`);
49
+ return value;
50
+ }
@@ -0,0 +1,13 @@
1
+ import type { AgentConfig, LlmConfig, SubagentConfig } from "./types.js";
2
+ /** Default subagents — Claude Code–style Task roles. */
3
+ export declare const DEFAULT_AGENTS: SubagentConfig[];
4
+ export declare function loadAgentConfig(configPath?: string): Promise<AgentConfig>;
5
+ /**
6
+ * Load an explicit env file, or discover .apollo/.env then .env.
7
+ * By default **overrides** existing shell vars so local agent config wins.
8
+ */
9
+ export declare function loadEnvFile(envPath?: string, options?: {
10
+ override?: boolean;
11
+ }): boolean;
12
+ export declare function loadLlmConfig(): LlmConfig;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE,wDAAwD;AACxD,eAAO,MAAM,cAAc,EAAE,cAAc,EA4B1C,CAAC;AAuBF,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,CAuB3F;AAYD,wBAAgB,aAAa,IAAI,SAAS,CAmCzC"}
package/dist/config.js ADDED
@@ -0,0 +1,190 @@
1
+ import fsSync from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ /** Default subagents — Claude Code–style Task roles. */
5
+ export const DEFAULT_AGENTS = [
6
+ {
7
+ name: "general-purpose",
8
+ description: "Complex multi-step work when the path is unclear: research, investigation, or mixed read/write tasks that need isolation from the main conversation.",
9
+ tools: ["*"],
10
+ prompt: `You are a general-purpose subagent. You receive a self-contained task brief and no prior chat history.
11
+
12
+ Work thoroughly but stay scoped. Use tools as needed. Return one concise final report with findings, evidence (paths, quotes), and any changes you made. Do not address the end user directly — your output goes back to the main agent.`,
13
+ },
14
+ {
15
+ name: "Explore",
16
+ description: "Fast read-only exploration: find files, search code, answer “where/how does X work?” without making edits.",
17
+ tools: ["list_files", "read_file", "git_status", "git_diff", "web_search", "web_fetch", "skill_search", "skill_read"],
18
+ prompt: `You are an Explore subagent (read-only). Map the codebase or research a question quickly.
19
+
20
+ Prefer list_files / read_file over shell. Do not modify files, run destructive commands, or commit. Return a short structured report: key paths, what you found, and open questions.`,
21
+ },
22
+ {
23
+ name: "Plan",
24
+ description: "Design an implementation plan without writing code: steps, critical files, risks, and trade-offs.",
25
+ tools: ["list_files", "read_file", "git_status", "git_diff", "web_search", "web_fetch", "skill_search", "skill_read"],
26
+ prompt: `You are a Plan subagent. Produce a clear implementation plan, not code changes.
27
+
28
+ Read only what you need. Return: goal, approach, ordered steps, critical files, risks, and what to verify. Do not edit files or run write/shell tools.`,
29
+ },
30
+ ];
31
+ const defaultConfig = {
32
+ workspaceRoot: ".",
33
+ maxTurns: 20,
34
+ context: {
35
+ maxChars: 256 * 1024,
36
+ summaryChars: 12000,
37
+ },
38
+ permissions: {
39
+ mode: "ask",
40
+ autoApproveReadOnly: true,
41
+ },
42
+ skills: {
43
+ directories: ["./skills"],
44
+ },
45
+ mcpServers: {},
46
+ agents: DEFAULT_AGENTS,
47
+ };
48
+ const DEFAULT_CONFIG_PATH = ".apollo/config.json";
49
+ const LEGACY_CONFIG_PATH = "agent.config.json";
50
+ export async function loadAgentConfig(configPath) {
51
+ const discoveredPath = configPath ?? [DEFAULT_CONFIG_PATH, LEGACY_CONFIG_PATH].find(fsSync.existsSync);
52
+ if (!discoveredPath) {
53
+ return { ...defaultConfig, workspaceRoot: path.resolve(defaultConfig.workspaceRoot) };
54
+ }
55
+ const raw = await fs.readFile(discoveredPath, "utf8");
56
+ const parsed = JSON.parse(raw);
57
+ const resolvedConfigPath = path.resolve(discoveredPath);
58
+ const configDir = path.dirname(resolvedConfigPath);
59
+ const baseDir = path.basename(configDir) === ".apollo" ? path.dirname(configDir) : configDir;
60
+ const agents = normalizeAgents(parsed.agents ?? parsed.workers) ?? defaultConfig.agents;
61
+ const config = {
62
+ ...defaultConfig,
63
+ ...parsed,
64
+ workspaceRoot: path.resolve(baseDir, parsed.workspaceRoot ?? defaultConfig.workspaceRoot),
65
+ context: { ...defaultConfig.context, ...parsed.context },
66
+ permissions: { ...defaultConfig.permissions, ...parsed.permissions },
67
+ skills: { ...defaultConfig.skills, ...parsed.skills },
68
+ mcpServers: parsed.mcpServers ?? defaultConfig.mcpServers,
69
+ agents,
70
+ };
71
+ validateAgentConfig(config);
72
+ return config;
73
+ }
74
+ function normalizeAgents(list) {
75
+ if (!list?.length)
76
+ return undefined;
77
+ return list.map((item) => ({
78
+ name: item.name,
79
+ description: item.description || item.whenToUse || item.prompt || item.instructions || "",
80
+ tools: item.tools,
81
+ prompt: item.prompt ||
82
+ item.instructions ||
83
+ `You are the ${item.name} subagent. Complete the assigned task and report back.`,
84
+ }));
85
+ }
86
+ /**
87
+ * Load an explicit env file, or discover .apollo/.env then .env.
88
+ * By default **overrides** existing shell vars so local agent config wins.
89
+ */
90
+ export function loadEnvFile(envPath, options = {}) {
91
+ const selectedPath = envPath ?? [".apollo/.env", ".env"].find(fsSync.existsSync);
92
+ if (!selectedPath)
93
+ return false;
94
+ const resolvedPath = path.resolve(selectedPath);
95
+ if (!fsSync.existsSync(resolvedPath))
96
+ return false;
97
+ const override = options.override ?? true;
98
+ const raw = fsSync.readFileSync(resolvedPath, "utf8");
99
+ for (const line of raw.split(/\r?\n/)) {
100
+ const trimmed = line.trim();
101
+ if (!trimmed || trimmed.startsWith("#"))
102
+ continue;
103
+ const separatorIndex = trimmed.indexOf("=");
104
+ if (separatorIndex <= 0)
105
+ continue;
106
+ const key = trimmed.slice(0, separatorIndex).trim();
107
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
108
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
109
+ continue;
110
+ if (!override && process.env[key] !== undefined)
111
+ continue;
112
+ process.env[key] = unwrapEnvValue(rawValue);
113
+ }
114
+ return true;
115
+ }
116
+ function unwrapEnvValue(value) {
117
+ if ((value.startsWith('"') && value.endsWith('"')) ||
118
+ (value.startsWith("'") && value.endsWith("'"))) {
119
+ return value.slice(1, -1);
120
+ }
121
+ return value;
122
+ }
123
+ export function loadLlmConfig() {
124
+ const authToken = process.env.ANTHROPIC_AUTH_TOKEN;
125
+ const baseUrl = process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com";
126
+ const model = process.env.ANTHROPIC_MODEL ||
127
+ process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL ||
128
+ process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME ||
129
+ process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME ||
130
+ "claude-haiku-4-5";
131
+ if (!authToken) {
132
+ throw new Error("Missing ANTHROPIC_AUTH_TOKEN. Set it in .apollo/.env, .env, or your shell before running.");
133
+ }
134
+ const cacheTtl = process.env.ANTHROPIC_CACHE_TTL ?? "5m";
135
+ if (cacheTtl !== "5m" && cacheTtl !== "1h") {
136
+ throw new Error("ANTHROPIC_CACHE_TTL must be 5m or 1h.");
137
+ }
138
+ const maxTokens = positiveNumberEnv("ANTHROPIC_MAX_TOKENS", 4096, true);
139
+ const timeoutMs = positiveNumberEnv("ANTHROPIC_TIMEOUT_MS", 300000, true);
140
+ let parsedBaseUrl;
141
+ try {
142
+ parsedBaseUrl = new URL(baseUrl);
143
+ }
144
+ catch {
145
+ throw new Error("ANTHROPIC_BASE_URL must be a valid HTTP or HTTPS URL.");
146
+ }
147
+ if (parsedBaseUrl.protocol !== "http:" && parsedBaseUrl.protocol !== "https:") {
148
+ throw new Error("ANTHROPIC_BASE_URL must be a valid HTTP or HTTPS URL.");
149
+ }
150
+ return {
151
+ authToken,
152
+ baseUrl,
153
+ model,
154
+ maxTokens,
155
+ timeoutMs,
156
+ cacheTtl,
157
+ };
158
+ }
159
+ function positiveNumberEnv(name, fallback, integer) {
160
+ const raw = process.env[name];
161
+ // Treat unset or empty ("FOO=" blank line in .env) as "use the default".
162
+ const value = raw === undefined || raw.trim() === "" ? fallback : Number(raw);
163
+ if (!Number.isFinite(value) || value <= 0 || (integer && !Number.isInteger(value))) {
164
+ throw new Error(`${name} must be a positive${integer ? " integer" : " number"}.`);
165
+ }
166
+ return value;
167
+ }
168
+ function validateAgentConfig(config) {
169
+ if (config.systemPrompt !== undefined && typeof config.systemPrompt !== "string") {
170
+ throw new Error("systemPrompt must be a string.");
171
+ }
172
+ if (!Number.isInteger(config.maxTurns) || config.maxTurns <= 0) {
173
+ throw new Error("maxTurns must be a positive integer.");
174
+ }
175
+ if (!Number.isFinite(config.context.maxChars) || config.context.maxChars <= 0) {
176
+ throw new Error("context.maxChars must be a positive number.");
177
+ }
178
+ if (!Number.isFinite(config.context.summaryChars) || config.context.summaryChars <= 0) {
179
+ throw new Error("context.summaryChars must be a positive number.");
180
+ }
181
+ if (!["ask", "readonly", "unrestricted"].includes(config.permissions.mode)) {
182
+ throw new Error("permissions.mode must be ask, readonly, or unrestricted.");
183
+ }
184
+ if (typeof config.permissions.autoApproveReadOnly !== "boolean") {
185
+ throw new Error("permissions.autoApproveReadOnly must be a boolean.");
186
+ }
187
+ if (!Array.isArray(config.skills.directories) || config.skills.directories.some((dir) => typeof dir !== "string" || !dir)) {
188
+ throw new Error("skills.directories must contain non-empty strings.");
189
+ }
190
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Wait-line verbs — keep them clear. Default is almost always "Thinking".
3
+ */
4
+ export declare const SPINNER_VERBS: readonly string[];
5
+ /** Short chat / greetings always show Thinking (not random Hashing). */
6
+ export declare function pickSpinnerVerb(userText?: string): string;
7
+ //# sourceMappingURL=spinner-verbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner-verbs.d.ts","sourceRoot":"","sources":["../../src/constants/spinner-verbs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAQ1C,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzD"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Wait-line verbs — keep them clear. Default is almost always "Thinking".
3
+ */
4
+ export const SPINNER_VERBS = [
5
+ "Thinking",
6
+ "Thinking",
7
+ "Thinking",
8
+ "Processing",
9
+ "Working",
10
+ "Considering",
11
+ "Composing",
12
+ ];
13
+ /** Short chat / greetings always show Thinking (not random Hashing). */
14
+ export function pickSpinnerVerb(userText) {
15
+ const t = (userText ?? "").trim();
16
+ if (t.length <= 20 || /^(你好|您好|hello|hi|hey|哈喽|在吗|早上好|晚上好)/i.test(t)) {
17
+ return "Thinking";
18
+ }
19
+ const i = Math.floor(Math.random() * SPINNER_VERBS.length);
20
+ return SPINNER_VERBS[i] ?? "Thinking";
21
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Lightweight coordinator workflow — research → implement → verify → deliver.
3
+ * Phases can run serial; research can fan-out parallel Tasks via the main agent.
4
+ */
5
+ import type { WorkflowPhaseSnapshot } from "../session/store.js";
6
+ export type PhaseName = WorkflowPhaseSnapshot["name"];
7
+ export type WorkflowState = {
8
+ goal: string;
9
+ phases: WorkflowPhaseSnapshot[];
10
+ startedAt: string;
11
+ maxRetries: number;
12
+ };
13
+ export declare function createWorkflow(goal: string, maxRetries?: number): WorkflowState;
14
+ export declare function workflowStatusBar(wf: WorkflowState, color: boolean): string;
15
+ export declare function setPhaseStatus(wf: WorkflowState, name: PhaseName, status: WorkflowPhaseSnapshot["status"], summary?: string): WorkflowState;
16
+ export declare function currentPhase(wf: WorkflowState): WorkflowPhaseSnapshot | undefined;
17
+ export declare function workflowSystemExtra(wf: WorkflowState): string;
18
+ /** Parse model markers to advance workflow. */
19
+ export declare function applyWorkflowMarkers(wf: WorkflowState, text: string): WorkflowState;
20
+ //# sourceMappingURL=workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/coordinator/workflow.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAEtD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,SAAI,GAAG,aAAa,CAY1E;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAgC3E;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EACvC,OAAO,CAAC,EAAE,MAAM,GACf,aAAa,CAcf;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,aAAa,GAAG,qBAAqB,GAAG,SAAS,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,CAqB7D;AAED,+CAA+C;AAC/C,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAgCnF"}
@@ -0,0 +1,120 @@
1
+ export function createWorkflow(goal, maxRetries = 2) {
2
+ return {
3
+ goal,
4
+ maxRetries,
5
+ startedAt: new Date().toISOString(),
6
+ phases: [
7
+ { id: "research", name: "research", label: "研究中", status: "pending", retries: 0 },
8
+ { id: "implement", name: "implement", label: "实现中", status: "pending", retries: 0 },
9
+ { id: "verify", name: "verify", label: "验证中", status: "pending", retries: 0 },
10
+ { id: "deliver", name: "deliver", label: "交付中", status: "pending", retries: 0 },
11
+ ],
12
+ };
13
+ }
14
+ export function workflowStatusBar(wf, color) {
15
+ const dim = color ? "\x1b[90m" : "";
16
+ const reset = color ? "\x1b[0m" : "";
17
+ const green = color ? "\x1b[32m" : "";
18
+ const red = color ? "\x1b[31m" : "";
19
+ const blue = color ? "\x1b[38;2;96;165;250m" : "";
20
+ const yellow = color ? "\x1b[33m" : "";
21
+ const parts = wf.phases.map((p) => {
22
+ let mark = "○";
23
+ let c = dim;
24
+ if (p.status === "running") {
25
+ mark = "◉";
26
+ c = yellow;
27
+ }
28
+ else if (p.status === "completed") {
29
+ mark = "●";
30
+ c = green;
31
+ }
32
+ else if (p.status === "failed") {
33
+ mark = "●";
34
+ c = red;
35
+ }
36
+ else if (p.status === "skipped") {
37
+ mark = "○";
38
+ c = dim;
39
+ }
40
+ else {
41
+ mark = "○";
42
+ c = dim;
43
+ }
44
+ return `${c}${mark} ${p.label}${reset}`;
45
+ });
46
+ const head = color ? `${blue}Workflow${reset}` : "Workflow";
47
+ return `\n${head} ${parts.join(" → ")}\n${dim}goal: ${wf.goal.slice(0, 100)}${reset}\n`;
48
+ }
49
+ export function setPhaseStatus(wf, name, status, summary) {
50
+ return {
51
+ ...wf,
52
+ phases: wf.phases.map((p) => p.name === name
53
+ ? {
54
+ ...p,
55
+ status,
56
+ summary: summary ?? p.summary,
57
+ retries: status === "failed" ? p.retries + 1 : p.retries,
58
+ }
59
+ : p),
60
+ };
61
+ }
62
+ export function currentPhase(wf) {
63
+ return wf.phases.find((p) => p.status === "running") ?? wf.phases.find((p) => p.status === "pending");
64
+ }
65
+ export function workflowSystemExtra(wf) {
66
+ const lines = wf.phases
67
+ .map((p) => `- ${p.name} (${p.label}): ${p.status}${p.summary ? ` — ${p.summary}` : ""}`)
68
+ .join("\n");
69
+ return `# Coordinator Workflow (ACTIVE)
70
+ You are orchestrating a multi-phase task. Goal: ${wf.goal}
71
+
72
+ ## Phases
73
+ ${lines}
74
+
75
+ ## Rules
76
+ 1. Work phase-by-phase. Prefer Explore for research, general-purpose for implement, Plan only for planning.
77
+ 2. For research: fan out parallel Task(Explore) when independent angles help.
78
+ 3. For implement: one focused Task at a time per file area; include file paths and exact changes in briefs.
79
+ 4. For verify: spawn a fresh Task — do not rubber-stamp. Run tests/typecheck when possible.
80
+ 5. For deliver: summarize results and list artifacts/paths for the user.
81
+ 6. On phase failure: retry up to ${wf.maxRetries} times with a corrected brief, then report to the user.
82
+ 7. After finishing a phase, say clearly: PHASE_DONE:<name>:<one-line summary>
83
+ 8. When all phases complete, say: WORKFLOW_DONE
84
+
85
+ You may use Task, tools, memory_*, and ask_user_question when blocked.`;
86
+ }
87
+ /** Parse model markers to advance workflow. */
88
+ export function applyWorkflowMarkers(wf, text) {
89
+ let next = wf;
90
+ const doneRe = /PHASE_DONE:(\w+):([^\n]+)/g;
91
+ for (const m of text.matchAll(doneRe)) {
92
+ const name = m[1];
93
+ const summary = (m[2] ?? "").trim();
94
+ if (["research", "implement", "verify", "deliver"].includes(name)) {
95
+ next = setPhaseStatus(next, name, "completed", summary);
96
+ // auto-start next pending
97
+ const order = ["research", "implement", "verify", "deliver"];
98
+ const idx = order.indexOf(name);
99
+ const following = order[idx + 1];
100
+ if (following) {
101
+ const ph = next.phases.find((p) => p.name === following);
102
+ if (ph && ph.status === "pending")
103
+ next = setPhaseStatus(next, following, "running");
104
+ }
105
+ }
106
+ }
107
+ if (/WORKFLOW_DONE/.test(text)) {
108
+ next = {
109
+ ...next,
110
+ phases: next.phases.map((p) => p.status === "pending" || p.status === "running" ? { ...p, status: "completed" } : p),
111
+ };
112
+ }
113
+ // ensure first pending becomes running if none running
114
+ if (!next.phases.some((p) => p.status === "running")) {
115
+ const first = next.phases.find((p) => p.status === "pending");
116
+ if (first)
117
+ next = setPhaseStatus(next, first.name, "running");
118
+ }
119
+ return next;
120
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Goal mode — pursue a user goal until done (open-ended autonomous loop).
3
+ * Differs from fixed 4-phase workflow: steps are free-form; exit via markers or /goal stop.
4
+ */
5
+ export type GoalStatus = "active" | "completed" | "failed" | "blocked" | "paused";
6
+ export type GoalState = {
7
+ goal: string;
8
+ status: GoalStatus;
9
+ /** Optional success criteria lines */
10
+ criteria: string[];
11
+ /** Rolling progress notes */
12
+ progress: string[];
13
+ iteration: number;
14
+ maxIterations: number;
15
+ startedAt: string;
16
+ completedAt?: string;
17
+ lastBlockedReason?: string;
18
+ };
19
+ export declare function createGoal(goal: string, maxIterations?: number, criteria?: string[]): GoalState;
20
+ export declare function goalStatusBar(g: GoalState, color: boolean): string;
21
+ export declare function goalSystemExtra(g: GoalState): string;
22
+ /** Parse model markers and update goal state. */
23
+ export declare function applyGoalMarkers(g: GoalState, text: string): GoalState;
24
+ export declare function shouldAutoContinue(g: GoalState): boolean;
25
+ export declare function nextGoalContinuePrompt(g: GoalState): string;
26
+ //# sourceMappingURL=goal-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goal-mode.d.ts","sourceRoot":"","sources":["../../src/goal/goal-mode.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAElF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,SAAK,EAAE,QAAQ,GAAE,MAAM,EAAO,GAAG,SAAS,CAU/F;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAsClE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CA2CpD;AAED,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAiCtE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,SAAS,GAAG,OAAO,CAExD;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAO3D"}