@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.
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "workspaceRoot": ".",
3
3
  "systemPrompt": "",
4
+ "modeInstructions": "",
4
5
  "maxTurns": 40,
5
6
  "context": {
6
7
  "maxChars": 262144,
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
- 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.
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.5";
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.5",
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.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE,wDAAwD;AACxD,eAAO,MAAM,cAAc,EAAE,cAAc,EA4B1C,CAAC;AAsBF,eAAO,MAAM,0BAA0B,oHAC4E,CAAC;AAWpH,wBAAgB,iBAAiB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAatE;AAED,wBAAsB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0B/E;AAiBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAwB3F;AAYD,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAkCrF"}
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 discoveredPath = configPath ?? [DEFAULT_CONFIG_PATH, LEGACY_CONFIG_PATH].find(fsSync.existsSync);
78
- if (!discoveredPath) {
79
- return { ...defaultConfig, workspaceRoot: path.resolve(defaultConfig.workspaceRoot) };
80
- }
81
- const raw = await fs.readFile(discoveredPath, "utf8");
82
- const parsed = JSON.parse(raw);
83
- const resolvedConfigPath = path.resolve(discoveredPath);
84
- const configDir = path.dirname(resolvedConfigPath);
85
- const baseDir = path.basename(configDir) === ".apollo" ? path.dirname(configDir) : configDir;
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
- ...parsed,
90
- workspaceRoot: path.resolve(baseDir, parsed.workspaceRoot ?? defaultConfig.workspaceRoot),
91
- context: { ...defaultConfig.context, ...parsed.context },
92
- permissions: { ...defaultConfig.permissions, ...parsed.permissions },
93
- skills: { ...defaultConfig.skills, ...parsed.skills },
94
- mcpServers: parsed.mcpServers ?? defaultConfig.mcpServers,
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
- startPinnedCliInput(traceOptions.color);
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
- stopPinnedCliInput();
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 an empty input chrome pinned below streaming output while this turn runs.
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
- startPinnedCliInput(traceOptions.color);
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
- stopPinnedCliInput();
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 startPinnedCliInput(color) {
956
- if (!stdin.isTTY || !stdout.isTTY || modalActive)
957
- return;
958
- stopPinnedCliInput();
959
- let renderedLines = 0;
960
- let renderedCursorLine = 0;
961
- const clear = () => {
962
- if (renderedLines === 0)
963
- return;
964
- if (renderedCursorLine > 0)
965
- stdout.write(`\x1b[${renderedCursorLine}A\r`);
966
- else
967
- stdout.write("\r");
968
- for (let index = 0; index < renderedLines; index += 1) {
969
- stdout.write("\x1b[2K");
970
- if (index < renderedLines - 1)
971
- stdout.write("\x1b[1B\r");
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
- if (renderedLines > 1)
974
- stdout.write(`\x1b[${renderedLines - 1}A\r`);
975
- renderedLines = 0;
976
- renderedCursorLine = 0;
977
- };
978
- const render = () => {
979
- if (modalActive || activeCliInput !== pinnedCliInput)
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
- lines.push(`${renderInputPrompt(options.color)}${view.lines[0] ?? ""}`);
1063
- lines.push(...view.lines.slice(1).map((line) => ` ${line}`));
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
- stdout.write(`${renderInputBorder("top", options.color)}\n${renderInputPrompt(options.color)}${preview === value ? submittedInputLabel(resolvedValue) : preview}\n${renderInputBorder("bottom", options.color)}\n`);
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
- /** Same spirit as CLAUDE.md / AGENTS.md discovery. */
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":"AAOA,sDAAsD;AACtD,eAAO,MAAM,yBAAyB,kGAM5B,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,GACf,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAgBrC"}
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
- "CLAUDE.md",
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
- return {
24
- path: rel,
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
- return null;
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
- /** Claude Code–style project instructions (APOLLO.md / CLAUDE.md / AGENTS.md) */
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,iFAAiF;IACjF,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"}
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 come from \`${proj.path}\` in the workspace. Follow them for this project.
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 配置路径;默认发现 `.apollo/config.json`,兼容旧 `agent.config.json` |
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
  ## 流式事件
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apolloyh/apollo-agent",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Apollo — your personal agent. Answer first, act when needed. Claude Code–style Task subagents.",
6
6
  "bin": {