@oh-my-pi/pi-coding-agent 14.7.5 → 14.7.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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.7.6] - 2026-05-07
6
+ ### Changed
7
+
8
+ - Changed the "Hide Thinking Blocks" setting (Ctrl+T) to also instruct the provider to omit thinking/reasoning summaries from responses, instead of just hiding them client-side. Anthropic sees `thinking.display = "omitted"` (where supported); OpenAI Responses / Azure / Codex requests drop `reasoning.summary` entirely.
9
+
10
+ ### Fixed
11
+
12
+ - Fixed the `Hide Thinking Blocks` toggle so changing it updates the active session’s request settings immediately, ensuring new responses reflect the current hide-thinking preference
13
+ - Fixed system prompt preparation to keep successful context data and only fall back to minimal defaults for preparation steps that fail
14
+ - Fixed system prompt preparation timeout to apply per-step instead of all-or-nothing: a single slow step (e.g. `buildAgentsMdSearch` on a huge directory tree, `buildWorkspaceTree`, `loadProjectContextFiles`) now falls back to its own minimal default while the other steps still populate, and the warning names which steps timed out.
15
+ - Fixed subagents re-running expensive workspace scans (`buildAgentsMdSearch`, `buildWorkspaceTree`) on every spawn: parents now forward their already-resolved `AGENTS.md` search and workspace tree to subagents through `createAgentSession`, matching how `contextFiles`, `skills`, and `promptTemplates` are already inherited. On large monorepos this removes seconds of redundant work per `task` invocation and prevents the per-subagent system-prompt timeout warnings.
16
+
5
17
  ## [14.7.5] - 2026-05-07
6
18
  ### Added
7
19
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "14.7.5",
4
+ "version": "14.7.6",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.21.0",
48
48
  "@mozilla/readability": "^0.6.0",
49
- "@oh-my-pi/omp-stats": "14.7.5",
50
- "@oh-my-pi/pi-agent-core": "14.7.5",
51
- "@oh-my-pi/pi-ai": "14.7.5",
52
- "@oh-my-pi/pi-natives": "14.7.5",
53
- "@oh-my-pi/pi-tui": "14.7.5",
54
- "@oh-my-pi/pi-utils": "14.7.5",
49
+ "@oh-my-pi/omp-stats": "14.7.6",
50
+ "@oh-my-pi/pi-agent-core": "14.7.6",
51
+ "@oh-my-pi/pi-ai": "14.7.6",
52
+ "@oh-my-pi/pi-natives": "14.7.6",
53
+ "@oh-my-pi/pi-tui": "14.7.6",
54
+ "@oh-my-pi/pi-utils": "14.7.6",
55
55
  "@puppeteer/browsers": "^2.13.0",
56
56
  "@sinclair/typebox": "^0.34.49",
57
57
  "@types/turndown": "5.0.6",
@@ -1,5 +1,7 @@
1
1
  import * as fs from "node:fs";
2
+ import { createRequire } from "node:module";
2
3
  import * as path from "node:path";
4
+ import { pathToFileURL } from "node:url";
3
5
  import * as util from "node:util";
4
6
  import * as vm from "node:vm";
5
7
 
@@ -417,6 +419,8 @@ async function createVmState(
417
419
  btoa,
418
420
  Buffer,
419
421
  process: createProcessSubset(cwd),
422
+ require: buildRequire(cwd),
423
+ createRequire,
420
424
  fs,
421
425
  fetch,
422
426
  Blob,
@@ -478,13 +482,81 @@ async function runQueued<T>(state: VmContextState, work: () => Promise<T>): Prom
478
482
  }
479
483
  }
480
484
 
485
+ function buildRequire(cwd: string): NodeJS.Require {
486
+ // Anchor `require` resolution at the session cwd. The filename does not need to exist;
487
+ // Node only uses it as a base for module resolution.
488
+ return createRequire(pathToFileURL(path.join(cwd, "[eval]")).href);
489
+ }
490
+
491
+ // Static `import ... from "x"` is not valid inside vm.runInContext. Rewrite the common
492
+ // forms to dynamic `await import(...)` so users can paste ESM-style imports verbatim.
493
+ const STATIC_IMPORT_RE = /^[ \t]*import\b(?:[ \t]+([^'"\n]+?)[ \t]+from)?[ \t]*(['"])([^'"\n]+)\2[ \t]*;?[ \t]*$/gm;
494
+
495
+ function splitTopLevel(clause: string): string[] {
496
+ const out: string[] = [];
497
+ let depth = 0;
498
+ let buf = "";
499
+ for (const ch of clause) {
500
+ if (ch === "{") depth++;
501
+ else if (ch === "}") depth--;
502
+ if (ch === "," && depth === 0) {
503
+ if (buf.trim()) out.push(buf.trim());
504
+ buf = "";
505
+ } else {
506
+ buf += ch;
507
+ }
508
+ }
509
+ if (buf.trim()) out.push(buf.trim());
510
+ return out;
511
+ }
512
+
513
+ function rewriteImportClause(clause: string, sourceLiteral: string): string {
514
+ let defaultName: string | undefined;
515
+ let namespaceName: string | undefined;
516
+ let namedBlock: string | undefined;
517
+ for (const part of splitTopLevel(clause)) {
518
+ if (part.startsWith("{")) {
519
+ namedBlock = part;
520
+ } else if (part.startsWith("*")) {
521
+ const m = part.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
522
+ if (!m) return `await import(${sourceLiteral}); /* unrewritten import: ${clause} */`;
523
+ namespaceName = m[1];
524
+ } else if (/^[A-Za-z_$][\w$]*$/.test(part)) {
525
+ defaultName = part;
526
+ } else {
527
+ return `await import(${sourceLiteral}); /* unrewritten import: ${clause} */`;
528
+ }
529
+ }
530
+ if (namedBlock) {
531
+ const inner = namedBlock.slice(1, -1).trim();
532
+ const renamed = inner.replace(/([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)/g, "$1: $2");
533
+ const props = defaultName ? `default: ${defaultName}, ${renamed}` : renamed;
534
+ return `const { ${props} } = await import(${sourceLiteral});`;
535
+ }
536
+ if (namespaceName && defaultName) {
537
+ return `const ${namespaceName} = await import(${sourceLiteral}); const ${defaultName} = ${namespaceName}.default;`;
538
+ }
539
+ if (namespaceName) return `const ${namespaceName} = await import(${sourceLiteral});`;
540
+ if (defaultName) return `const ${defaultName} = (await import(${sourceLiteral})).default;`;
541
+ return `await import(${sourceLiteral});`;
542
+ }
543
+
544
+ export function rewriteStaticImports(code: string): string {
545
+ return code.replace(STATIC_IMPORT_RE, (_match, clause: string | undefined, _quote, source: string) => {
546
+ const literal = JSON.stringify(source);
547
+ if (!clause) return `await import(${literal});`;
548
+ return rewriteImportClause(clause.trim(), literal);
549
+ });
550
+ }
551
+
481
552
  function wrapCode(code: string): { source: string; asyncWrapped: boolean } {
482
- const needsAsyncWrapper = /\bawait\b|\breturn\b/.test(code);
553
+ const rewritten = rewriteStaticImports(code);
554
+ const needsAsyncWrapper = /\bawait\b|\breturn\b/.test(rewritten);
483
555
  if (!needsAsyncWrapper) {
484
- return { source: code, asyncWrapped: false };
556
+ return { source: rewritten, asyncWrapped: false };
485
557
  }
486
558
  return {
487
- source: `(async () => {\n${code}\n})()`,
559
+ source: `(async () => {\n${rewritten}\n})()`,
488
560
  asyncWrapped: true,
489
561
  };
490
562
  }
@@ -701,6 +701,7 @@ export class InputController {
701
701
  toggleThinkingBlockVisibility(): void {
702
702
  this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
703
703
  settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
704
+ this.ctx.session.agent.hideThinkingSummary = this.ctx.hideThinkingBlock;
704
705
 
705
706
  // Rebuild chat from session messages
706
707
  this.ctx.chatContainer.clear();
@@ -270,6 +270,7 @@ export class SelectorController {
270
270
  break;
271
271
  case "hideThinking":
272
272
  this.ctx.hideThinkingBlock = value as boolean;
273
+ this.ctx.session.agent.hideThinkingSummary = value as boolean;
273
274
  for (const child of this.ctx.chatContainer.children) {
274
275
  if (child instanceof AssistantMessageComponent) {
275
276
  child.setHideThinkingBlock(value as boolean);
@@ -50,7 +50,7 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
50
50
 
51
51
  # URLs
52
52
 
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 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`.
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 a `:raw` suffix for untouched HTML. URL line selectors mirror the file form (`:50`, `:50-100`, `:50+150`, `:raw`). If a URL would otherwise look like `host:port`, add a trailing slash before the selector (e.g. `https://example.com/:80`).
54
54
  </instruction>
55
55
 
56
56
  <critical>
package/src/sdk.ts CHANGED
@@ -201,6 +201,10 @@ export interface CreateAgentSessionOptions {
201
201
  rules?: Rule[];
202
202
  /** Context files (AGENTS.md content). Default: discovered walking up from cwd */
203
203
  contextFiles?: Array<{ path: string; content: string }>;
204
+ /** Pre-built AGENTS.md search (skips re-scanning the workspace; passed by parents to subagents). */
205
+ agentsMdSearch?: AgentsMdSearch;
206
+ /** Pre-built workspace tree (skips re-scanning; passed by parents to subagents). */
207
+ workspaceTree?: WorkspaceTree;
204
208
  /** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
205
209
  promptTemplates?: PromptTemplate[];
206
210
  /** File-based slash commands. Default: discovered from commands/ directories */
@@ -687,11 +691,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
687
691
  if (!options.modelRegistry) {
688
692
  modelRegistry.refreshInBackground();
689
693
  }
690
- // Kick off AGENTS.md filesystem search in parallel — it is the slowest piece of buildSystemPrompt
691
- // (~200ms on large repos) and only needs `cwd`, so it can overlap with everything that follows.
692
- const agentsMdSearchPromise: Promise<AgentsMdSearch> = logger.time("buildAgentsMdSearch", buildAgentsMdSearch, cwd);
694
+ // Kick off AGENTS.md filesystem search and workspace tree in parallel — they are the slowest pieces of
695
+ // buildSystemPrompt (can be many seconds on large repos) and only need `cwd`, so they overlap with
696
+ // everything that follows. Subagents inherit the parent's resolved values via options.
697
+ const agentsMdSearchPromise: Promise<AgentsMdSearch> = options.agentsMdSearch
698
+ ? Promise.resolve(options.agentsMdSearch)
699
+ : logger.time("buildAgentsMdSearch", buildAgentsMdSearch, cwd);
693
700
  agentsMdSearchPromise.catch(() => {});
694
- const workspaceTreePromise: Promise<WorkspaceTree> = logger.time("buildWorkspaceTree", buildWorkspaceTree, cwd);
701
+ const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
702
+ ? Promise.resolve(options.workspaceTree)
703
+ : logger.time("buildWorkspaceTree", buildWorkspaceTree, cwd);
695
704
  workspaceTreePromise.catch(() => {});
696
705
 
697
706
  // Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
@@ -889,7 +898,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
889
898
  return { ttsrManager, rulebookRules, alwaysApplyRules };
890
899
  });
891
900
 
892
- const contextFiles = await contextFilesPromise;
901
+ const [contextFiles, resolvedAgentsMdSearch, resolvedWorkspaceTree] = await Promise.all([
902
+ contextFilesPromise,
903
+ agentsMdSearchPromise,
904
+ workspaceTreePromise,
905
+ ]);
893
906
 
894
907
  let agent: Agent;
895
908
  let session!: AgentSession;
@@ -973,6 +986,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
973
986
  },
974
987
  skipPythonPreflight: options.skipPythonPreflight,
975
988
  contextFiles,
989
+ agentsMdSearch: resolvedAgentsMdSearch,
990
+ workspaceTree: resolvedWorkspaceTree,
976
991
  skills,
977
992
  eventBus,
978
993
  outputSchema: options.outputSchema,
@@ -1637,6 +1652,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1637
1652
  presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
1638
1653
  repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
1639
1654
  serviceTier: initialServiceTier,
1655
+ hideThinkingSummary: settings.get("hideThinkingBlock"),
1640
1656
  kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
1641
1657
  preferWebsockets: preferOpenAICodexWebsockets,
1642
1658
  getToolContext: tc => toolContextStore.getContext(tc),
@@ -6610,6 +6610,7 @@ export class AgentSession {
6610
6610
  apiKey,
6611
6611
  sessionId: this.sessionId,
6612
6612
  reasoning: toReasoningEffort(this.thinkingLevel),
6613
+ hideThinkingSummary: this.agent.hideThinkingSummary,
6613
6614
  serviceTier: this.serviceTier,
6614
6615
  signal: args.signal,
6615
6616
  toolChoice: "none",
@@ -449,106 +449,121 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
449
449
  } = options;
450
450
  const resolvedCwd = cwd ?? getProjectDir();
451
451
 
452
- const prepPromise = (() => {
453
- const systemPromptCustomizationPromise = logger.time("loadSystemPromptFiles", loadSystemPromptFiles, {
454
- cwd: resolvedCwd,
455
- });
456
- const contextFilesPromise = providedContextFiles
457
- ? Promise.resolve(providedContextFiles)
458
- : logger.time("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
459
- const agentsMdSearchPromise =
460
- providedAgentsMdSearch !== undefined
461
- ? Promise.resolve(providedAgentsMdSearch)
462
- : logger.time("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
463
- const workspaceTreePromise =
464
- providedWorkspaceTree !== undefined
465
- ? Promise.resolve(providedWorkspaceTree)
466
- : logger.time("buildWorkspaceTree", buildWorkspaceTree, resolvedCwd);
467
- const skillsPromise: Promise<Skill[]> =
468
- providedSkills !== undefined
469
- ? Promise.resolve(providedSkills)
470
- : skillsSettings?.enabled !== false
471
- ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
472
- : Promise.resolve([]);
473
-
474
- return Promise.all([
452
+ const prepDefaults = {
453
+ resolvedCustomPrompt: undefined as string | undefined,
454
+ resolvedAppendPrompt: undefined as string | undefined,
455
+ systemPromptCustomization: null as string | null,
456
+ contextFiles: dedupeExactContextFiles(providedContextFiles ?? []),
457
+ agentsMdSearch: {
458
+ scopePath: ".",
459
+ limit: AGENTS_MD_LIMIT,
460
+ pattern: `AGENTS.md depth ${AGENTS_MD_MIN_DEPTH}-${AGENTS_MD_MAX_DEPTH}`,
461
+ files: [] as string[],
462
+ } satisfies AgentsMdSearch,
463
+ skills: providedSkills ?? ([] as Skill[]),
464
+ workspaceTree: {
465
+ rootPath: resolvedCwd,
466
+ rendered: "",
467
+ truncated: false,
468
+ totalLines: 0,
469
+ } satisfies WorkspaceTree,
470
+ };
471
+
472
+ const deadline = Bun.sleep(SYSTEM_PROMPT_PREP_TIMEOUT_MS).then(() => "__timeout__" as const);
473
+ const timedOut: string[] = [];
474
+ const failed: Array<{ name: string; error: unknown }> = [];
475
+
476
+ async function withDeadline<T>(name: string, work: Promise<T>, fallback: T): Promise<T> {
477
+ const tagged = work
478
+ .then(value => ({ kind: "ok" as const, value }))
479
+ .catch(error => ({ kind: "err" as const, error }));
480
+ const result = await Promise.race([tagged, deadline]);
481
+ if (result === "__timeout__") {
482
+ timedOut.push(name);
483
+ // Let the work continue in the background so its caches still warm; just log on completion.
484
+ void tagged.then(r => {
485
+ if (r.kind === "err") {
486
+ logger.warn("Background system prompt preparation step failed", { name, error: String(r.error) });
487
+ } else {
488
+ logger.debug("Background system prompt preparation step completed after timeout", { name });
489
+ }
490
+ });
491
+ return fallback;
492
+ }
493
+ if (result.kind === "err") {
494
+ failed.push({ name, error: result.error });
495
+ return fallback;
496
+ }
497
+ return result.value;
498
+ }
499
+
500
+ const systemPromptCustomizationPromise = logger.time("loadSystemPromptFiles", loadSystemPromptFiles, {
501
+ cwd: resolvedCwd,
502
+ });
503
+ const contextFilesPromise = providedContextFiles
504
+ ? Promise.resolve(providedContextFiles)
505
+ : logger.time("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
506
+ const agentsMdSearchPromise =
507
+ providedAgentsMdSearch !== undefined
508
+ ? Promise.resolve(providedAgentsMdSearch)
509
+ : logger.time("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
510
+ const workspaceTreePromise =
511
+ providedWorkspaceTree !== undefined
512
+ ? Promise.resolve(providedWorkspaceTree)
513
+ : logger.time("buildWorkspaceTree", buildWorkspaceTree, resolvedCwd);
514
+ const skillsPromise: Promise<Skill[]> =
515
+ providedSkills !== undefined
516
+ ? Promise.resolve(providedSkills)
517
+ : skillsSettings?.enabled !== false
518
+ ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
519
+ : Promise.resolve([]);
520
+
521
+ const [
522
+ resolvedCustomPrompt,
523
+ resolvedAppendPrompt,
524
+ systemPromptCustomization,
525
+ contextFiles,
526
+ agentsMdSearch,
527
+ skills,
528
+ workspaceTree,
529
+ ] = await Promise.all([
530
+ withDeadline(
531
+ "customPrompt",
475
532
  resolvePromptInput(customPrompt, "system prompt"),
533
+ prepDefaults.resolvedCustomPrompt,
534
+ ),
535
+ withDeadline(
536
+ "appendSystemPrompt",
476
537
  resolvePromptInput(appendSystemPrompt, "append system prompt"),
477
- systemPromptCustomizationPromise,
478
- contextFilesPromise,
479
- agentsMdSearchPromise,
480
- skillsPromise,
481
- workspaceTreePromise,
482
- ]).then(
483
- ([
484
- resolvedCustomPrompt,
485
- resolvedAppendPrompt,
486
- systemPromptCustomization,
487
- contextFiles,
488
- agentsMdSearch,
489
- skills,
490
- workspaceTree,
491
- ]) => ({
492
- resolvedCustomPrompt,
493
- resolvedAppendPrompt,
494
- systemPromptCustomization,
495
- contextFiles,
496
- agentsMdSearch,
497
- skills,
498
- workspaceTree,
499
- }),
500
- );
501
- })();
502
-
503
- const prepResult = await Promise.race([
504
- prepPromise
505
- .then(value => ({ type: "ready" as const, value }))
506
- .catch(error => ({ type: "error" as const, error })),
507
- Bun.sleep(SYSTEM_PROMPT_PREP_TIMEOUT_MS).then(() => ({ type: "timeout" as const })),
538
+ prepDefaults.resolvedAppendPrompt,
539
+ ),
540
+ withDeadline("loadSystemPromptFiles", systemPromptCustomizationPromise, prepDefaults.systemPromptCustomization),
541
+ withDeadline("loadProjectContextFiles", contextFilesPromise, prepDefaults.contextFiles).then(
542
+ dedupeExactContextFiles,
543
+ ),
544
+ withDeadline("buildAgentsMdSearch", agentsMdSearchPromise, prepDefaults.agentsMdSearch),
545
+ withDeadline("loadSkills", skillsPromise, prepDefaults.skills),
546
+ withDeadline("buildWorkspaceTree", workspaceTreePromise, prepDefaults.workspaceTree),
508
547
  ]);
509
548
 
510
- let resolvedCustomPrompt: string | undefined;
511
- let resolvedAppendPrompt: string | undefined;
512
- let systemPromptCustomization: string | null = null;
513
- let contextFiles: Array<{ path: string; content: string; depth?: number }> = dedupeExactContextFiles(
514
- providedContextFiles ?? [],
515
- );
516
- let agentsMdSearch: AgentsMdSearch = {
517
- scopePath: ".",
518
- limit: AGENTS_MD_LIMIT,
519
- pattern: `AGENTS.md depth ${AGENTS_MD_MIN_DEPTH}-${AGENTS_MD_MAX_DEPTH}`,
520
- files: [],
521
- };
522
- let workspaceTree: WorkspaceTree = {
523
- rootPath: resolvedCwd,
524
- rendered: "",
525
- truncated: false,
526
- totalLines: 0,
527
- };
528
- let skills: Skill[] = providedSkills ?? [];
529
-
530
- if (prepResult.type === "timeout") {
531
- logger.warn("System prompt preparation timed out; using minimal startup context", {
549
+ if (timedOut.length > 0) {
550
+ logger.warn("System prompt preparation steps timed out; using minimal fallback for those steps", {
532
551
  cwd: resolvedCwd,
533
552
  timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS,
553
+ steps: timedOut,
534
554
  });
535
555
  process.stderr.write(
536
- `Warning: system prompt preparation timed out after ${SYSTEM_PROMPT_PREP_TIMEOUT_MS}ms; using minimal startup context.\n`,
556
+ `Warning: system prompt preparation steps timed out after ${SYSTEM_PROMPT_PREP_TIMEOUT_MS}ms (${timedOut.join(", ")}); using minimal fallback for those steps.\n`,
537
557
  );
538
- } else if (prepResult.type === "error") {
539
- logger.warn("System prompt preparation failed; using minimal startup context", {
540
- cwd: resolvedCwd,
541
- error: String(prepResult.error),
542
- });
543
- process.stderr.write("Warning: system prompt preparation failed; using minimal startup context.\n");
544
- } else {
545
- resolvedCustomPrompt = prepResult.value.resolvedCustomPrompt;
546
- resolvedAppendPrompt = prepResult.value.resolvedAppendPrompt;
547
- systemPromptCustomization = prepResult.value.systemPromptCustomization;
548
- contextFiles = dedupeExactContextFiles(prepResult.value.contextFiles);
549
- agentsMdSearch = prepResult.value.agentsMdSearch;
550
- skills = prepResult.value.skills;
551
- workspaceTree = prepResult.value.workspaceTree;
558
+ }
559
+ if (failed.length > 0) {
560
+ for (const { name, error } of failed) {
561
+ logger.warn("System prompt preparation step failed; using minimal fallback", {
562
+ cwd: resolvedCwd,
563
+ step: name,
564
+ error: String(error),
565
+ });
566
+ }
552
567
  }
553
568
 
554
569
  const date = new Date().toISOString().slice(0, 10);
@@ -28,11 +28,13 @@ import { createAgentSession, discoverAuthStorage } from "../sdk";
28
28
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
29
29
  import type { AuthStorage } from "../session/auth-storage";
30
30
  import { SessionManager } from "../session/session-manager";
31
+ import type { AgentsMdSearch } from "../system-prompt";
31
32
  import { type ContextFileEntry, truncateTail } from "../tools";
32
33
  import { jtdToJsonSchema, normalizeSchema } from "../tools/jtd-to-json-schema";
33
34
  import { ToolAbortError } from "../tools/tool-errors";
34
35
  import type { EventBus } from "../utils/event-bus";
35
36
  import { buildNamedToolChoice } from "../utils/tool-choice";
37
+ import type { WorkspaceTree } from "../workspace-tree";
36
38
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
37
39
  import {
38
40
  type AgentDefinition,
@@ -158,6 +160,8 @@ export interface ExecutorOptions {
158
160
  contextFiles?: ContextFileEntry[];
159
161
  skills?: Skill[];
160
162
  promptTemplates?: PromptTemplate[];
163
+ agentsMdSearch?: AgentsMdSearch;
164
+ workspaceTree?: WorkspaceTree;
161
165
  mcpManager?: MCPManager;
162
166
  authStorage?: AuthStorage;
163
167
  modelRegistry?: ModelRegistry;
@@ -967,6 +971,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
967
971
  contextFiles: options.contextFiles,
968
972
  skills: options.skills,
969
973
  promptTemplates: options.promptTemplates,
974
+ agentsMdSearch: options.agentsMdSearch,
975
+ workspaceTree: options.workspaceTree,
970
976
  systemPrompt: defaultPrompt => [
971
977
  prompt.render(subagentSystemPromptTemplate, {
972
978
  base: defaultPrompt.join("\n\n"),
package/src/task/index.ts CHANGED
@@ -864,6 +864,8 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
864
864
  mcpManager: this.session.mcpManager,
865
865
  contextFiles,
866
866
  skills: availableSkills,
867
+ agentsMdSearch: this.session.agentsMdSearch,
868
+ workspaceTree: this.session.workspaceTree,
867
869
  promptTemplates,
868
870
  localProtocolOptions,
869
871
  parentHindsightSessionState: this.session.getHindsightSessionState?.(),
@@ -919,6 +921,8 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
919
921
  mcpManager: this.session.mcpManager,
920
922
  contextFiles,
921
923
  skills: availableSkills,
924
+ agentsMdSearch: this.session.agentsMdSearch,
925
+ workspaceTree: this.session.workspaceTree,
922
926
  promptTemplates,
923
927
  localProtocolOptions,
924
928
  parentHindsightSessionState: this.session.getHindsightSessionState?.(),
@@ -137,9 +137,10 @@ export function isReadableUrlPath(value: string): boolean {
137
137
  return /^https?:\/\//i.test(value) || /^www\./i.test(value);
138
138
  }
139
139
 
140
- const URL_LINE_RANGE_RE = /^L?(\d+)(?:([-+])L?(\d+))?$/i;
141
- // Embedded URL selectors (after a `:` in the path) keep the explicit `L` prefix to avoid colliding with ports such as `https://example.com:50`.
142
- const URL_EMBEDDED_LINE_RANGE_RE = /^L\d+(?:[-+]L?\d+)?$/i;
140
+ // URL line selectors mirror the file form: `:50`, `:50-100`, `:50+150`, `:raw`.
141
+ // If a URL would otherwise look like `host:port`, add a trailing slash before the selector
142
+ // (e.g. `https://example.com/:80` to read line 80 of the document at `https://example.com/`).
143
+ const URL_LINE_RANGE_RE = /^(\d+)(?:([-+])(\d+))?$/;
143
144
 
144
145
  export interface ParsedReadUrlTarget {
145
146
  path: string;
@@ -157,11 +158,11 @@ export function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null
157
158
 
158
159
  const selector = embedded?.sel;
159
160
  const raw = selector === "raw";
160
- const lineMatch = selector ? URL_LINE_RANGE_RE.exec(selector) : null;
161
+ const lineMatch = selector && selector !== "raw" ? URL_LINE_RANGE_RE.exec(selector) : null;
161
162
  if (lineMatch) {
162
163
  const startLine = Number.parseInt(lineMatch[1]!, 10);
163
164
  if (startLine < 1) {
164
- throw new ToolError("URL line selector 0 is invalid; lines are 1-indexed. Use :L1.");
165
+ throw new ToolError("URL line selector 0 is invalid; lines are 1-indexed. Use :1.");
165
166
  }
166
167
  const sep = lineMatch[2];
167
168
  const rhs = lineMatch[3] ? Number.parseInt(lineMatch[3], 10) : undefined;
@@ -195,13 +196,13 @@ function tryExtractEmbeddedUrlSelector(readPath: string): { path: string; sel?:
195
196
  }
196
197
 
197
198
  const candidateSelector = readPath.slice(lastColonIndex + 1);
198
- const isEmbeddedSelector = candidateSelector === "raw" || URL_EMBEDDED_LINE_RANGE_RE.test(candidateSelector);
199
- if (!isEmbeddedSelector) {
199
+ const basePath = readPath.slice(0, lastColonIndex);
200
+ if (!isReadableUrlPath(basePath)) {
200
201
  return null;
201
202
  }
202
203
 
203
- const basePath = readPath.slice(0, lastColonIndex);
204
- if (!isReadableUrlPath(basePath)) {
204
+ const isEmbeddedSelector = candidateSelector === "raw" || URL_LINE_RANGE_RE.test(candidateSelector);
205
+ if (!isEmbeddedSelector) {
205
206
  return null;
206
207
  }
207
208
 
@@ -14,11 +14,13 @@ import type { PlanModeState } from "../plan-mode/state";
14
14
  import type { AgentRegistry } from "../registry/agent-registry";
15
15
  import type { CustomMessage } from "../session/messages";
16
16
  import type { ToolChoiceQueue } from "../session/tool-choice-queue";
17
+ import type { AgentsMdSearch } from "../system-prompt";
17
18
  import { TaskTool } from "../task";
18
19
  import type { AgentOutputManager } from "../task/output-manager";
19
20
  import type { DiscoverableTool, DiscoverableToolSearchIndex } from "../tool-discovery/tool-index";
20
21
  import type { EventBus } from "../utils/event-bus";
21
22
  import { WebSearchTool } from "../web/search";
23
+ import type { WorkspaceTree } from "../workspace-tree";
22
24
  import { AskTool } from "./ask";
23
25
  import { AstEditTool } from "./ast-edit";
24
26
  import { AstGrepTool } from "./ast-grep";
@@ -120,6 +122,10 @@ export interface ToolSession {
120
122
  skipPythonPreflight?: boolean;
121
123
  /** Pre-loaded context files (AGENTS.md, etc) */
122
124
  contextFiles?: ContextFileEntry[];
125
+ /** Pre-loaded AGENTS.md search (forwarded to subagents to skip re-scanning) */
126
+ agentsMdSearch?: AgentsMdSearch;
127
+ /** Pre-loaded workspace tree (forwarded to subagents to skip re-scanning) */
128
+ workspaceTree?: WorkspaceTree;
123
129
  /** Pre-loaded skills */
124
130
  skills?: Skill[];
125
131
  /** Pre-loaded prompt templates */