@apolloyh/apollo-agent 0.1.5 → 0.1.7
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/.apollo/config.example.json +1 -0
- package/README.md +12 -3
- package/dist/brand.d.ts +1 -1
- package/dist/brand.js +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +87 -18
- package/dist/index.js +74 -73
- package/dist/project-instructions.d.ts +2 -3
- package/dist/project-instructions.d.ts.map +1 -1
- package/dist/project-instructions.js +28 -13
- package/dist/runtime/prompt-builder.d.ts +1 -1
- package/dist/runtime/prompt-builder.d.ts.map +1 -1
- package/dist/runtime/prompt-builder.js +1 -1
- package/docs/sdk.md +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,9 +35,18 @@ apollo -r <session-id>
|
|
|
35
35
|
|
|
36
36
|
Apollo loads project `.apollo/.env`, project `.env`, then user-level `~/.apollo/.env`; use `--env-file <path>` to override discovery.
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
Apollo creates global defaults at `~/.apollo/config.json` and copies the complete option
|
|
39
|
+
reference to `~/.apollo/config.example.json`. Project overrides live at
|
|
40
|
+
`.apollo/config.json`; Apollo also reads the legacy `agent.config.json` during migration.
|
|
41
|
+
Configuration layers as built-in defaults → user config → project config → `--config`.
|
|
42
|
+
Set `systemPrompt` in either config to append stable custom system instructions; restart Apollo to reload them.
|
|
43
|
+
|
|
44
|
+
Apollo also loads `~/.apollo/APOLLO.md` followed by the project `APOLLO.md` (lowercase
|
|
45
|
+
`apollo.md` is accepted). If no project Apollo file exists, the standard `AGENTS.md` and
|
|
46
|
+
`.apollo/instructions.md` paths remain supported. These instructions are frozen for the session.
|
|
47
|
+
|
|
48
|
+
While Apollo is working, type and press Enter to queue another user message. Queued messages
|
|
49
|
+
run in FIFO order after the current turn; press Esc to interrupt the current turn safely.
|
|
41
50
|
|
|
42
51
|
Commands: `/help` · `/skills` · `/agents` · `/sessions` · `/resume` · `/verbose` · `/stream` · `/clear` · `/exit`
|
|
43
52
|
|
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.7";
|
|
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.7",
|
|
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
CHANGED
|
@@ -6,6 +6,14 @@ export declare function ensureUserEnvFile(): {
|
|
|
6
6
|
path: string;
|
|
7
7
|
created: boolean;
|
|
8
8
|
};
|
|
9
|
+
export declare function ensureUserConfigFile(): {
|
|
10
|
+
path: string;
|
|
11
|
+
created: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function ensureUserConfigExampleFile(): {
|
|
14
|
+
path: string;
|
|
15
|
+
created: boolean;
|
|
16
|
+
};
|
|
9
17
|
export declare function loadAgentConfig(configPath?: string): Promise<AgentConfig>;
|
|
10
18
|
/**
|
|
11
19
|
* Load an explicit env file, or discover project env then ~/.apollo/.env.
|
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":"AAKA,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;AAwBpH,wBAAgB,iBAAiB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAatE;AAED,wBAAgB,oBAAoB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAazE;AAED,wBAAgB,2BAA2B,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAchF;AAED,wBAAsB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAkD/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
|
@@ -2,6 +2,7 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
/** Default subagents — Claude Code–style Task roles. */
|
|
6
7
|
export const DEFAULT_AGENTS = [
|
|
7
8
|
{
|
|
@@ -57,6 +58,15 @@ ANTHROPIC_AUTH_TOKEN=
|
|
|
57
58
|
# ANTHROPIC_BASE_URL=https://api.anthropic.com
|
|
58
59
|
# ANTHROPIC_MODEL=claude-haiku-4-5
|
|
59
60
|
`;
|
|
61
|
+
const USER_CONFIG_TEMPLATE = `${JSON.stringify({
|
|
62
|
+
systemPrompt: "",
|
|
63
|
+
maxTurns: defaultConfig.maxTurns,
|
|
64
|
+
context: defaultConfig.context,
|
|
65
|
+
permissions: defaultConfig.permissions,
|
|
66
|
+
skills: defaultConfig.skills,
|
|
67
|
+
mcpServers: defaultConfig.mcpServers,
|
|
68
|
+
}, null, 2)}\n`;
|
|
69
|
+
const BUNDLED_CONFIG_EXAMPLE_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.apollo/config.example.json");
|
|
60
70
|
export function ensureUserEnvFile() {
|
|
61
71
|
const directory = path.join(os.homedir(), ".apollo");
|
|
62
72
|
const envPath = path.join(directory, ".env");
|
|
@@ -73,27 +83,86 @@ export function ensureUserEnvFile() {
|
|
|
73
83
|
throw error;
|
|
74
84
|
}
|
|
75
85
|
}
|
|
86
|
+
export function ensureUserConfigFile() {
|
|
87
|
+
const directory = path.join(os.homedir(), ".apollo");
|
|
88
|
+
const configPath = path.join(directory, "config.json");
|
|
89
|
+
if (fsSync.existsSync(configPath))
|
|
90
|
+
return { path: configPath, created: false };
|
|
91
|
+
fsSync.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
92
|
+
try {
|
|
93
|
+
fsSync.writeFileSync(configPath, USER_CONFIG_TEMPLATE, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
94
|
+
return { path: configPath, created: true };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error.code === "EEXIST")
|
|
98
|
+
return { path: configPath, created: false };
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function ensureUserConfigExampleFile() {
|
|
103
|
+
const directory = path.join(os.homedir(), ".apollo");
|
|
104
|
+
const examplePath = path.join(directory, "config.example.json");
|
|
105
|
+
if (fsSync.existsSync(examplePath))
|
|
106
|
+
return { path: examplePath, created: false };
|
|
107
|
+
const template = fsSync.readFileSync(BUNDLED_CONFIG_EXAMPLE_PATH, "utf8");
|
|
108
|
+
fsSync.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
109
|
+
try {
|
|
110
|
+
fsSync.writeFileSync(examplePath, template, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
111
|
+
return { path: examplePath, created: true };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error.code === "EEXIST")
|
|
115
|
+
return { path: examplePath, created: false };
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
76
119
|
export async function loadAgentConfig(configPath) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const agents = normalizeAgents(parsed.agents ?? parsed.workers) ?? defaultConfig.agents;
|
|
87
|
-
const config = {
|
|
120
|
+
const userConfigPath = path.join(os.homedir(), ".apollo", "config.json");
|
|
121
|
+
const projectConfigPath = [DEFAULT_CONFIG_PATH, LEGACY_CONFIG_PATH].find(fsSync.existsSync);
|
|
122
|
+
const layers = [
|
|
123
|
+
...(fsSync.existsSync(userConfigPath) ? [{ file: userConfigPath, projectScoped: false }] : []),
|
|
124
|
+
...(projectConfigPath ? [{ file: projectConfigPath, projectScoped: true }] : []),
|
|
125
|
+
...(configPath ? [{ file: configPath, projectScoped: true }] : []),
|
|
126
|
+
];
|
|
127
|
+
const seen = new Set();
|
|
128
|
+
let config = {
|
|
88
129
|
...defaultConfig,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
agents,
|
|
130
|
+
workspaceRoot: path.resolve(defaultConfig.workspaceRoot),
|
|
131
|
+
context: { ...defaultConfig.context },
|
|
132
|
+
permissions: { ...defaultConfig.permissions },
|
|
133
|
+
skills: { ...defaultConfig.skills },
|
|
134
|
+
mcpServers: { ...defaultConfig.mcpServers },
|
|
135
|
+
agents: [...defaultConfig.agents],
|
|
96
136
|
};
|
|
137
|
+
for (const layer of layers) {
|
|
138
|
+
const resolvedConfigPath = path.resolve(layer.file);
|
|
139
|
+
if (seen.has(resolvedConfigPath))
|
|
140
|
+
continue;
|
|
141
|
+
seen.add(resolvedConfigPath);
|
|
142
|
+
const raw = await fs.readFile(resolvedConfigPath, "utf8");
|
|
143
|
+
const parsed = JSON.parse(raw);
|
|
144
|
+
const { workers: _workers, ...overrides } = parsed;
|
|
145
|
+
if (typeof overrides.systemPrompt === "string" && !overrides.systemPrompt.trim())
|
|
146
|
+
delete overrides.systemPrompt;
|
|
147
|
+
if (typeof overrides.modeInstructions === "string" && !overrides.modeInstructions.trim())
|
|
148
|
+
delete overrides.modeInstructions;
|
|
149
|
+
const configDir = path.dirname(resolvedConfigPath);
|
|
150
|
+
const baseDir = path.basename(configDir) === ".apollo" ? path.dirname(configDir) : configDir;
|
|
151
|
+
const agents = normalizeAgents(parsed.agents ?? parsed.workers) ?? config.agents;
|
|
152
|
+
const workspaceRoot = layer.projectScoped && parsed.workspaceRoot !== undefined
|
|
153
|
+
? path.resolve(baseDir, parsed.workspaceRoot)
|
|
154
|
+
: config.workspaceRoot;
|
|
155
|
+
config = {
|
|
156
|
+
...config,
|
|
157
|
+
...overrides,
|
|
158
|
+
workspaceRoot,
|
|
159
|
+
context: { ...config.context, ...parsed.context },
|
|
160
|
+
permissions: { ...config.permissions, ...parsed.permissions },
|
|
161
|
+
skills: { ...config.skills, ...parsed.skills },
|
|
162
|
+
mcpServers: { ...config.mcpServers, ...parsed.mcpServers },
|
|
163
|
+
agents,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
97
166
|
validateAgentConfig(config);
|
|
98
167
|
return config;
|
|
99
168
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import readline from "node:readline/promises";
|
|
|
4
4
|
import { ASCII_LOGO, PRODUCT } from "./brand.js";
|
|
5
5
|
import { parseArgs } from "./cli-args.js";
|
|
6
6
|
import { createPastedTextBlock, pastedTextPreview, renderInputView, resolvePastedText, } from "./cli-input.js";
|
|
7
|
-
import { ensureUserEnvFile, loadAgentConfig, loadEnvFile, loadLlmConfig, MISSING_AUTH_TOKEN_MESSAGE, } from "./config.js";
|
|
7
|
+
import { ensureUserEnvFile, ensureUserConfigExampleFile, ensureUserConfigFile, loadAgentConfig, loadEnvFile, loadLlmConfig, MISSING_AUTH_TOKEN_MESSAGE, } from "./config.js";
|
|
8
8
|
import { pickSpinnerVerb } from "./constants/spinner-verbs.js";
|
|
9
9
|
import { QueryEngine } from "./runtime/query-engine.js";
|
|
10
10
|
import { createCliApprovalProvider } from "./runtime/permissions.js";
|
|
@@ -14,7 +14,6 @@ import { ThoughtFoldManager } from "./thought-fold.js";
|
|
|
14
14
|
import { workflowStatusBar } from "./coordinator/workflow.js";
|
|
15
15
|
import { CliTraceRenderer, stripTerminalEscapes } from "./trace.js";
|
|
16
16
|
let activeCliInput;
|
|
17
|
-
let pinnedCliInput;
|
|
18
17
|
let modalActive = false;
|
|
19
18
|
let resolveModal;
|
|
20
19
|
let modalDone;
|
|
@@ -59,6 +58,8 @@ function filterSlashCommands(buffer) {
|
|
|
59
58
|
async function main() {
|
|
60
59
|
const args = parseArgs(process.argv.slice(2));
|
|
61
60
|
const userEnv = ensureUserEnvFile();
|
|
61
|
+
const userConfig = ensureUserConfigFile();
|
|
62
|
+
const userConfigExample = ensureUserConfigExampleFile();
|
|
62
63
|
loadEnvFile(args.envPath);
|
|
63
64
|
const task = args.task ?? (stdin.isTTY ? undefined : (await readStdin()).trim());
|
|
64
65
|
if (!task && !stdin.isTTY) {
|
|
@@ -99,6 +100,7 @@ async function main() {
|
|
|
99
100
|
const rendererSink = renderer.sink();
|
|
100
101
|
const taskActivity = new TaskActivityLog();
|
|
101
102
|
const inputHistory = [];
|
|
103
|
+
const pendingUserMessages = [];
|
|
102
104
|
// traceSink closes over the runtime, but the runtime is created after this. Hold it in a
|
|
103
105
|
// mutable ref that createRuntime populates, so goal_status events don't hit a TDZ (task mode
|
|
104
106
|
// never reaches the interactive `runtime` binding). Also lets SIGINT close whichever is active.
|
|
@@ -241,6 +243,12 @@ async function main() {
|
|
|
241
243
|
if (userEnv.created) {
|
|
242
244
|
stdout.write(`\nCreated ${shortenHome(userEnv.path)}. Add ANTHROPIC_AUTH_TOKEN there, then restart Apollo.\n`);
|
|
243
245
|
}
|
|
246
|
+
if (userConfig.created) {
|
|
247
|
+
stdout.write(`Created ${shortenHome(userConfig.path)} with default settings.\n`);
|
|
248
|
+
}
|
|
249
|
+
if (userConfigExample.created) {
|
|
250
|
+
stdout.write(`Created ${shortenHome(userConfigExample.path)} with all configuration options.\n`);
|
|
251
|
+
}
|
|
244
252
|
const approvalProvider = createCliApprovalProvider(agentConfig, traceSink, () => yolo, {
|
|
245
253
|
beforePrompt: () => {
|
|
246
254
|
stopLiveStatus("none");
|
|
@@ -274,7 +282,7 @@ async function main() {
|
|
|
274
282
|
await modalDone;
|
|
275
283
|
continue;
|
|
276
284
|
}
|
|
277
|
-
const text = (await readCliLine({ color: traceOptions.color, history: inputHistory })).trim();
|
|
285
|
+
const text = (pendingUserMessages.shift() ?? await readCliLine({ color: traceOptions.color, history: inputHistory })).trim();
|
|
278
286
|
if (!text)
|
|
279
287
|
continue;
|
|
280
288
|
if (text === "/exit" || text === "/quit") {
|
|
@@ -406,12 +414,12 @@ async function main() {
|
|
|
406
414
|
outcomePrintedThisTurn = false;
|
|
407
415
|
activeVerb = pickSpinnerVerb(goalBody);
|
|
408
416
|
const firstPrompt = `Start Goal mode.\nGoal: ${goalBody}\nMake the first concrete step. Use tools as needed.\nEnd with GOAL_PROGRESS / GOAL_DONE / GOAL_BLOCKED / GOAL_FAILED.`;
|
|
409
|
-
|
|
417
|
+
const turnInput = startTurnInput(runtime, traceOptions.color, inputHistory, pendingUserMessages);
|
|
410
418
|
try {
|
|
411
419
|
await runGoalLoop(runtime, renderer, thoughtFold, traceOptions, firstPrompt);
|
|
412
420
|
}
|
|
413
421
|
finally {
|
|
414
|
-
|
|
422
|
+
await turnInput.stop();
|
|
415
423
|
}
|
|
416
424
|
continue;
|
|
417
425
|
}
|
|
@@ -531,12 +539,12 @@ async function main() {
|
|
|
531
539
|
continue;
|
|
532
540
|
}
|
|
533
541
|
// ── user turn ──────────────────────────────────────────────
|
|
534
|
-
// Keep
|
|
542
|
+
// Keep a live queue input below streaming output while this turn runs.
|
|
535
543
|
runningTaskCount = 0;
|
|
536
544
|
assistantResponseStarted = false;
|
|
537
545
|
outcomePrintedThisTurn = false;
|
|
538
546
|
activeVerb = pickSpinnerVerb(text);
|
|
539
|
-
|
|
547
|
+
const turnInput = startTurnInput(runtime, traceOptions.color, inputHistory, pendingUserMessages);
|
|
540
548
|
try {
|
|
541
549
|
if (!llmConfig.authToken)
|
|
542
550
|
throw new Error(MISSING_AUTH_TOKEN_MESSAGE);
|
|
@@ -558,7 +566,7 @@ async function main() {
|
|
|
558
566
|
writeAboveActiveInput(runtime.renderWorkflowBar(traceOptions.color));
|
|
559
567
|
}
|
|
560
568
|
void runtime.saveCurrentSession().catch(() => null);
|
|
561
|
-
|
|
569
|
+
await turnInput.stop();
|
|
562
570
|
}
|
|
563
571
|
}
|
|
564
572
|
const sessionId = runtime.getSessionId();
|
|
@@ -952,67 +960,42 @@ function writeAboveActiveInput(text) {
|
|
|
952
960
|
input?.renderAfterExternalOutput();
|
|
953
961
|
}
|
|
954
962
|
}
|
|
955
|
-
function
|
|
956
|
-
if (!stdin.isTTY || !stdout.isTTY
|
|
957
|
-
return;
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
963
|
+
function startTurnInput(runtime, color, history, pendingMessages) {
|
|
964
|
+
if (!stdin.isTTY || !stdout.isTTY)
|
|
965
|
+
return { stop: async () => undefined };
|
|
966
|
+
let accepting = true;
|
|
967
|
+
const done = (async () => {
|
|
968
|
+
while (accepting) {
|
|
969
|
+
if (modalActive && modalDone) {
|
|
970
|
+
await modalDone;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
const value = await readCliLine({
|
|
974
|
+
color,
|
|
975
|
+
history,
|
|
976
|
+
queueMode: true,
|
|
977
|
+
onEscape: () => {
|
|
978
|
+
const cancelled = runtime.cancelCurrentTurn();
|
|
979
|
+
if (cancelled)
|
|
980
|
+
accepting = false;
|
|
981
|
+
return cancelled;
|
|
982
|
+
},
|
|
983
|
+
});
|
|
984
|
+
const queued = value.trim();
|
|
985
|
+
if (queued)
|
|
986
|
+
pendingMessages.push(queued);
|
|
987
|
+
if (!accepting)
|
|
988
|
+
break;
|
|
972
989
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
return;
|
|
981
|
-
clear();
|
|
982
|
-
const statusLine = renderLiveStatusLine(color);
|
|
983
|
-
const lines = [
|
|
984
|
-
...(statusLine ? [statusLine] : []),
|
|
985
|
-
renderInputBorder("top", color),
|
|
986
|
-
renderInputPrompt(color),
|
|
987
|
-
renderInputBorder("bottom", color),
|
|
988
|
-
];
|
|
989
|
-
stdout.write(lines.join("\n"));
|
|
990
|
-
renderedLines = lines.length;
|
|
991
|
-
renderedCursorLine = statusLine ? 2 : 1;
|
|
992
|
-
const rowsBelowInput = lines.length - 1 - renderedCursorLine;
|
|
993
|
-
stdout.write(`\x1b[${rowsBelowInput}A\r\x1b[3G`);
|
|
994
|
-
};
|
|
995
|
-
const pinned = {
|
|
996
|
-
clearForExternalOutput: clear,
|
|
997
|
-
renderAfterExternalOutput: render,
|
|
998
|
-
cancelForModal: () => {
|
|
999
|
-
clear();
|
|
1000
|
-
if (activeCliInput === pinned)
|
|
1001
|
-
activeCliInput = undefined;
|
|
990
|
+
})();
|
|
991
|
+
return {
|
|
992
|
+
stop: async () => {
|
|
993
|
+
accepting = false;
|
|
994
|
+
activeCliInput?.cancelForModal();
|
|
995
|
+
await done;
|
|
996
|
+
stdout.write("\x1b[?25h");
|
|
1002
997
|
},
|
|
1003
998
|
};
|
|
1004
|
-
pinnedCliInput = pinned;
|
|
1005
|
-
activeCliInput = pinned;
|
|
1006
|
-
render();
|
|
1007
|
-
}
|
|
1008
|
-
function stopPinnedCliInput() {
|
|
1009
|
-
const pinned = pinnedCliInput;
|
|
1010
|
-
if (!pinned)
|
|
1011
|
-
return;
|
|
1012
|
-
pinned.clearForExternalOutput();
|
|
1013
|
-
if (activeCliInput === pinned)
|
|
1014
|
-
activeCliInput = undefined;
|
|
1015
|
-
pinnedCliInput = undefined;
|
|
1016
999
|
}
|
|
1017
1000
|
async function readCliLine(options) {
|
|
1018
1001
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
@@ -1044,6 +1027,8 @@ async function readCliLine(options) {
|
|
|
1044
1027
|
done = true;
|
|
1045
1028
|
if (onData)
|
|
1046
1029
|
stdin.off("data", onData);
|
|
1030
|
+
if (stdin.isTTY)
|
|
1031
|
+
stdout.write("\x1b[?25h");
|
|
1047
1032
|
if (stdin.isTTY)
|
|
1048
1033
|
stdout.write("\x1b[?2004l");
|
|
1049
1034
|
if (stdin.isTTY)
|
|
@@ -1059,8 +1044,10 @@ async function readCliLine(options) {
|
|
|
1059
1044
|
lines.push(liveStatusLine);
|
|
1060
1045
|
lines.push(renderInputBorder("top", options.color));
|
|
1061
1046
|
const view = renderInputView(buffer, cursor, inputContentWidth(), pastedTextBlocks);
|
|
1062
|
-
|
|
1063
|
-
lines.push(
|
|
1047
|
+
const idleQueue = options.queueMode && !buffer;
|
|
1048
|
+
lines.push(`${renderInputPrompt(options.color)}${idleQueue ? renderTurnInputHint(options.color) : (view.lines[0] ?? "")}`);
|
|
1049
|
+
if (!idleQueue)
|
|
1050
|
+
lines.push(...view.lines.slice(1).map((line) => ` ${line}`));
|
|
1064
1051
|
if (paletteOpen) {
|
|
1065
1052
|
const filtered = filterSlashCommands(buffer);
|
|
1066
1053
|
lines.push(...renderSlashPaletteLines(selected, options.color, filtered, buffer));
|
|
@@ -1096,6 +1083,7 @@ async function readCliLine(options) {
|
|
|
1096
1083
|
renderedCursorLine = (renderLiveStatusLine(options.color) ? 2 : 1) + view.cursorLine;
|
|
1097
1084
|
const rowsBelowInput = lines.length - 1 - renderedCursorLine;
|
|
1098
1085
|
stdout.write(`\x1b[${rowsBelowInput}A\r\x1b[${3 + view.cursorWidth}G`);
|
|
1086
|
+
stdout.write(options.queueMode && !buffer ? "\x1b[?25l" : "\x1b[?25h");
|
|
1099
1087
|
};
|
|
1100
1088
|
const finish = (value) => {
|
|
1101
1089
|
clearRendered();
|
|
@@ -1106,7 +1094,14 @@ async function readCliLine(options) {
|
|
|
1106
1094
|
if (options.history.length > 100)
|
|
1107
1095
|
options.history.splice(0, options.history.length - 100);
|
|
1108
1096
|
const preview = pastedTextPreview(value, pastedTextBlocks).replace(/\n/g, " ↵ ").replace(/\t/g, " ");
|
|
1109
|
-
|
|
1097
|
+
const submitted = preview === value ? submittedInputLabel(resolvedValue) : preview;
|
|
1098
|
+
if (options.queueMode) {
|
|
1099
|
+
const label = options.color ? "\x1b[90m• Queued\x1b[0m" : "• Queued";
|
|
1100
|
+
stdout.write(`${label} ${submitted}\n`);
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
stdout.write(`${renderInputBorder("top", options.color)}\n${renderInputPrompt(options.color)}${submitted}\n${renderInputBorder("bottom", options.color)}\n`);
|
|
1104
|
+
}
|
|
1110
1105
|
}
|
|
1111
1106
|
complete(resolvedValue);
|
|
1112
1107
|
};
|
|
@@ -1174,7 +1169,13 @@ async function readCliLine(options) {
|
|
|
1174
1169
|
finish("/exit");
|
|
1175
1170
|
return;
|
|
1176
1171
|
}
|
|
1172
|
+
if (chunk === "\x1b" && options.onEscape?.()) {
|
|
1173
|
+
finish("");
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1177
1176
|
if (chunk === "\r" || chunk === "\n") {
|
|
1177
|
+
if (options.queueMode && !buffer)
|
|
1178
|
+
return;
|
|
1178
1179
|
finish(buffer);
|
|
1179
1180
|
return;
|
|
1180
1181
|
}
|
|
@@ -1364,10 +1365,6 @@ function endModalInput() {
|
|
|
1364
1365
|
resolveModal?.();
|
|
1365
1366
|
resolveModal = undefined;
|
|
1366
1367
|
modalDone = undefined;
|
|
1367
|
-
if (pinnedCliInput && !activeCliInput) {
|
|
1368
|
-
activeCliInput = pinnedCliInput;
|
|
1369
|
-
pinnedCliInput.renderAfterExternalOutput();
|
|
1370
|
-
}
|
|
1371
1368
|
}
|
|
1372
1369
|
async function askPlainQuestion(prompt) {
|
|
1373
1370
|
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
@@ -1610,6 +1607,10 @@ function compactLine(text, max) {
|
|
|
1610
1607
|
function renderInputPrompt(useColor) {
|
|
1611
1608
|
return useColor ? "\x1b[38;2;96;165;250m›\x1b[0m " : "› ";
|
|
1612
1609
|
}
|
|
1610
|
+
function renderTurnInputHint(useColor) {
|
|
1611
|
+
const hint = "Type to queue a message · Esc to interrupt";
|
|
1612
|
+
return useColor ? `\x1b[90m${hint}\x1b[0m` : hint;
|
|
1613
|
+
}
|
|
1613
1614
|
function renderLiveStatusLine(useColor) {
|
|
1614
1615
|
if (!liveStatus)
|
|
1615
1616
|
return undefined;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const PROJECT_INSTRUCTION_FILES: readonly ["APOLLO.md", "CLAUDE.md", "AGENTS.md", ".apollo/instructions.md", ".claude/CLAUDE.md"];
|
|
1
|
+
export declare const PROJECT_INSTRUCTION_FILES: readonly ["APOLLO.md", "apollo.md", "AGENTS.md", ".apollo/instructions.md"];
|
|
3
2
|
export type ProjectInstructions = {
|
|
4
3
|
path: string;
|
|
5
4
|
content: string;
|
|
6
5
|
};
|
|
7
|
-
export declare function loadProjectInstructions(workspaceRoot: string, maxChars?: number): Promise<ProjectInstructions | null>;
|
|
6
|
+
export declare function loadProjectInstructions(workspaceRoot: string, maxChars?: number, userHome?: string): Promise<ProjectInstructions | null>;
|
|
8
7
|
//# sourceMappingURL=project-instructions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-instructions.d.ts","sourceRoot":"","sources":["../src/project-instructions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"project-instructions.d.ts","sourceRoot":"","sources":["../src/project-instructions.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,yBAAyB,6EAK5B,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,MAAM,EACrB,QAAQ,SAAQ,EAChB,QAAQ,SAAe,GACtB,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAgCrC"}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code–style project instructions.
|
|
3
|
-
* Loads the first matching file from the workspace root (in order).
|
|
4
|
-
*/
|
|
1
|
+
/** Apollo project instructions. Loads the first matching workspace file in order. */
|
|
5
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
6
5
|
import { safeRealResolve } from "./utils.js";
|
|
7
|
-
/** Same spirit as CLAUDE.md / AGENTS.md discovery. */
|
|
8
6
|
export const PROJECT_INSTRUCTION_FILES = [
|
|
9
7
|
"APOLLO.md",
|
|
10
|
-
"
|
|
8
|
+
"apollo.md",
|
|
11
9
|
"AGENTS.md",
|
|
12
10
|
".apollo/instructions.md",
|
|
13
|
-
".claude/CLAUDE.md",
|
|
14
11
|
];
|
|
15
|
-
export async function loadProjectInstructions(workspaceRoot, maxChars = 24000) {
|
|
12
|
+
export async function loadProjectInstructions(workspaceRoot, maxChars = 24000, userHome = os.homedir()) {
|
|
13
|
+
const loaded = [];
|
|
14
|
+
for (const fileName of ["APOLLO.md", "apollo.md"]) {
|
|
15
|
+
try {
|
|
16
|
+
const content = (await fs.readFile(path.join(userHome, ".apollo", fileName), "utf8")).trim();
|
|
17
|
+
if (content)
|
|
18
|
+
loaded.push({ path: `~/.apollo/${fileName}`, content });
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// User-level instructions are optional.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
for (const rel of PROJECT_INSTRUCTION_FILES) {
|
|
17
26
|
try {
|
|
18
27
|
const full = await safeRealResolve(workspaceRoot, rel);
|
|
@@ -20,14 +29,20 @@ export async function loadProjectInstructions(workspaceRoot, maxChars = 24000) {
|
|
|
20
29
|
const content = raw.trim();
|
|
21
30
|
if (!content)
|
|
22
31
|
continue;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
content: content.length > maxChars ? `${content.slice(0, maxChars)}\n\n…(truncated)` : content,
|
|
26
|
-
};
|
|
32
|
+
loaded.push({ path: rel, content });
|
|
33
|
+
break;
|
|
27
34
|
}
|
|
28
35
|
catch {
|
|
29
36
|
// try next
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
|
-
|
|
39
|
+
if (!loaded.length)
|
|
40
|
+
return null;
|
|
41
|
+
const combined = loaded
|
|
42
|
+
.map((instruction) => `## ${instruction.path}\n${instruction.content}`)
|
|
43
|
+
.join("\n\n");
|
|
44
|
+
return {
|
|
45
|
+
path: loaded.map((instruction) => instruction.path).join(" + "),
|
|
46
|
+
content: combined.length > maxChars ? `${combined.slice(0, maxChars)}\n\n…(truncated)` : combined,
|
|
47
|
+
};
|
|
33
48
|
}
|
|
@@ -3,7 +3,7 @@ type PromptBuilderOptions = {
|
|
|
3
3
|
agentConfig: AgentConfig;
|
|
4
4
|
tools: AnthropicTool[];
|
|
5
5
|
subagent?: SubagentConfig;
|
|
6
|
-
/**
|
|
6
|
+
/** Apollo project instructions (APOLLO.md / AGENTS.md). */
|
|
7
7
|
projectInstructions?: {
|
|
8
8
|
path: string;
|
|
9
9
|
content: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-builder.d.ts","sourceRoot":"","sources":["../../src/runtime/prompt-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI9E,KAAK,oBAAoB,GAAG;IAC1B,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,
|
|
1
|
+
{"version":3,"file":"prompt-builder.d.ts","sourceRoot":"","sources":["../../src/runtime/prompt-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI9E,KAAK,oBAAoB,GAAG;IAC1B,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,2DAA2D;IAC3D,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAChE,CAAC;AAEF,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;IAE1D,KAAK,IAAI,MAAM;IAqBf,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,0BAA0B;IASlC,OAAO,CAAC,yBAAyB;IAOjC,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,WAAW;CAQpB"}
|
|
@@ -55,7 +55,7 @@ ${PRODUCT.tagline}`;
|
|
|
55
55
|
if (!proj?.content)
|
|
56
56
|
return "";
|
|
57
57
|
return `# Project Instructions
|
|
58
|
-
These instructions
|
|
58
|
+
These instructions were loaded from \`${proj.path}\`. Follow them for this project; project-level instructions take precedence over user-level instructions when they conflict.
|
|
59
59
|
|
|
60
60
|
${proj.content}`;
|
|
61
61
|
}
|
package/docs/sdk.md
CHANGED
|
@@ -89,7 +89,7 @@ const engine = await createQueryEngine({
|
|
|
89
89
|
|
|
90
90
|
| 参数 | 类型 | 说明 |
|
|
91
91
|
|---|---|---|
|
|
92
|
-
| `configPath` | `string` | Agent
|
|
92
|
+
| `configPath` | `string` | Agent 显式配置路径;在用户配置和项目配置之后覆盖,默认还会发现 `~/.apollo/config.json`、`.apollo/config.json`,并兼容旧 `agent.config.json` |
|
|
93
93
|
| `envPath` | `string` | 可选环境变量文件;未提供模型配置时默认发现 `.apollo/.env`,然后 `.env` |
|
|
94
94
|
| `agentConfig` | `AgentConfig` | 直接传入完整 Agent 配置;优先于 `configPath` |
|
|
95
95
|
| `llmConfig` | `LlmConfig` | 直接传入模型配置;优先于环境变量 |
|
|
@@ -104,7 +104,7 @@ const engine = await createQueryEngine({
|
|
|
104
104
|
|
|
105
105
|
## 系统提示词与缓存
|
|
106
106
|
|
|
107
|
-
`.apollo/config.json`
|
|
107
|
+
`~/.apollo/config.json` 和项目的 `.apollo/config.json` 都可以配置:
|
|
108
108
|
|
|
109
109
|
```json
|
|
110
110
|
{
|
|
@@ -114,6 +114,10 @@ const engine = await createQueryEngine({
|
|
|
114
114
|
|
|
115
115
|
Engine 创建时读取配置,首次请求时拼接 System Prompt 和工具定义,随后在该 Engine 生命周期内冻结。修改配置文件不会影响已经存在的 Engine,必须关闭并创建新 Engine。
|
|
116
116
|
|
|
117
|
+
配置合并顺序为:内置默认值 → 用户配置 → 项目配置 → `configPath`。用户配置中的 `workspaceRoot` 会被忽略,避免全局配置把所有项目锁定到用户目录;工作区路径由项目配置或显式配置决定。
|
|
118
|
+
|
|
119
|
+
系统提示词还会依次拼接用户级 `~/.apollo/APOLLO.md` 和项目级 `APOLLO.md`(兼容小写 `apollo.md`)。如果项目中没有 Apollo 指令文件,则继续读取通用的 `AGENTS.md` 或 `.apollo/instructions.md`。指令在 Engine 首次请求时冻结。
|
|
120
|
+
|
|
117
121
|
运行中主要变化的是 `messages`,接近上下文上限时会执行语义压缩;稳定的 `system` 和 `tools` 有利于命中模型厂商的 Prompt Cache。
|
|
118
122
|
|
|
119
123
|
## 流式事件
|