@apolloyh/apollo-agent 0.1.4 → 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/cli-input.d.ts +16 -0
- package/dist/cli-input.d.ts.map +1 -0
- package/dist/cli-input.js +68 -0
- 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 +106 -107
- 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/dist/skills/skills.d.ts.map +1 -1
- package/dist/skills/skills.js +8 -4
- package/dist/tools/builtin.js +2 -2
- 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",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type PastedTextBlock = {
|
|
2
|
+
id: number;
|
|
3
|
+
label: string;
|
|
4
|
+
marker: string;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export type InputView = {
|
|
8
|
+
cursorLine: number;
|
|
9
|
+
cursorWidth: number;
|
|
10
|
+
lines: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function createPastedTextBlock(text: string, id: number): PastedTextBlock | undefined;
|
|
13
|
+
export declare function resolvePastedText(value: string, blocks: ReadonlyMap<string, PastedTextBlock>): string;
|
|
14
|
+
export declare function pastedTextPreview(value: string, blocks: ReadonlyMap<string, PastedTextBlock>): string;
|
|
15
|
+
export declare function renderInputView(value: string, cursor: number, width: number, blocks: ReadonlyMap<string, PastedTextBlock>): InputView;
|
|
16
|
+
//# sourceMappingURL=cli-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-input.d.ts","sourceRoot":"","sources":["../src/cli-input.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAMF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAQ3F;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,MAAM,CAErG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,MAAM,CAErG;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAC3C,SAAS,CAyCX"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const MIN_FOLDED_PASTE_LINES = 6;
|
|
2
|
+
const MIN_FOLDED_PASTE_CHARS = 1000;
|
|
3
|
+
const FIRST_MARKER = 0xf0000;
|
|
4
|
+
export function createPastedTextBlock(text, id) {
|
|
5
|
+
const lineCount = text.split("\n").length;
|
|
6
|
+
if (lineCount < MIN_FOLDED_PASTE_LINES && Array.from(text).length < MIN_FOLDED_PASTE_CHARS) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const marker = String.fromCodePoint(FIRST_MARKER + id);
|
|
10
|
+
const size = lineCount > 1 ? `+${lineCount} lines` : `+${Array.from(text).length} chars`;
|
|
11
|
+
return { id, label: `[Pasted text #${id} ${size}]`, marker, text };
|
|
12
|
+
}
|
|
13
|
+
export function resolvePastedText(value, blocks) {
|
|
14
|
+
return Array.from(value, (character) => blocks.get(character)?.text ?? character).join("");
|
|
15
|
+
}
|
|
16
|
+
export function pastedTextPreview(value, blocks) {
|
|
17
|
+
return Array.from(value, (character) => blocks.get(character)?.label ?? character).join("");
|
|
18
|
+
}
|
|
19
|
+
export function renderInputView(value, cursor, width, blocks) {
|
|
20
|
+
const characters = Array.from(value);
|
|
21
|
+
const positions = [];
|
|
22
|
+
const lines = [""];
|
|
23
|
+
let line = 0;
|
|
24
|
+
let column = 0;
|
|
25
|
+
const newLine = () => {
|
|
26
|
+
lines.push("");
|
|
27
|
+
line += 1;
|
|
28
|
+
column = 0;
|
|
29
|
+
};
|
|
30
|
+
const appendCharacter = (character) => {
|
|
31
|
+
if (character === "\n") {
|
|
32
|
+
newLine();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const rendered = character === "\t" ? " " : character;
|
|
36
|
+
for (const outputCharacter of rendered) {
|
|
37
|
+
const characterWidth = displayWidth(outputCharacter);
|
|
38
|
+
if (column > 0 && column + characterWidth > width)
|
|
39
|
+
newLine();
|
|
40
|
+
lines[line] += outputCharacter;
|
|
41
|
+
column += characterWidth;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
characters.forEach((character, index) => {
|
|
45
|
+
const block = blocks.get(character);
|
|
46
|
+
if (block && column > 0 && column + displayWidth(block.label) > width)
|
|
47
|
+
newLine();
|
|
48
|
+
positions[index] = { line, width: column };
|
|
49
|
+
for (const outputCharacter of block?.label ?? character)
|
|
50
|
+
appendCharacter(outputCharacter);
|
|
51
|
+
});
|
|
52
|
+
positions[characters.length] = { line, width: column };
|
|
53
|
+
const cursorPosition = positions[Math.max(0, Math.min(cursor, characters.length))] ?? { line: 0, width: 0 };
|
|
54
|
+
return {
|
|
55
|
+
cursorLine: cursorPosition.line,
|
|
56
|
+
cursorWidth: cursorPosition.width,
|
|
57
|
+
lines,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function displayWidth(text) {
|
|
61
|
+
let length = 0;
|
|
62
|
+
for (const character of text)
|
|
63
|
+
length += isWideCharacter(character) ? 2 : 1;
|
|
64
|
+
return length;
|
|
65
|
+
}
|
|
66
|
+
function isWideCharacter(character) {
|
|
67
|
+
return /[\u1100-\u115F\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF00-\uFF60\uFFE0-\uFFE6]/u.test(character);
|
|
68
|
+
}
|
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
|
@@ -3,7 +3,8 @@ import { stdin, stdout } from "node:process";
|
|
|
3
3
|
import readline from "node:readline/promises";
|
|
4
4
|
import { ASCII_LOGO, PRODUCT } from "./brand.js";
|
|
5
5
|
import { parseArgs } from "./cli-args.js";
|
|
6
|
-
import {
|
|
6
|
+
import { createPastedTextBlock, pastedTextPreview, renderInputView, resolvePastedText, } from "./cli-input.js";
|
|
7
|
+
import { ensureUserEnvFile, ensureUserConfigFile, loadAgentConfig, loadEnvFile, loadLlmConfig, MISSING_AUTH_TOKEN_MESSAGE, } from "./config.js";
|
|
7
8
|
import { pickSpinnerVerb } from "./constants/spinner-verbs.js";
|
|
8
9
|
import { QueryEngine } from "./runtime/query-engine.js";
|
|
9
10
|
import { createCliApprovalProvider } from "./runtime/permissions.js";
|
|
@@ -13,7 +14,6 @@ import { ThoughtFoldManager } from "./thought-fold.js";
|
|
|
13
14
|
import { workflowStatusBar } from "./coordinator/workflow.js";
|
|
14
15
|
import { CliTraceRenderer, stripTerminalEscapes } from "./trace.js";
|
|
15
16
|
let activeCliInput;
|
|
16
|
-
let pinnedCliInput;
|
|
17
17
|
let modalActive = false;
|
|
18
18
|
let resolveModal;
|
|
19
19
|
let modalDone;
|
|
@@ -23,6 +23,7 @@ let activeVerb = "Thinking";
|
|
|
23
23
|
let assistantResponseStarted = false;
|
|
24
24
|
/** Prevent double green/red outcome lines in one user turn */
|
|
25
25
|
let outcomePrintedThisTurn = false;
|
|
26
|
+
let pastedTextSequence = 0;
|
|
26
27
|
const slashCommands = [
|
|
27
28
|
{ command: "/help", usage: "/help", description: "Show slash commands" },
|
|
28
29
|
{ command: "/skill", usage: "/skill", description: "List installed skills" },
|
|
@@ -57,6 +58,7 @@ function filterSlashCommands(buffer) {
|
|
|
57
58
|
async function main() {
|
|
58
59
|
const args = parseArgs(process.argv.slice(2));
|
|
59
60
|
const userEnv = ensureUserEnvFile();
|
|
61
|
+
const userConfig = ensureUserConfigFile();
|
|
60
62
|
loadEnvFile(args.envPath);
|
|
61
63
|
const task = args.task ?? (stdin.isTTY ? undefined : (await readStdin()).trim());
|
|
62
64
|
if (!task && !stdin.isTTY) {
|
|
@@ -97,6 +99,7 @@ async function main() {
|
|
|
97
99
|
const rendererSink = renderer.sink();
|
|
98
100
|
const taskActivity = new TaskActivityLog();
|
|
99
101
|
const inputHistory = [];
|
|
102
|
+
const pendingUserMessages = [];
|
|
100
103
|
// traceSink closes over the runtime, but the runtime is created after this. Hold it in a
|
|
101
104
|
// mutable ref that createRuntime populates, so goal_status events don't hit a TDZ (task mode
|
|
102
105
|
// never reaches the interactive `runtime` binding). Also lets SIGINT close whichever is active.
|
|
@@ -239,6 +242,9 @@ async function main() {
|
|
|
239
242
|
if (userEnv.created) {
|
|
240
243
|
stdout.write(`\nCreated ${shortenHome(userEnv.path)}. Add ANTHROPIC_AUTH_TOKEN there, then restart Apollo.\n`);
|
|
241
244
|
}
|
|
245
|
+
if (userConfig.created) {
|
|
246
|
+
stdout.write(`Created ${shortenHome(userConfig.path)} with default settings.\n`);
|
|
247
|
+
}
|
|
242
248
|
const approvalProvider = createCliApprovalProvider(agentConfig, traceSink, () => yolo, {
|
|
243
249
|
beforePrompt: () => {
|
|
244
250
|
stopLiveStatus("none");
|
|
@@ -272,7 +278,7 @@ async function main() {
|
|
|
272
278
|
await modalDone;
|
|
273
279
|
continue;
|
|
274
280
|
}
|
|
275
|
-
const text = (await readCliLine({ color: traceOptions.color, history: inputHistory })).trim();
|
|
281
|
+
const text = (pendingUserMessages.shift() ?? await readCliLine({ color: traceOptions.color, history: inputHistory })).trim();
|
|
276
282
|
if (!text)
|
|
277
283
|
continue;
|
|
278
284
|
if (text === "/exit" || text === "/quit") {
|
|
@@ -404,12 +410,12 @@ async function main() {
|
|
|
404
410
|
outcomePrintedThisTurn = false;
|
|
405
411
|
activeVerb = pickSpinnerVerb(goalBody);
|
|
406
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.`;
|
|
407
|
-
|
|
413
|
+
const turnInput = startTurnInput(runtime, traceOptions.color, inputHistory, pendingUserMessages);
|
|
408
414
|
try {
|
|
409
415
|
await runGoalLoop(runtime, renderer, thoughtFold, traceOptions, firstPrompt);
|
|
410
416
|
}
|
|
411
417
|
finally {
|
|
412
|
-
|
|
418
|
+
await turnInput.stop();
|
|
413
419
|
}
|
|
414
420
|
continue;
|
|
415
421
|
}
|
|
@@ -529,12 +535,12 @@ async function main() {
|
|
|
529
535
|
continue;
|
|
530
536
|
}
|
|
531
537
|
// ── user turn ──────────────────────────────────────────────
|
|
532
|
-
// Keep
|
|
538
|
+
// Keep a live queue input below streaming output while this turn runs.
|
|
533
539
|
runningTaskCount = 0;
|
|
534
540
|
assistantResponseStarted = false;
|
|
535
541
|
outcomePrintedThisTurn = false;
|
|
536
542
|
activeVerb = pickSpinnerVerb(text);
|
|
537
|
-
|
|
543
|
+
const turnInput = startTurnInput(runtime, traceOptions.color, inputHistory, pendingUserMessages);
|
|
538
544
|
try {
|
|
539
545
|
if (!llmConfig.authToken)
|
|
540
546
|
throw new Error(MISSING_AUTH_TOKEN_MESSAGE);
|
|
@@ -556,7 +562,7 @@ async function main() {
|
|
|
556
562
|
writeAboveActiveInput(runtime.renderWorkflowBar(traceOptions.color));
|
|
557
563
|
}
|
|
558
564
|
void runtime.saveCurrentSession().catch(() => null);
|
|
559
|
-
|
|
565
|
+
await turnInput.stop();
|
|
560
566
|
}
|
|
561
567
|
}
|
|
562
568
|
const sessionId = runtime.getSessionId();
|
|
@@ -950,67 +956,42 @@ function writeAboveActiveInput(text) {
|
|
|
950
956
|
input?.renderAfterExternalOutput();
|
|
951
957
|
}
|
|
952
958
|
}
|
|
953
|
-
function
|
|
954
|
-
if (!stdin.isTTY || !stdout.isTTY
|
|
955
|
-
return;
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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;
|
|
970
985
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
return;
|
|
979
|
-
clear();
|
|
980
|
-
const statusLine = renderLiveStatusLine(color);
|
|
981
|
-
const lines = [
|
|
982
|
-
...(statusLine ? [statusLine] : []),
|
|
983
|
-
renderInputBorder("top", color),
|
|
984
|
-
renderInputPrompt(color),
|
|
985
|
-
renderInputBorder("bottom", color),
|
|
986
|
-
];
|
|
987
|
-
stdout.write(lines.join("\n"));
|
|
988
|
-
renderedLines = lines.length;
|
|
989
|
-
renderedCursorLine = statusLine ? 2 : 1;
|
|
990
|
-
const rowsBelowInput = lines.length - 1 - renderedCursorLine;
|
|
991
|
-
stdout.write(`\x1b[${rowsBelowInput}A\r\x1b[3G`);
|
|
992
|
-
};
|
|
993
|
-
const pinned = {
|
|
994
|
-
clearForExternalOutput: clear,
|
|
995
|
-
renderAfterExternalOutput: render,
|
|
996
|
-
cancelForModal: () => {
|
|
997
|
-
clear();
|
|
998
|
-
if (activeCliInput === pinned)
|
|
999
|
-
activeCliInput = undefined;
|
|
986
|
+
})();
|
|
987
|
+
return {
|
|
988
|
+
stop: async () => {
|
|
989
|
+
accepting = false;
|
|
990
|
+
activeCliInput?.cancelForModal();
|
|
991
|
+
await done;
|
|
992
|
+
stdout.write("\x1b[?25h");
|
|
1000
993
|
},
|
|
1001
994
|
};
|
|
1002
|
-
pinnedCliInput = pinned;
|
|
1003
|
-
activeCliInput = pinned;
|
|
1004
|
-
render();
|
|
1005
|
-
}
|
|
1006
|
-
function stopPinnedCliInput() {
|
|
1007
|
-
const pinned = pinnedCliInput;
|
|
1008
|
-
if (!pinned)
|
|
1009
|
-
return;
|
|
1010
|
-
pinned.clearForExternalOutput();
|
|
1011
|
-
if (activeCliInput === pinned)
|
|
1012
|
-
activeCliInput = undefined;
|
|
1013
|
-
pinnedCliInput = undefined;
|
|
1014
995
|
}
|
|
1015
996
|
async function readCliLine(options) {
|
|
1016
997
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
@@ -1032,6 +1013,7 @@ async function readCliLine(options) {
|
|
|
1032
1013
|
let previousRawMode = false;
|
|
1033
1014
|
let renderedCursorLine = 0;
|
|
1034
1015
|
let pasteBuffer;
|
|
1016
|
+
const pastedTextBlocks = new Map();
|
|
1035
1017
|
const pasteStart = "\x1b[200~";
|
|
1036
1018
|
const pasteEnd = "\x1b[201~";
|
|
1037
1019
|
const complete = (value) => {
|
|
@@ -1041,6 +1023,8 @@ async function readCliLine(options) {
|
|
|
1041
1023
|
done = true;
|
|
1042
1024
|
if (onData)
|
|
1043
1025
|
stdin.off("data", onData);
|
|
1026
|
+
if (stdin.isTTY)
|
|
1027
|
+
stdout.write("\x1b[?25h");
|
|
1044
1028
|
if (stdin.isTTY)
|
|
1045
1029
|
stdout.write("\x1b[?2004l");
|
|
1046
1030
|
if (stdin.isTTY)
|
|
@@ -1055,8 +1039,11 @@ async function readCliLine(options) {
|
|
|
1055
1039
|
if (liveStatusLine)
|
|
1056
1040
|
lines.push(liveStatusLine);
|
|
1057
1041
|
lines.push(renderInputBorder("top", options.color));
|
|
1058
|
-
const view =
|
|
1059
|
-
|
|
1042
|
+
const view = renderInputView(buffer, cursor, inputContentWidth(), pastedTextBlocks);
|
|
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}`));
|
|
1060
1047
|
if (paletteOpen) {
|
|
1061
1048
|
const filtered = filterSlashCommands(buffer);
|
|
1062
1049
|
lines.push(...renderSlashPaletteLines(selected, options.color, filtered, buffer));
|
|
@@ -1088,21 +1075,31 @@ async function readCliLine(options) {
|
|
|
1088
1075
|
const lines = linesForState();
|
|
1089
1076
|
stdout.write(lines.join("\n"));
|
|
1090
1077
|
renderedLines = lines.length;
|
|
1091
|
-
|
|
1078
|
+
const view = renderInputView(buffer, cursor, inputContentWidth(), pastedTextBlocks);
|
|
1079
|
+
renderedCursorLine = (renderLiveStatusLine(options.color) ? 2 : 1) + view.cursorLine;
|
|
1092
1080
|
const rowsBelowInput = lines.length - 1 - renderedCursorLine;
|
|
1093
|
-
const view = inputView(buffer, cursor, Math.max(20, (stdout.columns ?? 100) - 4));
|
|
1094
1081
|
stdout.write(`\x1b[${rowsBelowInput}A\r\x1b[${3 + view.cursorWidth}G`);
|
|
1082
|
+
stdout.write(options.queueMode && !buffer ? "\x1b[?25l" : "\x1b[?25h");
|
|
1095
1083
|
};
|
|
1096
1084
|
const finish = (value) => {
|
|
1097
1085
|
clearRendered();
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1086
|
+
const resolvedValue = resolvePastedText(value, pastedTextBlocks);
|
|
1087
|
+
if (resolvedValue) {
|
|
1088
|
+
if (options.history.at(-1) !== resolvedValue)
|
|
1089
|
+
options.history.push(resolvedValue);
|
|
1101
1090
|
if (options.history.length > 100)
|
|
1102
1091
|
options.history.splice(0, options.history.length - 100);
|
|
1103
|
-
|
|
1092
|
+
const preview = pastedTextPreview(value, pastedTextBlocks).replace(/\n/g, " ↵ ").replace(/\t/g, " ");
|
|
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
|
+
}
|
|
1104
1101
|
}
|
|
1105
|
-
complete(
|
|
1102
|
+
complete(resolvedValue);
|
|
1106
1103
|
};
|
|
1107
1104
|
const cancelForModal = () => {
|
|
1108
1105
|
clearRendered();
|
|
@@ -1168,7 +1165,13 @@ async function readCliLine(options) {
|
|
|
1168
1165
|
finish("/exit");
|
|
1169
1166
|
return;
|
|
1170
1167
|
}
|
|
1168
|
+
if (chunk === "\x1b" && options.onEscape?.()) {
|
|
1169
|
+
finish("");
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1171
1172
|
if (chunk === "\r" || chunk === "\n") {
|
|
1173
|
+
if (options.queueMode && !buffer)
|
|
1174
|
+
return;
|
|
1172
1175
|
finish(buffer);
|
|
1173
1176
|
return;
|
|
1174
1177
|
}
|
|
@@ -1240,11 +1243,11 @@ async function readCliLine(options) {
|
|
|
1240
1243
|
if (historyIndex === options.history.length)
|
|
1241
1244
|
historyDraft = buffer;
|
|
1242
1245
|
historyIndex -= 1;
|
|
1243
|
-
|
|
1246
|
+
setBuffer(options.history[historyIndex] ?? "");
|
|
1244
1247
|
}
|
|
1245
1248
|
else if (chunk === "\x1b[B" && historyIndex < options.history.length) {
|
|
1246
1249
|
historyIndex += 1;
|
|
1247
|
-
|
|
1250
|
+
setBuffer(historyIndex === options.history.length ? historyDraft : options.history[historyIndex] ?? "");
|
|
1248
1251
|
}
|
|
1249
1252
|
cursor = Array.from(buffer).length;
|
|
1250
1253
|
closePalette();
|
|
@@ -1271,13 +1274,31 @@ async function readCliLine(options) {
|
|
|
1271
1274
|
render();
|
|
1272
1275
|
};
|
|
1273
1276
|
const insertText = (text) => {
|
|
1274
|
-
const safeText = stripTerminalEscapes(text)
|
|
1277
|
+
const safeText = stripTerminalEscapes(text)
|
|
1278
|
+
.replace(/\r\n?/g, "\n")
|
|
1279
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
1275
1280
|
const characters = Array.from(buffer);
|
|
1276
|
-
const
|
|
1281
|
+
const block = createPastedTextBlock(safeText, pastedTextSequence + 1);
|
|
1282
|
+
if (block) {
|
|
1283
|
+
pastedTextSequence += 1;
|
|
1284
|
+
pastedTextBlocks.set(block.marker, block);
|
|
1285
|
+
}
|
|
1286
|
+
const inserted = block ? [block.marker] : Array.from(safeText);
|
|
1277
1287
|
characters.splice(cursor, 0, ...inserted);
|
|
1278
1288
|
buffer = characters.join("");
|
|
1279
1289
|
cursor += inserted.length;
|
|
1280
1290
|
};
|
|
1291
|
+
function setBuffer(value) {
|
|
1292
|
+
const block = createPastedTextBlock(value, pastedTextSequence + 1);
|
|
1293
|
+
if (block) {
|
|
1294
|
+
pastedTextSequence += 1;
|
|
1295
|
+
pastedTextBlocks.set(block.marker, block);
|
|
1296
|
+
buffer = block.marker;
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
buffer = value;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1281
1302
|
const handleData = (chunk) => {
|
|
1282
1303
|
let rest = chunk;
|
|
1283
1304
|
while (rest) {
|
|
@@ -1340,10 +1361,6 @@ function endModalInput() {
|
|
|
1340
1361
|
resolveModal?.();
|
|
1341
1362
|
resolveModal = undefined;
|
|
1342
1363
|
modalDone = undefined;
|
|
1343
|
-
if (pinnedCliInput && !activeCliInput) {
|
|
1344
|
-
activeCliInput = pinnedCliInput;
|
|
1345
|
-
pinnedCliInput.renderAfterExternalOutput();
|
|
1346
|
-
}
|
|
1347
1364
|
}
|
|
1348
1365
|
async function askPlainQuestion(prompt) {
|
|
1349
1366
|
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
@@ -1586,6 +1603,10 @@ function compactLine(text, max) {
|
|
|
1586
1603
|
function renderInputPrompt(useColor) {
|
|
1587
1604
|
return useColor ? "\x1b[38;2;96;165;250m›\x1b[0m " : "› ";
|
|
1588
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
|
+
}
|
|
1589
1610
|
function renderLiveStatusLine(useColor) {
|
|
1590
1611
|
if (!liveStatus)
|
|
1591
1612
|
return undefined;
|
|
@@ -1644,30 +1665,8 @@ function truncateDisplay(text, width) {
|
|
|
1644
1665
|
}
|
|
1645
1666
|
return `${result}…`;
|
|
1646
1667
|
}
|
|
1647
|
-
function
|
|
1648
|
-
|
|
1649
|
-
const normalize = (text) => text.replace(/\n/g, " ↵ ").replace(/\t/g, " ");
|
|
1650
|
-
const before = normalize(characters.slice(0, cursor).join(""));
|
|
1651
|
-
const after = normalize(characters.slice(cursor).join(""));
|
|
1652
|
-
if (displayLength(before) >= width) {
|
|
1653
|
-
const visible = takeDisplayEnd(before, width - 1);
|
|
1654
|
-
return { text: `…${visible}`, cursorWidth: 1 + displayLength(visible) };
|
|
1655
|
-
}
|
|
1656
|
-
const remaining = Math.max(0, width - displayLength(before));
|
|
1657
|
-
const visibleAfter = remaining > 0 ? truncateDisplay(after, remaining) : "";
|
|
1658
|
-
return { text: `${before}${visibleAfter}`, cursorWidth: displayLength(before) };
|
|
1659
|
-
}
|
|
1660
|
-
function takeDisplayEnd(text, width) {
|
|
1661
|
-
const result = [];
|
|
1662
|
-
let length = 0;
|
|
1663
|
-
for (const char of Array.from(text).reverse()) {
|
|
1664
|
-
const charWidth = displayLength(char);
|
|
1665
|
-
if (length + charWidth > width)
|
|
1666
|
-
break;
|
|
1667
|
-
result.push(char);
|
|
1668
|
-
length += charWidth;
|
|
1669
|
-
}
|
|
1670
|
-
return result.reverse().join("");
|
|
1668
|
+
function inputContentWidth() {
|
|
1669
|
+
return Math.max(20, Math.min(stdout.columns ?? 100, 100) - 4);
|
|
1671
1670
|
}
|
|
1672
1671
|
function submittedInputLabel(value) {
|
|
1673
1672
|
if (!value.includes("\n"))
|
|
@@ -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
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/skills/skills.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;gBAE7B,MAAM,EAAE,WAAW;IAEhD,OAAO,CAAC,SAAS;IAMX,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAoBtF,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY/E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/skills/skills.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;gBAE7B,MAAM,EAAE,WAAW;IAEhD,OAAO,CAAC,SAAS;IAMX,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAoBtF,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY/E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IA2CvG,OAAO,CAAC,OAAO;YAKD,WAAW;CAuB1B"}
|
package/dist/skills/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { truncate } from "../utils.js";
|
|
4
4
|
export class SkillManager {
|
|
5
5
|
config;
|
|
6
6
|
catalogCache;
|
|
@@ -55,9 +55,13 @@ export class SkillManager {
|
|
|
55
55
|
return truncate(body, 20000);
|
|
56
56
|
}
|
|
57
57
|
async installLocal(sourcePath, requestedName) {
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
58
|
+
// Local skill sources may live outside the active workspace (for example in a
|
|
59
|
+
// package cache or /tmp). Resolve the selected source canonically, while the
|
|
60
|
+
// installation target remains confined to the configured skills directory.
|
|
61
|
+
const requestedSource = path.isAbsolute(sourcePath)
|
|
62
|
+
? sourcePath
|
|
63
|
+
: path.resolve(this.config.workspaceRoot, sourcePath);
|
|
64
|
+
const source = await fs.realpath(requestedSource);
|
|
61
65
|
const stat = await fs.stat(source);
|
|
62
66
|
const skillFile = stat.isDirectory() ? path.join(source, "SKILL.md") : source;
|
|
63
67
|
if (path.basename(skillFile) !== "SKILL.md") {
|
package/dist/tools/builtin.js
CHANGED
|
@@ -331,12 +331,12 @@ export function createBuiltinRegistry(options) {
|
|
|
331
331
|
});
|
|
332
332
|
registry.register({
|
|
333
333
|
name: "skill_install",
|
|
334
|
-
description: "Install a local skill into the first configured skills directory.
|
|
334
|
+
description: "Install a local skill into the first configured skills directory. The source may be outside the workspace and must be a SKILL.md file or a directory containing SKILL.md.",
|
|
335
335
|
risk: "high",
|
|
336
336
|
input_schema: {
|
|
337
337
|
type: "object",
|
|
338
338
|
properties: {
|
|
339
|
-
sourcePath: { type: "string", description: "
|
|
339
|
+
sourcePath: { type: "string", description: "Local path, including outside the workspace, to SKILL.md or a directory containing SKILL.md." },
|
|
340
340
|
name: { type: "string", description: "Optional installed skill name." },
|
|
341
341
|
},
|
|
342
342
|
required: ["sourcePath"],
|
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
|
## 流式事件
|