@oh-my-pi/pi-coding-agent 14.6.6 → 14.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +7 -4
- package/examples/sdk/README.md +1 -1
- package/package.json +7 -7
- package/src/autoresearch/index.ts +48 -44
- package/src/cli/read-cli.ts +58 -0
- package/src/cli.ts +1 -0
- package/src/commands/read.ts +40 -0
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/analysis/conventional.ts +1 -1
- package/src/commit/analysis/summary.ts +1 -1
- package/src/commit/changelog/generate.ts +1 -1
- package/src/commit/map-reduce/map-phase.ts +1 -1
- package/src/commit/map-reduce/reduce-phase.ts +1 -1
- package/src/config/settings-schema.ts +39 -0
- package/src/edit/line-hash.ts +34 -4
- package/src/edit/modes/hashline.ts +201 -6
- package/src/edit/streaming.ts +4 -1
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/runner.ts +3 -3
- package/src/extensibility/extensions/types.ts +4 -4
- package/src/main.ts +3 -3
- package/src/memories/index.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +1 -1
- package/src/modes/components/read-tool-group.ts +4 -9
- package/src/modes/components/tool-execution.ts +4 -0
- package/src/modes/controllers/event-controller.ts +2 -0
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/modes/utils/context-usage.ts +12 -5
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/project-prompt.md +36 -0
- package/src/prompts/system/system-prompt.md +0 -29
- package/src/prompts/tools/github.md +1 -0
- package/src/prompts/tools/read.md +15 -14
- package/src/sdk.ts +29 -28
- package/src/session/agent-session.ts +20 -12
- package/src/session/compaction/branch-summarization.ts +1 -1
- package/src/session/compaction/compaction.ts +3 -3
- package/src/session/session-dump-format.ts +10 -5
- package/src/session/streaming-output.ts +1 -1
- package/src/system-prompt.ts +35 -3
- package/src/task/executor.ts +4 -3
- package/src/tools/fetch.ts +4 -4
- package/src/tools/gh.ts +187 -0
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/path-utils.ts +11 -0
- package/src/tools/read.ts +388 -204
- package/src/tools/search.ts +1 -1
- package/src/tools/sqlite-reader.ts +1 -1
- package/src/utils/commit-message-generator.ts +1 -1
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/workspace-tree.ts +396 -0
|
@@ -6,29 +6,30 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
|
|
|
6
6
|
- For URLs, `read` fetches the page and returns clean extracted text/markdown by default (reader-mode). It handles HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs, etc. You **SHOULD** reach for `read` — not a browser/puppeteer tool — for fetching and inspecting web content.
|
|
7
7
|
|
|
8
8
|
## Parameters
|
|
9
|
-
- `path` — file path or URL (required)
|
|
10
|
-
- `sel` — optional selector for line ranges or raw mode
|
|
9
|
+
- `path` — file path or URL (required). Append `:<sel>` for line ranges or raw mode (for example `src/foo.ts:50-200` or `src/foo.ts:raw`).
|
|
11
10
|
- `timeout` — seconds, for URLs only
|
|
12
11
|
|
|
13
12
|
## Selectors
|
|
14
13
|
|
|
15
|
-
|`
|
|
14
|
+
|`path` suffix|Behavior|
|
|
16
15
|
|---|---|
|
|
17
|
-
|_(omitted)_|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
|_(omitted)_|For parseable code files, return a structural summary. Otherwise read from the start (up to {{DEFAULT_LIMIT}} lines).|
|
|
17
|
+
|`:50`|Read from line 50 onward|
|
|
18
|
+
|`:50-200`|Read lines 50-200|
|
|
19
|
+
|`:50+150`|Read 150 lines starting at line 50|
|
|
20
|
+
|`:20+1`|Read exactly one line|
|
|
21
|
+
|`:raw`|Read verbatim text without anchors or summarization|
|
|
22
22
|
|
|
23
23
|
# Filesystem
|
|
24
24
|
- Reading a directory path returns a list of dirents.
|
|
25
25
|
{{#if IS_HL_MODE}}
|
|
26
|
-
- Reading a file returns lines prefixed with anchors (line+hash): `41th|def alpha():`
|
|
26
|
+
- Reading a file with an explicit selector returns lines prefixed with anchors (line+hash): `41th|def alpha():`
|
|
27
27
|
{{else}}
|
|
28
28
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
29
|
-
- Reading a file returns lines prefixed with line numbers: `41|def alpha():`
|
|
29
|
+
- Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`
|
|
30
30
|
{{/if}}
|
|
31
31
|
{{/if}}
|
|
32
|
+
- Reading a parseable code file without a selector returns a structural summary with signatures/declarations kept and large bodies collapsed to `…`. Use `:raw` or an explicit range such as `:1-9999` for verbatim content.
|
|
32
33
|
|
|
33
34
|
# Inspection
|
|
34
35
|
|
|
@@ -36,7 +37,7 @@ Extracts text from PDF, Word, PowerPoint, Excel, RTF, EPUB, and Jupyter notebook
|
|
|
36
37
|
|
|
37
38
|
# Directories & Archives
|
|
38
39
|
|
|
39
|
-
Directories and archive roots return a list of entries. Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read contents.
|
|
40
|
+
Directories and archive roots return a list of entries. Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read contents, and append a selector to the archive entry such as `archive.zip:dir/file.ts:50-60`.
|
|
40
41
|
|
|
41
42
|
# SQLite Databases
|
|
42
43
|
|
|
@@ -50,13 +51,13 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
|
|
|
50
51
|
|
|
51
52
|
# URLs
|
|
52
53
|
|
|
53
|
-
Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, PDFs at URLs, and similar text-based resources. Returns clean reader-mode text/markdown — no browser required. Use
|
|
54
|
+
Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, PDFs at URLs, and similar text-based resources. Returns clean reader-mode text/markdown — no browser required. Use a `:raw` suffix for untouched HTML; `timeout` to override the default request timeout. URL line selectors require the `L` form, for example `https://example.com/page:L50-L60`.
|
|
54
55
|
</instruction>
|
|
55
56
|
|
|
56
57
|
<critical>
|
|
57
58
|
- You **MUST** use `read` for every file, directory, archive, and URL read. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, and `wget` are **FORBIDDEN** for inspection — any such Bash call is a bug, regardless of how short or convenient it looks.
|
|
58
59
|
- You **MUST** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser if `read` fails to deliver reasonable content.
|
|
59
60
|
- You **MUST** always include the `path` parameter — never call `read` with an empty argument object `{}`.
|
|
60
|
-
- For specific line ranges,
|
|
61
|
-
- You **MAY** use
|
|
61
|
+
- For specific line ranges, append the selector to `path` (e.g. `path="src/foo.ts:50-200"`, `path="src/foo.ts:50+150"`) — do **NOT** reach for `sed -n`, `awk NR`, or `head`/`tail` pipelines.
|
|
62
|
+
- You **MAY** use path suffix selectors with URL reads; the tool paginates cached fetched output.
|
|
62
63
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -102,6 +102,7 @@ import { closeAllConnections } from "./ssh/connection-manager";
|
|
|
102
102
|
import { unmountAll } from "./ssh/sshfs-mount";
|
|
103
103
|
import {
|
|
104
104
|
type AgentsMdSearch,
|
|
105
|
+
type BuildSystemPromptResult,
|
|
105
106
|
buildAgentsMdSearch,
|
|
106
107
|
buildSystemPrompt as buildSystemPromptInternal,
|
|
107
108
|
buildSystemPromptToolMetadata,
|
|
@@ -140,6 +141,7 @@ import { wrapToolWithMetaNotice } from "./tools/output-meta";
|
|
|
140
141
|
import { queueResolveHandler } from "./tools/resolve";
|
|
141
142
|
import { EventBus } from "./utils/event-bus";
|
|
142
143
|
import { buildNamedToolChoice } from "./utils/tool-choice";
|
|
144
|
+
import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
143
145
|
|
|
144
146
|
// Types
|
|
145
147
|
export interface CreateAgentSessionOptions {
|
|
@@ -165,8 +167,8 @@ export interface CreateAgentSessionOptions {
|
|
|
165
167
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
166
168
|
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
167
169
|
|
|
168
|
-
/** System prompt.
|
|
169
|
-
systemPrompt?: string | ((defaultPrompt: string) => string);
|
|
170
|
+
/** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
|
|
171
|
+
systemPrompt?: string[] | ((defaultPrompt: string[]) => string[]);
|
|
170
172
|
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
171
173
|
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
172
174
|
providerSessionId?: string;
|
|
@@ -270,6 +272,7 @@ export type { Skill } from "./extensibility/skills";
|
|
|
270
272
|
export type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
271
273
|
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp";
|
|
272
274
|
export type { Tool } from "./tools";
|
|
275
|
+
export { buildDirectoryTree, buildWorkspaceTree, type DirectoryTree, type WorkspaceTree } from "./workspace-tree";
|
|
273
276
|
|
|
274
277
|
export {
|
|
275
278
|
// Individual tool classes (for custom usage)
|
|
@@ -399,9 +402,12 @@ export interface BuildSystemPromptOptions {
|
|
|
399
402
|
}
|
|
400
403
|
|
|
401
404
|
/**
|
|
402
|
-
* Build the default system prompt.
|
|
405
|
+
* Build the default provider-facing system prompt blocks.
|
|
406
|
+
*
|
|
407
|
+
* The returned `systemPrompt` preserves the stable harness prompt and dynamic project context
|
|
408
|
+
* as separate entries so providers can cache prompt prefixes without concatenating blocks.
|
|
403
409
|
*/
|
|
404
|
-
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<
|
|
410
|
+
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
|
|
405
411
|
return await buildSystemPromptInternal({
|
|
406
412
|
cwd: options.cwd,
|
|
407
413
|
skills: options.skills,
|
|
@@ -652,7 +658,7 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
|
|
|
652
658
|
* const { session } = await createAgentSession({
|
|
653
659
|
* model: myModel,
|
|
654
660
|
* getApiKey: async () => Bun.env.MY_KEY,
|
|
655
|
-
* systemPrompt: 'You are helpful.',
|
|
661
|
+
* systemPrompt: ['You are helpful.'],
|
|
656
662
|
* tools: codingTools({ cwd: getProjectDir() }),
|
|
657
663
|
* skills: [],
|
|
658
664
|
* sessionManager: SessionManager.inMemory(),
|
|
@@ -680,6 +686,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
680
686
|
// (~200ms on large repos) and only needs `cwd`, so it can overlap with everything that follows.
|
|
681
687
|
const agentsMdSearchPromise: Promise<AgentsMdSearch> = logger.time("buildAgentsMdSearch", buildAgentsMdSearch, cwd);
|
|
682
688
|
agentsMdSearchPromise.catch(() => {});
|
|
689
|
+
const workspaceTreePromise: Promise<WorkspaceTree> = logger.time("buildWorkspaceTree", buildWorkspaceTree, cwd);
|
|
690
|
+
workspaceTreePromise.catch(() => {});
|
|
683
691
|
|
|
684
692
|
// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
|
|
685
693
|
// at their respective consumer sites. Their work can overlap with model resolution, secret loading,
|
|
@@ -1330,7 +1338,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1330
1338
|
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
1331
1339
|
const eagerTasks = settings.get("task.eager");
|
|
1332
1340
|
const intentField = settings.get("tools.intentTracing") || $flag("PI_INTENT_TRACING") ? INTENT_FIELD : undefined;
|
|
1333
|
-
const rebuildSystemPrompt = async (
|
|
1341
|
+
const rebuildSystemPrompt = async (
|
|
1342
|
+
toolNames: string[],
|
|
1343
|
+
tools: Map<string, AgentTool>,
|
|
1344
|
+
): Promise<BuildSystemPromptResult> => {
|
|
1334
1345
|
toolContextStore.setToolNames(toolNames);
|
|
1335
1346
|
const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
|
|
1336
1347
|
const discoverableMCPSummary = summarizeDiscoverableMCPTools(discoverableMCPTools);
|
|
@@ -1380,33 +1391,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1380
1391
|
eagerTasks,
|
|
1381
1392
|
secretsEnabled,
|
|
1382
1393
|
agentsMdSearch: agentsMdSearchPromise,
|
|
1394
|
+
workspaceTree: workspaceTreePromise,
|
|
1383
1395
|
});
|
|
1384
1396
|
|
|
1385
1397
|
if (options.systemPrompt === undefined) {
|
|
1386
1398
|
return defaultPrompt;
|
|
1387
1399
|
}
|
|
1388
|
-
if (
|
|
1389
|
-
return
|
|
1390
|
-
cwd,
|
|
1391
|
-
skills,
|
|
1392
|
-
contextFiles,
|
|
1393
|
-
tools: promptTools,
|
|
1394
|
-
toolNames,
|
|
1395
|
-
rules: rulebookRules,
|
|
1396
|
-
alwaysApplyRules,
|
|
1397
|
-
skillsSettings: settings.getGroup("skills"),
|
|
1398
|
-
customPrompt: options.systemPrompt,
|
|
1399
|
-
appendSystemPrompt: appendPrompt,
|
|
1400
|
-
repeatToolDescriptions,
|
|
1401
|
-
intentField,
|
|
1402
|
-
mcpDiscoveryMode: hasDiscoverableMCPTools,
|
|
1403
|
-
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1404
|
-
eagerTasks,
|
|
1405
|
-
secretsEnabled,
|
|
1406
|
-
agentsMdSearch: agentsMdSearchPromise,
|
|
1407
|
-
});
|
|
1400
|
+
if (Array.isArray(options.systemPrompt)) {
|
|
1401
|
+
return { systemPrompt: options.systemPrompt };
|
|
1408
1402
|
}
|
|
1409
|
-
return
|
|
1403
|
+
return {
|
|
1404
|
+
systemPrompt: options.systemPrompt(defaultPrompt.systemPrompt),
|
|
1405
|
+
};
|
|
1410
1406
|
};
|
|
1411
1407
|
|
|
1412
1408
|
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
@@ -1472,7 +1468,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1472
1468
|
}
|
|
1473
1469
|
}
|
|
1474
1470
|
|
|
1475
|
-
const systemPrompt = await logger.time(
|
|
1471
|
+
const { systemPrompt } = await logger.time(
|
|
1472
|
+
"buildSystemPrompt",
|
|
1473
|
+
rebuildSystemPrompt,
|
|
1474
|
+
initialToolNames,
|
|
1475
|
+
toolRegistry,
|
|
1476
|
+
);
|
|
1476
1477
|
|
|
1477
1478
|
const promptTemplates = await promptTemplatesPromise;
|
|
1478
1479
|
toolSession.promptTemplates = promptTemplates;
|
|
@@ -245,8 +245,8 @@ export interface AgentSessionConfig {
|
|
|
245
245
|
onResponse?: SimpleStreamOptions["onResponse"];
|
|
246
246
|
/** Current session message-to-LLM conversion pipeline */
|
|
247
247
|
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
248
|
-
/** System prompt builder that can consider tool availability */
|
|
249
|
-
rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<string>;
|
|
248
|
+
/** System prompt builder that can consider tool availability. Returns ordered provider-facing blocks. */
|
|
249
|
+
rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<{ systemPrompt: string[] }>;
|
|
250
250
|
/**
|
|
251
251
|
* Optional accessor for live MCP server instructions. Read by the session's
|
|
252
252
|
* `rebuildSystemPrompt`-skip optimization to detect server-side instruction
|
|
@@ -520,9 +520,11 @@ export class AgentSession {
|
|
|
520
520
|
#onPayload: SimpleStreamOptions["onPayload"] | undefined;
|
|
521
521
|
#onResponse: SimpleStreamOptions["onResponse"] | undefined;
|
|
522
522
|
#convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
523
|
-
#rebuildSystemPrompt:
|
|
523
|
+
#rebuildSystemPrompt:
|
|
524
|
+
| ((toolNames: string[], tools: Map<string, AgentTool>) => Promise<{ systemPrompt: string[] }>)
|
|
525
|
+
| undefined;
|
|
524
526
|
#getMcpServerInstructions: (() => Map<string, string> | undefined) | undefined;
|
|
525
|
-
#baseSystemPrompt: string;
|
|
527
|
+
#baseSystemPrompt: string[];
|
|
526
528
|
/**
|
|
527
529
|
* Signature of the (toolNames, tool descriptions) tuple passed to the most
|
|
528
530
|
* recent successful `rebuildSystemPrompt` call. Used to skip redundant rebuilds
|
|
@@ -2083,8 +2085,8 @@ export class AgentSession {
|
|
|
2083
2085
|
getLastAssistantMessage(): AssistantMessage | undefined {
|
|
2084
2086
|
return this.#findLastAssistantMessage();
|
|
2085
2087
|
}
|
|
2086
|
-
/** Current effective system prompt (includes any per-turn extension modifications) */
|
|
2087
|
-
get systemPrompt(): string {
|
|
2088
|
+
/** Current effective system prompt blocks (includes any per-turn extension modifications) */
|
|
2089
|
+
get systemPrompt(): string[] {
|
|
2088
2090
|
return this.agent.state.systemPrompt;
|
|
2089
2091
|
}
|
|
2090
2092
|
|
|
@@ -2281,7 +2283,8 @@ export class AgentSession {
|
|
|
2281
2283
|
if (this.#rebuildSystemPrompt) {
|
|
2282
2284
|
const signature = this.#computeAppliedToolSignature(validToolNames, tools);
|
|
2283
2285
|
if (signature !== this.#lastAppliedToolSignature) {
|
|
2284
|
-
|
|
2286
|
+
const built = await this.#rebuildSystemPrompt(validToolNames, this.#toolRegistry);
|
|
2287
|
+
this.#baseSystemPrompt = built.systemPrompt;
|
|
2285
2288
|
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
2286
2289
|
this.#lastAppliedToolSignature = signature;
|
|
2287
2290
|
}
|
|
@@ -2324,7 +2327,8 @@ export class AgentSession {
|
|
|
2324
2327
|
async refreshBaseSystemPrompt(): Promise<void> {
|
|
2325
2328
|
if (!this.#rebuildSystemPrompt) return;
|
|
2326
2329
|
const activeToolNames = this.getActiveToolNames();
|
|
2327
|
-
|
|
2330
|
+
const built = await this.#rebuildSystemPrompt(activeToolNames, this.#toolRegistry);
|
|
2331
|
+
this.#baseSystemPrompt = built.systemPrompt;
|
|
2328
2332
|
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
2329
2333
|
// Refresh the cached signature so a subsequent `#applyActiveToolsByName` with
|
|
2330
2334
|
// the same tool set does not re-rebuild on top of the explicit refresh we
|
|
@@ -2335,14 +2339,14 @@ export class AgentSession {
|
|
|
2335
2339
|
this.#lastAppliedToolSignature = this.#computeAppliedToolSignature(activeToolNames, activeTools);
|
|
2336
2340
|
}
|
|
2337
2341
|
|
|
2338
|
-
async #buildSystemPromptForAgentStart(promptText: string): Promise<string> {
|
|
2342
|
+
async #buildSystemPromptForAgentStart(promptText: string): Promise<string[]> {
|
|
2339
2343
|
const backend = resolveMemoryBackend(this.settings);
|
|
2340
2344
|
if (!backend.beforeAgentStartPrompt) return this.#baseSystemPrompt;
|
|
2341
2345
|
|
|
2342
2346
|
try {
|
|
2343
2347
|
const injected = await backend.beforeAgentStartPrompt(this, promptText);
|
|
2344
2348
|
if (!injected) return this.#baseSystemPrompt;
|
|
2345
|
-
return
|
|
2349
|
+
return [...this.#baseSystemPrompt, injected];
|
|
2346
2350
|
} catch (err) {
|
|
2347
2351
|
logger.debug("Memory backend beforeAgentStartPrompt failed", {
|
|
2348
2352
|
backend: backend.id,
|
|
@@ -4215,7 +4219,11 @@ export class AgentSession {
|
|
|
4215
4219
|
apiKey,
|
|
4216
4220
|
customInstructions,
|
|
4217
4221
|
compactionAbortController.signal,
|
|
4218
|
-
{
|
|
4222
|
+
{
|
|
4223
|
+
promptOverride: hookPrompt,
|
|
4224
|
+
extraContext: hookContext,
|
|
4225
|
+
remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
|
|
4226
|
+
},
|
|
4219
4227
|
);
|
|
4220
4228
|
summary = result.summary;
|
|
4221
4229
|
shortSummary = result.shortSummary;
|
|
@@ -5328,7 +5336,7 @@ export class AgentSession {
|
|
|
5328
5336
|
compactResult = await compact(preparation, candidate, apiKey, undefined, autoCompactionSignal, {
|
|
5329
5337
|
promptOverride: hookPrompt,
|
|
5330
5338
|
extraContext: hookContext,
|
|
5331
|
-
remoteInstructions: this.#baseSystemPrompt,
|
|
5339
|
+
remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
|
|
5332
5340
|
initiatorOverride: "agent",
|
|
5333
5341
|
});
|
|
5334
5342
|
break;
|
|
@@ -290,7 +290,7 @@ export async function generateBranchSummary(
|
|
|
290
290
|
// Call LLM for summarization
|
|
291
291
|
const response = await completeSimple(
|
|
292
292
|
model,
|
|
293
|
-
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
|
|
293
|
+
{ systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
|
|
294
294
|
{ apiKey, signal, maxTokens: 2048 },
|
|
295
295
|
);
|
|
296
296
|
|
|
@@ -1019,7 +1019,7 @@ export async function generateSummary(
|
|
|
1019
1019
|
|
|
1020
1020
|
const response = await completeSimple(
|
|
1021
1021
|
model,
|
|
1022
|
-
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
|
|
1022
|
+
{ systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
|
|
1023
1023
|
{ maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride: options?.initiatorOverride },
|
|
1024
1024
|
);
|
|
1025
1025
|
|
|
@@ -1066,7 +1066,7 @@ async function generateShortSummary(
|
|
|
1066
1066
|
const response = await completeSimple(
|
|
1067
1067
|
model,
|
|
1068
1068
|
{
|
|
1069
|
-
systemPrompt: SUMMARIZATION_SYSTEM_PROMPT,
|
|
1069
|
+
systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT],
|
|
1070
1070
|
messages: [{ role: "user", content: [{ type: "text", text: promptText }], timestamp: Date.now() }],
|
|
1071
1071
|
},
|
|
1072
1072
|
{ maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride: options?.initiatorOverride },
|
|
@@ -1386,7 +1386,7 @@ async function generateTurnPrefixSummary(
|
|
|
1386
1386
|
|
|
1387
1387
|
const response = await completeSimple(
|
|
1388
1388
|
model,
|
|
1389
|
-
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
|
|
1389
|
+
{ systemPrompt: [SUMMARIZATION_SYSTEM_PROMPT], messages: summarizationMessages },
|
|
1390
1390
|
{ maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride },
|
|
1391
1391
|
);
|
|
1392
1392
|
|
|
@@ -25,7 +25,7 @@ export interface SessionDumpToolInfo {
|
|
|
25
25
|
|
|
26
26
|
export interface FormatSessionDumpTextOptions {
|
|
27
27
|
messages: readonly AgentMessage[];
|
|
28
|
-
systemPrompt?: string | null;
|
|
28
|
+
systemPrompt?: readonly string[] | null;
|
|
29
29
|
model?: Model | null;
|
|
30
30
|
thinkingLevel?: ThinkingLevel | string | null;
|
|
31
31
|
tools?: readonly SessionDumpToolInfo[];
|
|
@@ -64,11 +64,16 @@ function formatArgsAsXml(args: Record<string, unknown>, indent = "\t"): string {
|
|
|
64
64
|
export function formatSessionDumpText(options: FormatSessionDumpTextOptions): string {
|
|
65
65
|
const lines: string[] = [];
|
|
66
66
|
|
|
67
|
-
const systemPrompt = options.systemPrompt;
|
|
68
|
-
if (systemPrompt) {
|
|
67
|
+
const systemPrompt = options.systemPrompt?.filter(prompt => prompt.length > 0) ?? [];
|
|
68
|
+
if (systemPrompt.length > 0) {
|
|
69
69
|
lines.push("## System Prompt\n");
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
for (let index = 0; index < systemPrompt.length; index++) {
|
|
71
|
+
if (systemPrompt.length > 1) {
|
|
72
|
+
lines.push(`### System Prompt ${index + 1}\n`);
|
|
73
|
+
}
|
|
74
|
+
lines.push(systemPrompt[index]);
|
|
75
|
+
lines.push("\n");
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
const model = options.model;
|
|
@@ -759,7 +759,7 @@ export function formatHeadTruncationNotice(
|
|
|
759
759
|
const totalFileLines = options.totalFileLines ?? truncation.totalLines;
|
|
760
760
|
const endLineDisplay = startLineDisplay + (truncation.outputLines ?? truncation.totalLines) - 1;
|
|
761
761
|
const nextOffset = endLineDisplay + 1;
|
|
762
|
-
const notice = `[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use
|
|
762
|
+
const notice = `[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use :${nextOffset} to continue]`;
|
|
763
763
|
return `\n\n${notice}`;
|
|
764
764
|
}
|
|
765
765
|
|
package/src/system-prompt.ts
CHANGED
|
@@ -13,7 +13,9 @@ import type { SkillsSettings } from "./config/settings";
|
|
|
13
13
|
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
|
14
14
|
import { loadSkills, type Skill } from "./extensibility/skills";
|
|
15
15
|
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
16
|
+
import projectPromptTemplate from "./prompts/system/project-prompt.md" with { type: "text" };
|
|
16
17
|
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
|
18
|
+
import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
17
19
|
|
|
18
20
|
interface AlwaysApplyRule {
|
|
19
21
|
name: string;
|
|
@@ -409,12 +411,20 @@ export interface BuildSystemPromptOptions {
|
|
|
409
411
|
secretsEnabled?: boolean;
|
|
410
412
|
/** Pre-loaded AGENTS.md search (skips discovery if provided). May be a Promise to allow early kick-off. */
|
|
411
413
|
agentsMdSearch?: AgentsMdSearch | Promise<AgentsMdSearch>;
|
|
414
|
+
/** Pre-loaded workspace tree (skips discovery if provided). May be a Promise to allow early kick-off. */
|
|
415
|
+
workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Result of building provider-facing system prompt messages. */
|
|
419
|
+
export interface BuildSystemPromptResult {
|
|
420
|
+
/** Ordered system prompt blocks. Providers should preserve entries as distinct messages/blocks. */
|
|
421
|
+
systemPrompt: string[];
|
|
412
422
|
}
|
|
413
423
|
|
|
414
424
|
/** Build the system prompt with tools, guidelines, and context */
|
|
415
|
-
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<
|
|
425
|
+
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
|
|
416
426
|
if ($env.NULL_PROMPT === "true") {
|
|
417
|
-
return
|
|
427
|
+
return { systemPrompt: [] };
|
|
418
428
|
}
|
|
419
429
|
|
|
420
430
|
const {
|
|
@@ -435,6 +445,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
435
445
|
eagerTasks = false,
|
|
436
446
|
secretsEnabled = false,
|
|
437
447
|
agentsMdSearch: providedAgentsMdSearch,
|
|
448
|
+
workspaceTree: providedWorkspaceTree,
|
|
438
449
|
} = options;
|
|
439
450
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
440
451
|
|
|
@@ -449,6 +460,10 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
449
460
|
providedAgentsMdSearch !== undefined
|
|
450
461
|
? Promise.resolve(providedAgentsMdSearch)
|
|
451
462
|
: logger.time("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
|
|
463
|
+
const workspaceTreePromise =
|
|
464
|
+
providedWorkspaceTree !== undefined
|
|
465
|
+
? Promise.resolve(providedWorkspaceTree)
|
|
466
|
+
: logger.time("buildWorkspaceTree", buildWorkspaceTree, resolvedCwd);
|
|
452
467
|
const skillsPromise: Promise<Skill[]> =
|
|
453
468
|
providedSkills !== undefined
|
|
454
469
|
? Promise.resolve(providedSkills)
|
|
@@ -463,6 +478,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
463
478
|
contextFilesPromise,
|
|
464
479
|
agentsMdSearchPromise,
|
|
465
480
|
skillsPromise,
|
|
481
|
+
workspaceTreePromise,
|
|
466
482
|
]).then(
|
|
467
483
|
([
|
|
468
484
|
resolvedCustomPrompt,
|
|
@@ -471,6 +487,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
471
487
|
contextFiles,
|
|
472
488
|
agentsMdSearch,
|
|
473
489
|
skills,
|
|
490
|
+
workspaceTree,
|
|
474
491
|
]) => ({
|
|
475
492
|
resolvedCustomPrompt,
|
|
476
493
|
resolvedAppendPrompt,
|
|
@@ -478,6 +495,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
478
495
|
contextFiles,
|
|
479
496
|
agentsMdSearch,
|
|
480
497
|
skills,
|
|
498
|
+
workspaceTree,
|
|
481
499
|
}),
|
|
482
500
|
);
|
|
483
501
|
})();
|
|
@@ -501,6 +519,12 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
501
519
|
pattern: `AGENTS.md depth ${AGENTS_MD_MIN_DEPTH}-${AGENTS_MD_MAX_DEPTH}`,
|
|
502
520
|
files: [],
|
|
503
521
|
};
|
|
522
|
+
let workspaceTree: WorkspaceTree = {
|
|
523
|
+
rootPath: resolvedCwd,
|
|
524
|
+
rendered: "",
|
|
525
|
+
truncated: false,
|
|
526
|
+
totalLines: 0,
|
|
527
|
+
};
|
|
504
528
|
let skills: Skill[] = providedSkills ?? [];
|
|
505
529
|
|
|
506
530
|
if (prepResult.type === "timeout") {
|
|
@@ -524,6 +548,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
524
548
|
contextFiles = dedupeExactContextFiles(prepResult.value.contextFiles);
|
|
525
549
|
agentsMdSearch = prepResult.value.agentsMdSearch;
|
|
526
550
|
skills = prepResult.value.skills;
|
|
551
|
+
workspaceTree = prepResult.value.workspaceTree;
|
|
527
552
|
}
|
|
528
553
|
|
|
529
554
|
const date = new Date().toISOString().slice(0, 10);
|
|
@@ -578,6 +603,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
578
603
|
environment,
|
|
579
604
|
contextFiles,
|
|
580
605
|
agentsMdSearch,
|
|
606
|
+
workspaceTree,
|
|
581
607
|
skills: filteredSkills,
|
|
582
608
|
rules: rules ?? [],
|
|
583
609
|
alwaysApplyRules: injectedAlwaysApplyRules,
|
|
@@ -599,5 +625,11 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
599
625
|
rendered += `\n\n<critical>\nThe \`${reportToolIssueToolName}\` tool is available for automated QA. If ANY tool you call returns output that is unexpected, incorrect, malformed, or otherwise inconsistent with what you anticipated given the tool's described behavior and your parameters, call \`${reportToolIssueToolName}\` with the tool name and a concise description of the discrepancy. Do not hesitate to report — false positives are acceptable.\n</critical>`;
|
|
600
626
|
}
|
|
601
627
|
|
|
602
|
-
|
|
628
|
+
const systemPrompt = [rendered];
|
|
629
|
+
const projectPrompt = resolvedCustomPrompt ? "" : prompt.render(projectPromptTemplate, data).trim();
|
|
630
|
+
if (projectPrompt) {
|
|
631
|
+
systemPrompt.push(projectPrompt);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return { systemPrompt };
|
|
603
635
|
}
|
package/src/task/executor.ts
CHANGED
|
@@ -967,9 +967,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
967
967
|
contextFiles: options.contextFiles,
|
|
968
968
|
skills: options.skills,
|
|
969
969
|
promptTemplates: options.promptTemplates,
|
|
970
|
-
systemPrompt: defaultPrompt =>
|
|
970
|
+
systemPrompt: defaultPrompt => [
|
|
971
971
|
prompt.render(subagentSystemPromptTemplate, {
|
|
972
|
-
base: defaultPrompt,
|
|
972
|
+
base: defaultPrompt.join("\n\n"),
|
|
973
973
|
agent: agent.systemPrompt,
|
|
974
974
|
worktree: worktree ?? "",
|
|
975
975
|
outputSchema: normalizedOutputSchema,
|
|
@@ -977,6 +977,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
977
977
|
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
|
978
978
|
ircSelfId: ircEnabled ? id : "",
|
|
979
979
|
}),
|
|
980
|
+
],
|
|
980
981
|
sessionManager,
|
|
981
982
|
hasUI: false,
|
|
982
983
|
spawns: spawnsEnv,
|
|
@@ -1016,7 +1017,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1016
1017
|
}
|
|
1017
1018
|
|
|
1018
1019
|
session.sessionManager.appendSessionInit({
|
|
1019
|
-
systemPrompt: session.agent.state.systemPrompt,
|
|
1020
|
+
systemPrompt: session.agent.state.systemPrompt.join("\n\n"),
|
|
1020
1021
|
task,
|
|
1021
1022
|
tools: session.getActiveToolNames(),
|
|
1022
1023
|
outputSchema,
|
package/src/tools/fetch.ts
CHANGED
|
@@ -148,20 +148,20 @@ export interface ParsedReadUrlTarget {
|
|
|
148
148
|
limit?: number;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
export function parseReadUrlTarget(readPath: string
|
|
152
|
-
const embedded =
|
|
151
|
+
export function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null {
|
|
152
|
+
const embedded = tryExtractEmbeddedUrlSelector(readPath);
|
|
153
153
|
const urlPath = embedded?.path ?? readPath;
|
|
154
154
|
if (!isReadableUrlPath(urlPath)) {
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
const selector =
|
|
158
|
+
const selector = embedded?.sel;
|
|
159
159
|
const raw = selector === "raw";
|
|
160
160
|
const lineMatch = selector ? URL_LINE_RANGE_RE.exec(selector) : null;
|
|
161
161
|
if (lineMatch) {
|
|
162
162
|
const startLine = Number.parseInt(lineMatch[1]!, 10);
|
|
163
163
|
if (startLine < 1) {
|
|
164
|
-
throw new ToolError("
|
|
164
|
+
throw new ToolError("URL line selector 0 is invalid; lines are 1-indexed. Use :L1.");
|
|
165
165
|
}
|
|
166
166
|
const sep = lineMatch[2];
|
|
167
167
|
const rhs = lineMatch[3] ? Number.parseInt(lineMatch[3], 10) : undefined;
|