@apolloyh/apollo-agent 0.1.5 → 0.1.6
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/README.md +11 -3
- package/dist/brand.d.ts +1 -1
- package/dist/brand.js +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +68 -18
- package/dist/index.js +70 -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,17 @@ 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`. Project overrides live at
|
|
39
|
+
`.apollo/config.json`; Apollo also reads the legacy `agent.config.json` during migration.
|
|
40
|
+
Configuration layers as built-in defaults → user config → project config → `--config`.
|
|
41
|
+
Set `systemPrompt` in either config to append stable custom system instructions; restart Apollo to reload them.
|
|
42
|
+
|
|
43
|
+
Apollo also loads `~/.apollo/APOLLO.md` followed by the project `APOLLO.md` (lowercase
|
|
44
|
+
`apollo.md` is accepted). If no project Apollo file exists, the standard `AGENTS.md` and
|
|
45
|
+
`.apollo/instructions.md` paths remain supported. These instructions are frozen for the session.
|
|
46
|
+
|
|
47
|
+
While Apollo is working, type and press Enter to queue another user message. Queued messages
|
|
48
|
+
run in FIFO order after the current turn; press Esc to interrupt the current turn safely.
|
|
41
49
|
|
|
42
50
|
Commands: `/help` · `/skills` · `/agents` · `/sessions` · `/resume` · `/verbose` · `/stream` · `/clear` · `/exit`
|
|
43
51
|
|
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.6";
|
|
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.6",
|
|
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,10 @@ 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
|
+
};
|
|
9
13
|
export declare function loadAgentConfig(configPath?: string): Promise<AgentConfig>;
|
|
10
14
|
/**
|
|
11
15
|
* 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":"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;
|
|
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;AAoBpH,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,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
|
@@ -57,6 +57,14 @@ ANTHROPIC_AUTH_TOKEN=
|
|
|
57
57
|
# ANTHROPIC_BASE_URL=https://api.anthropic.com
|
|
58
58
|
# ANTHROPIC_MODEL=claude-haiku-4-5
|
|
59
59
|
`;
|
|
60
|
+
const USER_CONFIG_TEMPLATE = `${JSON.stringify({
|
|
61
|
+
systemPrompt: "",
|
|
62
|
+
maxTurns: defaultConfig.maxTurns,
|
|
63
|
+
context: defaultConfig.context,
|
|
64
|
+
permissions: defaultConfig.permissions,
|
|
65
|
+
skills: defaultConfig.skills,
|
|
66
|
+
mcpServers: defaultConfig.mcpServers,
|
|
67
|
+
}, null, 2)}\n`;
|
|
60
68
|
export function ensureUserEnvFile() {
|
|
61
69
|
const directory = path.join(os.homedir(), ".apollo");
|
|
62
70
|
const envPath = path.join(directory, ".env");
|
|
@@ -73,27 +81,69 @@ export function ensureUserEnvFile() {
|
|
|
73
81
|
throw error;
|
|
74
82
|
}
|
|
75
83
|
}
|
|
84
|
+
export function ensureUserConfigFile() {
|
|
85
|
+
const directory = path.join(os.homedir(), ".apollo");
|
|
86
|
+
const configPath = path.join(directory, "config.json");
|
|
87
|
+
if (fsSync.existsSync(configPath))
|
|
88
|
+
return { path: configPath, created: false };
|
|
89
|
+
fsSync.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
90
|
+
try {
|
|
91
|
+
fsSync.writeFileSync(configPath, USER_CONFIG_TEMPLATE, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
92
|
+
return { path: configPath, created: true };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code === "EEXIST")
|
|
96
|
+
return { path: configPath, created: false };
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
76
100
|
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 = {
|
|
101
|
+
const userConfigPath = path.join(os.homedir(), ".apollo", "config.json");
|
|
102
|
+
const projectConfigPath = [DEFAULT_CONFIG_PATH, LEGACY_CONFIG_PATH].find(fsSync.existsSync);
|
|
103
|
+
const layers = [
|
|
104
|
+
...(fsSync.existsSync(userConfigPath) ? [{ file: userConfigPath, projectScoped: false }] : []),
|
|
105
|
+
...(projectConfigPath ? [{ file: projectConfigPath, projectScoped: true }] : []),
|
|
106
|
+
...(configPath ? [{ file: configPath, projectScoped: true }] : []),
|
|
107
|
+
];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
let config = {
|
|
88
110
|
...defaultConfig,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
agents,
|
|
111
|
+
workspaceRoot: path.resolve(defaultConfig.workspaceRoot),
|
|
112
|
+
context: { ...defaultConfig.context },
|
|
113
|
+
permissions: { ...defaultConfig.permissions },
|
|
114
|
+
skills: { ...defaultConfig.skills },
|
|
115
|
+
mcpServers: { ...defaultConfig.mcpServers },
|
|
116
|
+
agents: [...defaultConfig.agents],
|
|
96
117
|
};
|
|
118
|
+
for (const layer of layers) {
|
|
119
|
+
const resolvedConfigPath = path.resolve(layer.file);
|
|
120
|
+
if (seen.has(resolvedConfigPath))
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(resolvedConfigPath);
|
|
123
|
+
const raw = await fs.readFile(resolvedConfigPath, "utf8");
|
|
124
|
+
const parsed = JSON.parse(raw);
|
|
125
|
+
const { workers: _workers, ...overrides } = parsed;
|
|
126
|
+
if (typeof overrides.systemPrompt === "string" && !overrides.systemPrompt.trim())
|
|
127
|
+
delete overrides.systemPrompt;
|
|
128
|
+
if (typeof overrides.modeInstructions === "string" && !overrides.modeInstructions.trim())
|
|
129
|
+
delete overrides.modeInstructions;
|
|
130
|
+
const configDir = path.dirname(resolvedConfigPath);
|
|
131
|
+
const baseDir = path.basename(configDir) === ".apollo" ? path.dirname(configDir) : configDir;
|
|
132
|
+
const agents = normalizeAgents(parsed.agents ?? parsed.workers) ?? config.agents;
|
|
133
|
+
const workspaceRoot = layer.projectScoped && parsed.workspaceRoot !== undefined
|
|
134
|
+
? path.resolve(baseDir, parsed.workspaceRoot)
|
|
135
|
+
: config.workspaceRoot;
|
|
136
|
+
config = {
|
|
137
|
+
...config,
|
|
138
|
+
...overrides,
|
|
139
|
+
workspaceRoot,
|
|
140
|
+
context: { ...config.context, ...parsed.context },
|
|
141
|
+
permissions: { ...config.permissions, ...parsed.permissions },
|
|
142
|
+
skills: { ...config.skills, ...parsed.skills },
|
|
143
|
+
mcpServers: { ...config.mcpServers, ...parsed.mcpServers },
|
|
144
|
+
agents,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
97
147
|
validateAgentConfig(config);
|
|
98
148
|
return config;
|
|
99
149
|
}
|
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, 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,7 @@ 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
62
|
loadEnvFile(args.envPath);
|
|
63
63
|
const task = args.task ?? (stdin.isTTY ? undefined : (await readStdin()).trim());
|
|
64
64
|
if (!task && !stdin.isTTY) {
|
|
@@ -99,6 +99,7 @@ async function main() {
|
|
|
99
99
|
const rendererSink = renderer.sink();
|
|
100
100
|
const taskActivity = new TaskActivityLog();
|
|
101
101
|
const inputHistory = [];
|
|
102
|
+
const pendingUserMessages = [];
|
|
102
103
|
// traceSink closes over the runtime, but the runtime is created after this. Hold it in a
|
|
103
104
|
// mutable ref that createRuntime populates, so goal_status events don't hit a TDZ (task mode
|
|
104
105
|
// never reaches the interactive `runtime` binding). Also lets SIGINT close whichever is active.
|
|
@@ -241,6 +242,9 @@ async function main() {
|
|
|
241
242
|
if (userEnv.created) {
|
|
242
243
|
stdout.write(`\nCreated ${shortenHome(userEnv.path)}. Add ANTHROPIC_AUTH_TOKEN there, then restart Apollo.\n`);
|
|
243
244
|
}
|
|
245
|
+
if (userConfig.created) {
|
|
246
|
+
stdout.write(`Created ${shortenHome(userConfig.path)} with default settings.\n`);
|
|
247
|
+
}
|
|
244
248
|
const approvalProvider = createCliApprovalProvider(agentConfig, traceSink, () => yolo, {
|
|
245
249
|
beforePrompt: () => {
|
|
246
250
|
stopLiveStatus("none");
|
|
@@ -274,7 +278,7 @@ async function main() {
|
|
|
274
278
|
await modalDone;
|
|
275
279
|
continue;
|
|
276
280
|
}
|
|
277
|
-
const text = (await readCliLine({ color: traceOptions.color, history: inputHistory })).trim();
|
|
281
|
+
const text = (pendingUserMessages.shift() ?? await readCliLine({ color: traceOptions.color, history: inputHistory })).trim();
|
|
278
282
|
if (!text)
|
|
279
283
|
continue;
|
|
280
284
|
if (text === "/exit" || text === "/quit") {
|
|
@@ -406,12 +410,12 @@ async function main() {
|
|
|
406
410
|
outcomePrintedThisTurn = false;
|
|
407
411
|
activeVerb = pickSpinnerVerb(goalBody);
|
|
408
412
|
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
|
-
|
|
413
|
+
const turnInput = startTurnInput(runtime, traceOptions.color, inputHistory, pendingUserMessages);
|
|
410
414
|
try {
|
|
411
415
|
await runGoalLoop(runtime, renderer, thoughtFold, traceOptions, firstPrompt);
|
|
412
416
|
}
|
|
413
417
|
finally {
|
|
414
|
-
|
|
418
|
+
await turnInput.stop();
|
|
415
419
|
}
|
|
416
420
|
continue;
|
|
417
421
|
}
|
|
@@ -531,12 +535,12 @@ async function main() {
|
|
|
531
535
|
continue;
|
|
532
536
|
}
|
|
533
537
|
// ── user turn ──────────────────────────────────────────────
|
|
534
|
-
// Keep
|
|
538
|
+
// Keep a live queue input below streaming output while this turn runs.
|
|
535
539
|
runningTaskCount = 0;
|
|
536
540
|
assistantResponseStarted = false;
|
|
537
541
|
outcomePrintedThisTurn = false;
|
|
538
542
|
activeVerb = pickSpinnerVerb(text);
|
|
539
|
-
|
|
543
|
+
const turnInput = startTurnInput(runtime, traceOptions.color, inputHistory, pendingUserMessages);
|
|
540
544
|
try {
|
|
541
545
|
if (!llmConfig.authToken)
|
|
542
546
|
throw new Error(MISSING_AUTH_TOKEN_MESSAGE);
|
|
@@ -558,7 +562,7 @@ async function main() {
|
|
|
558
562
|
writeAboveActiveInput(runtime.renderWorkflowBar(traceOptions.color));
|
|
559
563
|
}
|
|
560
564
|
void runtime.saveCurrentSession().catch(() => null);
|
|
561
|
-
|
|
565
|
+
await turnInput.stop();
|
|
562
566
|
}
|
|
563
567
|
}
|
|
564
568
|
const sessionId = runtime.getSessionId();
|
|
@@ -952,67 +956,42 @@ function writeAboveActiveInput(text) {
|
|
|
952
956
|
input?.renderAfterExternalOutput();
|
|
953
957
|
}
|
|
954
958
|
}
|
|
955
|
-
function
|
|
956
|
-
if (!stdin.isTTY || !stdout.isTTY
|
|
957
|
-
return;
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
959
|
+
function startTurnInput(runtime, color, history, pendingMessages) {
|
|
960
|
+
if (!stdin.isTTY || !stdout.isTTY)
|
|
961
|
+
return { stop: async () => undefined };
|
|
962
|
+
let accepting = true;
|
|
963
|
+
const done = (async () => {
|
|
964
|
+
while (accepting) {
|
|
965
|
+
if (modalActive && modalDone) {
|
|
966
|
+
await modalDone;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
const value = await readCliLine({
|
|
970
|
+
color,
|
|
971
|
+
history,
|
|
972
|
+
queueMode: true,
|
|
973
|
+
onEscape: () => {
|
|
974
|
+
const cancelled = runtime.cancelCurrentTurn();
|
|
975
|
+
if (cancelled)
|
|
976
|
+
accepting = false;
|
|
977
|
+
return cancelled;
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
const queued = value.trim();
|
|
981
|
+
if (queued)
|
|
982
|
+
pendingMessages.push(queued);
|
|
983
|
+
if (!accepting)
|
|
984
|
+
break;
|
|
972
985
|
}
|
|
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;
|
|
986
|
+
})();
|
|
987
|
+
return {
|
|
988
|
+
stop: async () => {
|
|
989
|
+
accepting = false;
|
|
990
|
+
activeCliInput?.cancelForModal();
|
|
991
|
+
await done;
|
|
992
|
+
stdout.write("\x1b[?25h");
|
|
1002
993
|
},
|
|
1003
994
|
};
|
|
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
995
|
}
|
|
1017
996
|
async function readCliLine(options) {
|
|
1018
997
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
@@ -1044,6 +1023,8 @@ async function readCliLine(options) {
|
|
|
1044
1023
|
done = true;
|
|
1045
1024
|
if (onData)
|
|
1046
1025
|
stdin.off("data", onData);
|
|
1026
|
+
if (stdin.isTTY)
|
|
1027
|
+
stdout.write("\x1b[?25h");
|
|
1047
1028
|
if (stdin.isTTY)
|
|
1048
1029
|
stdout.write("\x1b[?2004l");
|
|
1049
1030
|
if (stdin.isTTY)
|
|
@@ -1059,8 +1040,10 @@ async function readCliLine(options) {
|
|
|
1059
1040
|
lines.push(liveStatusLine);
|
|
1060
1041
|
lines.push(renderInputBorder("top", options.color));
|
|
1061
1042
|
const view = renderInputView(buffer, cursor, inputContentWidth(), pastedTextBlocks);
|
|
1062
|
-
|
|
1063
|
-
lines.push(
|
|
1043
|
+
const idleQueue = options.queueMode && !buffer;
|
|
1044
|
+
lines.push(`${renderInputPrompt(options.color)}${idleQueue ? renderTurnInputHint(options.color) : (view.lines[0] ?? "")}`);
|
|
1045
|
+
if (!idleQueue)
|
|
1046
|
+
lines.push(...view.lines.slice(1).map((line) => ` ${line}`));
|
|
1064
1047
|
if (paletteOpen) {
|
|
1065
1048
|
const filtered = filterSlashCommands(buffer);
|
|
1066
1049
|
lines.push(...renderSlashPaletteLines(selected, options.color, filtered, buffer));
|
|
@@ -1096,6 +1079,7 @@ async function readCliLine(options) {
|
|
|
1096
1079
|
renderedCursorLine = (renderLiveStatusLine(options.color) ? 2 : 1) + view.cursorLine;
|
|
1097
1080
|
const rowsBelowInput = lines.length - 1 - renderedCursorLine;
|
|
1098
1081
|
stdout.write(`\x1b[${rowsBelowInput}A\r\x1b[${3 + view.cursorWidth}G`);
|
|
1082
|
+
stdout.write(options.queueMode && !buffer ? "\x1b[?25l" : "\x1b[?25h");
|
|
1099
1083
|
};
|
|
1100
1084
|
const finish = (value) => {
|
|
1101
1085
|
clearRendered();
|
|
@@ -1106,7 +1090,14 @@ async function readCliLine(options) {
|
|
|
1106
1090
|
if (options.history.length > 100)
|
|
1107
1091
|
options.history.splice(0, options.history.length - 100);
|
|
1108
1092
|
const preview = pastedTextPreview(value, pastedTextBlocks).replace(/\n/g, " ↵ ").replace(/\t/g, " ");
|
|
1109
|
-
|
|
1093
|
+
const submitted = preview === value ? submittedInputLabel(resolvedValue) : preview;
|
|
1094
|
+
if (options.queueMode) {
|
|
1095
|
+
const label = options.color ? "\x1b[90m• Queued\x1b[0m" : "• Queued";
|
|
1096
|
+
stdout.write(`${label} ${submitted}\n`);
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
stdout.write(`${renderInputBorder("top", options.color)}\n${renderInputPrompt(options.color)}${submitted}\n${renderInputBorder("bottom", options.color)}\n`);
|
|
1100
|
+
}
|
|
1110
1101
|
}
|
|
1111
1102
|
complete(resolvedValue);
|
|
1112
1103
|
};
|
|
@@ -1174,7 +1165,13 @@ async function readCliLine(options) {
|
|
|
1174
1165
|
finish("/exit");
|
|
1175
1166
|
return;
|
|
1176
1167
|
}
|
|
1168
|
+
if (chunk === "\x1b" && options.onEscape?.()) {
|
|
1169
|
+
finish("");
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1177
1172
|
if (chunk === "\r" || chunk === "\n") {
|
|
1173
|
+
if (options.queueMode && !buffer)
|
|
1174
|
+
return;
|
|
1178
1175
|
finish(buffer);
|
|
1179
1176
|
return;
|
|
1180
1177
|
}
|
|
@@ -1364,10 +1361,6 @@ function endModalInput() {
|
|
|
1364
1361
|
resolveModal?.();
|
|
1365
1362
|
resolveModal = undefined;
|
|
1366
1363
|
modalDone = undefined;
|
|
1367
|
-
if (pinnedCliInput && !activeCliInput) {
|
|
1368
|
-
activeCliInput = pinnedCliInput;
|
|
1369
|
-
pinnedCliInput.renderAfterExternalOutput();
|
|
1370
|
-
}
|
|
1371
1364
|
}
|
|
1372
1365
|
async function askPlainQuestion(prompt) {
|
|
1373
1366
|
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
@@ -1610,6 +1603,10 @@ function compactLine(text, max) {
|
|
|
1610
1603
|
function renderInputPrompt(useColor) {
|
|
1611
1604
|
return useColor ? "\x1b[38;2;96;165;250m›\x1b[0m " : "› ";
|
|
1612
1605
|
}
|
|
1606
|
+
function renderTurnInputHint(useColor) {
|
|
1607
|
+
const hint = "Type to queue a message · Esc to interrupt";
|
|
1608
|
+
return useColor ? `\x1b[90m${hint}\x1b[0m` : hint;
|
|
1609
|
+
}
|
|
1613
1610
|
function renderLiveStatusLine(useColor) {
|
|
1614
1611
|
if (!liveStatus)
|
|
1615
1612
|
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
|
## 流式事件
|