@oh-my-pi/pi-coding-agent 14.9.1 → 14.9.3

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +3 -3
  4. package/src/config/prompt-templates.ts +0 -5
  5. package/src/config/settings-schema.ts +38 -0
  6. package/src/eval/eval.lark +10 -31
  7. package/src/eval/index.ts +1 -0
  8. package/src/eval/parse.ts +156 -255
  9. package/src/eval/sniff.ts +28 -0
  10. package/src/export/html/template.css +38 -0
  11. package/src/export/html/template.generated.ts +1 -1
  12. package/src/export/html/template.js +209 -15
  13. package/src/extensibility/extensions/runner.ts +173 -177
  14. package/src/hashline/apply.ts +8 -24
  15. package/src/hashline/constants.ts +20 -0
  16. package/src/hashline/execute.ts +0 -1
  17. package/src/hashline/grammar.lark +16 -27
  18. package/src/hashline/hash.ts +4 -34
  19. package/src/hashline/input.ts +16 -2
  20. package/src/hashline/parser.ts +12 -40
  21. package/src/hashline/types.ts +1 -2
  22. package/src/internal-urls/agent-protocol.ts +1 -0
  23. package/src/internal-urls/artifact-protocol.ts +1 -0
  24. package/src/internal-urls/docs-index.generated.ts +2 -1
  25. package/src/internal-urls/jobs-protocol.ts +1 -0
  26. package/src/internal-urls/local-protocol.ts +1 -0
  27. package/src/internal-urls/mcp-protocol.ts +1 -0
  28. package/src/internal-urls/memory-protocol.ts +1 -0
  29. package/src/internal-urls/pi-protocol.ts +1 -0
  30. package/src/internal-urls/router.ts +2 -1
  31. package/src/internal-urls/rule-protocol.ts +1 -0
  32. package/src/internal-urls/skill-protocol.ts +1 -0
  33. package/src/internal-urls/types.ts +18 -2
  34. package/src/mcp/transports/http.ts +49 -47
  35. package/src/prompts/system/custom-system-prompt.md +0 -2
  36. package/src/prompts/system/now-prompt.md +7 -0
  37. package/src/prompts/system/project-prompt.md +2 -0
  38. package/src/prompts/system/subagent-system-prompt.md +18 -9
  39. package/src/prompts/system/subagent-user-prompt.md +1 -10
  40. package/src/prompts/system/system-prompt.md +154 -233
  41. package/src/prompts/tools/bash.md +0 -24
  42. package/src/prompts/tools/eval.md +26 -13
  43. package/src/prompts/tools/hashline.md +1 -4
  44. package/src/sdk.ts +12 -22
  45. package/src/session/agent-session.ts +49 -17
  46. package/src/system-prompt.ts +38 -104
  47. package/src/task/executor.ts +15 -9
  48. package/src/task/index.ts +38 -33
  49. package/src/task/render.ts +4 -2
  50. package/src/tools/bash.ts +15 -41
  51. package/src/tools/eval.ts +13 -36
  52. package/src/tools/index.ts +0 -3
  53. package/src/tools/path-utils.ts +21 -1
  54. package/src/tools/read.ts +71 -49
  55. package/src/tools/search.ts +13 -1
  56. package/src/utils/file-display-mode.ts +11 -5
  57. package/src/workspace-tree.ts +210 -410
  58. package/src/task/template.ts +0 -47
  59. package/src/tools/bash-normalize.ts +0 -107
package/src/sdk.ts CHANGED
@@ -100,9 +100,7 @@ import { SessionManager } from "./session/session-manager";
100
100
  import { closeAllConnections } from "./ssh/connection-manager";
101
101
  import { unmountAll } from "./ssh/sshfs-mount";
102
102
  import {
103
- type AgentsMdSearch,
104
103
  type BuildSystemPromptResult,
105
- buildAgentsMdSearch,
106
104
  buildSystemPrompt as buildSystemPromptInternal,
107
105
  buildSystemPromptToolMetadata,
108
106
  loadProjectContextFiles as loadContextFilesInternal,
@@ -201,8 +199,6 @@ export interface CreateAgentSessionOptions {
201
199
  rules?: Rule[];
202
200
  /** Context files (AGENTS.md content). Default: discovered walking up from cwd */
203
201
  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
202
  /** Pre-built workspace tree (skips re-scanning; passed by parents to subagents). */
207
203
  workspaceTree?: WorkspaceTree;
208
204
  /** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
@@ -691,16 +687,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
691
687
  if (!options.modelRegistry) {
692
688
  modelRegistry.refreshInBackground();
693
689
  }
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);
700
- agentsMdSearchPromise.catch(() => {});
690
+ // Kick off workspace tree discovery early. The native workspace scan returns
691
+ // both the rendered-tree input and the AGENTS.md directory-context index, so
692
+ // startup does not perform a second recursive filesystem search. Subagents
693
+ // inherit the parent's resolved values via options.
694
+ const STARTUP_SCAN_DEADLINE_MS = 5000;
701
695
  const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
702
696
  ? Promise.resolve(options.workspaceTree)
703
- : logger.time("buildWorkspaceTree", buildWorkspaceTree, cwd);
697
+ : logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }));
704
698
  workspaceTreePromise.catch(() => {});
705
699
 
706
700
  // Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
@@ -898,12 +892,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
898
892
  return { ttsrManager, rulebookRules, alwaysApplyRules };
899
893
  });
900
894
 
901
- // Resolve contextFiles up-front (it's needed before tool creation). The agentsMd / workspace tree
902
- // scans are slowest on large repos and we MUST NOT block startup on them — race them against a
903
- // short deadline. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal will
904
- // re-race them through its own withDeadline path, and subagents will scan independently (still
905
- // cheaper than an unbounded parent hang). Background work continues so caches still warm.
906
- const STARTUP_SCAN_DEADLINE_MS = 5000;
895
+ // Resolve contextFiles up-front (it's needed before tool creation). The
896
+ // workspace tree scan is slow on large repos and we MUST NOT block startup on
897
+ // it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
898
+ // will re-race the same promise through its own withDeadline path. Background
899
+ // work continues so caches still warm.
907
900
  const raceWithDeadline = <T>(name: string, work: Promise<T>): Promise<T | undefined> =>
908
901
  Promise.race([
909
902
  work,
@@ -916,9 +909,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
916
909
  return undefined;
917
910
  }),
918
911
  ]);
919
- const [contextFiles, resolvedAgentsMdSearch, resolvedWorkspaceTree] = await Promise.all([
912
+ const [contextFiles, resolvedWorkspaceTree] = await Promise.all([
920
913
  contextFilesPromise,
921
- raceWithDeadline("buildAgentsMdSearch", agentsMdSearchPromise),
922
914
  raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
923
915
  ]);
924
916
 
@@ -1004,7 +996,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1004
996
  },
1005
997
  skipPythonPreflight: options.skipPythonPreflight,
1006
998
  contextFiles,
1007
- agentsMdSearch: resolvedAgentsMdSearch,
1008
999
  workspaceTree: resolvedWorkspaceTree,
1009
1000
  skills,
1010
1001
  eventBus,
@@ -1456,7 +1447,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1456
1447
  mcpDiscoveryServerSummaries: discoverableToolSummary.servers.map(formatDiscoverableMCPToolServerSummary),
1457
1448
  eagerTasks,
1458
1449
  secretsEnabled,
1459
- agentsMdSearch: agentsMdSearchPromise,
1460
1450
  workspaceTree: workspaceTreePromise,
1461
1451
  });
1462
1452
 
@@ -570,6 +570,7 @@ export class AgentSession {
570
570
  #agentId: string | undefined;
571
571
  #agentRegistry: AgentRegistry | undefined;
572
572
  #providerSessionId: string | undefined;
573
+ #isDisposed = false;
573
574
  // Extension system
574
575
  #extensionRunner: ExtensionRunner | undefined = undefined;
575
576
  #turnIndex = 0;
@@ -646,23 +647,32 @@ export class AgentSession {
646
647
  #hindsightSessionState: HindsightSessionState | undefined = undefined;
647
648
  readonly rawSseDebugBuffer: RawSseDebugBuffer;
648
649
 
649
- #startPowerAssertion(): void {
650
- if (process.platform !== "darwin") {
651
- return;
652
- }
650
+ #acquirePowerAssertion(): void {
651
+ if (process.platform !== "darwin") return;
652
+ if (this.#powerAssertion) return;
653
+ const idle = this.settings.get("power.preventIdleSleep");
654
+ const system = this.settings.get("power.preventSystemSleep");
655
+ const user = this.settings.get("power.declareUserActive");
656
+ const display = this.settings.get("power.preventDisplaySleep");
657
+ // All four off → user opted out; do nothing.
658
+ if (!idle && !system && !user && !display) return;
653
659
  try {
654
- this.#powerAssertion = MacOSPowerAssertion.start({ reason: "Oh My Pi agent session" });
660
+ this.#powerAssertion = MacOSPowerAssertion.start({
661
+ reason: "Oh My Pi agent session",
662
+ idle,
663
+ system,
664
+ user,
665
+ display,
666
+ });
655
667
  } catch (error) {
656
668
  logger.warn("Failed to acquire macOS power assertion", { error: String(error) });
657
669
  }
658
670
  }
659
671
 
660
- #stopPowerAssertion(): void {
672
+ #releasePowerAssertion(): void {
661
673
  const assertion = this.#powerAssertion;
662
674
  this.#powerAssertion = undefined;
663
- if (!assertion) {
664
- return;
665
- }
675
+ if (!assertion) return;
666
676
  try {
667
677
  assertion.stop();
668
678
  } catch (error) {
@@ -670,11 +680,30 @@ export class AgentSession {
670
680
  }
671
681
  }
672
682
 
683
+ #beginInFlight(): void {
684
+ this.#promptInFlightCount++;
685
+ if (this.#promptInFlightCount === 1) {
686
+ this.#acquirePowerAssertion();
687
+ }
688
+ }
689
+
690
+ #endInFlight(): void {
691
+ this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
692
+ if (this.#promptInFlightCount === 0) {
693
+ this.#releasePowerAssertion();
694
+ }
695
+ }
696
+
697
+ #resetInFlight(): void {
698
+ this.#promptInFlightCount = 0;
699
+ this.#releasePowerAssertion();
700
+ }
701
+
673
702
  constructor(config: AgentSessionConfig) {
674
703
  this.agent = config.agent;
675
704
  this.sessionManager = config.sessionManager;
676
705
  this.settings = config.settings;
677
- this.#startPowerAssertion();
706
+ // Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
678
707
  this.#asyncJobManager = config.asyncJobManager;
679
708
  this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
680
709
  this.#scopedModels = config.scopedModels ?? [];
@@ -2108,6 +2137,8 @@ export class AgentSession {
2108
2137
  * Call this when completely done with the session.
2109
2138
  */
2110
2139
  async dispose(): Promise<void> {
2140
+ this.#isDisposed = true;
2141
+ this.#pendingBackgroundExchanges = [];
2111
2142
  this.#evalExecutionDisposing = true;
2112
2143
  try {
2113
2144
  if (this.#extensionRunner?.hasHandlers("session_shutdown")) {
@@ -2130,7 +2161,7 @@ export class AgentSession {
2130
2161
  );
2131
2162
  }
2132
2163
  await disposeKernelSessionsByOwner(this.#evalKernelOwnerId);
2133
- this.#stopPowerAssertion();
2164
+ this.#releasePowerAssertion();
2134
2165
  await this.sessionManager.close();
2135
2166
  this.#closeAllProviderSessions("dispose");
2136
2167
  const hindsightState = this.setHindsightSessionState(undefined);
@@ -3171,7 +3202,7 @@ export class AgentSession {
3171
3202
  skipPostPromptRecoveryWait?: boolean;
3172
3203
  },
3173
3204
  ): Promise<void> {
3174
- this.#promptInFlightCount++;
3205
+ this.#beginInFlight();
3175
3206
  const generation = this.#promptGeneration;
3176
3207
  try {
3177
3208
  // Flush any pending bash messages before the new prompt
@@ -3291,7 +3322,7 @@ export class AgentSession {
3291
3322
  await this.#waitForPostPromptRecovery();
3292
3323
  }
3293
3324
  } finally {
3294
- this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
3325
+ this.#endInFlight();
3295
3326
  }
3296
3327
  }
3297
3328
 
@@ -3877,7 +3908,7 @@ export class AgentSession {
3877
3908
  // Clear prompt-in-flight state: waitForIdle resolves when the agent loop's finally
3878
3909
  // block runs, but nested prompt setup/finalizers may still be unwinding. Without this,
3879
3910
  // a subsequent prompt() can incorrectly observe the session as busy after an abort.
3880
- this.#promptInFlightCount = 0;
3911
+ this.#resetInFlight();
3881
3912
  // Safety net: if the agent loop aborted without producing an assistant
3882
3913
  // message (e.g. failed before the first stream), the in-flight yield was
3883
3914
  // never resolved or rejected by the normal message_end path. Reject it now
@@ -4699,7 +4730,7 @@ export class AgentSession {
4699
4730
  if (handoffSignal.aborted) {
4700
4731
  throw new Error("Handoff cancelled");
4701
4732
  }
4702
- this.#promptInFlightCount++;
4733
+ this.#beginInFlight();
4703
4734
  try {
4704
4735
  this.agent.setSystemPrompt(this.#baseSystemPrompt);
4705
4736
  await this.#promptAgentWithIdleRetry([
@@ -4711,7 +4742,7 @@ export class AgentSession {
4711
4742
  },
4712
4743
  ]);
4713
4744
  } finally {
4714
- this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
4745
+ this.#endInFlight();
4715
4746
  }
4716
4747
  await completionPromise;
4717
4748
 
@@ -6853,7 +6884,8 @@ export class AgentSession {
6853
6884
  if (this.#scheduledBackgroundExchangeFlush) return;
6854
6885
  this.#scheduledBackgroundExchangeFlush = true;
6855
6886
  const attempt = (): void => {
6856
- if (this.#pendingBackgroundExchanges.length === 0) {
6887
+ if (this.#pendingBackgroundExchanges.length === 0 || this.#isDisposed) {
6888
+ this.#pendingBackgroundExchanges = [];
6857
6889
  this.#scheduledBackgroundExchangeFlush = false;
6858
6890
  return;
6859
6891
  }
@@ -4,7 +4,6 @@
4
4
 
5
5
  import * as os from "node:os";
6
6
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
7
- import { FileType, glob } from "@oh-my-pi/pi-natives";
8
7
  import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
9
8
  import { $ } from "bun";
10
9
  import { contextFileCapability } from "./capability/context-file";
@@ -13,9 +12,11 @@ import type { SkillsSettings } from "./config/settings";
13
12
  import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
14
13
  import { loadSkills, type Skill } from "./extensibility/skills";
15
14
  import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
15
+ import nowPromptTemplate from "./prompts/system/now-prompt.md" with { type: "text" };
16
16
  import projectPromptTemplate from "./prompts/system/project-prompt.md" with { type: "text" };
17
17
  import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
18
- import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
18
+ import { shortenPath } from "./tools/render-utils";
19
+ import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
19
20
 
20
21
  interface AlwaysApplyRule {
21
22
  name: string;
@@ -84,58 +85,7 @@ function parseWmicTable(output: string, header: string): string | null {
84
85
  return filtered[0] ?? null;
85
86
  }
86
87
 
87
- const AGENTS_MD_MIN_DEPTH = 1;
88
- const AGENTS_MD_MAX_DEPTH = 4;
89
- const AGENTS_MD_LIMIT = 200;
90
88
  const SYSTEM_PROMPT_PREP_TIMEOUT_MS = 5000;
91
- const AGENTS_MD_EXCLUDED_DIRS = new Set(["node_modules", ".git"]);
92
-
93
- export interface AgentsMdSearch {
94
- scopePath: string;
95
- limit: number;
96
- pattern: string;
97
- files: string[];
98
- }
99
-
100
- async function listAgentsMdFiles(root: string, limit: number): Promise<string[]> {
101
- try {
102
- const result = await glob({
103
- pattern: "**/AGENTS.md",
104
- path: root,
105
- fileType: FileType.File,
106
- recursive: true,
107
- hidden: false,
108
- gitignore: true,
109
- maxResults: limit * 4,
110
- cache: true,
111
- });
112
- const files: string[] = [];
113
- for (const m of result.matches) {
114
- const rel = m.path.replace(/\\/g, "/");
115
- if (!rel?.endsWith("AGENTS.md")) continue;
116
- const segments = rel.split("/");
117
- const depth = segments.length - 1;
118
- if (depth < AGENTS_MD_MIN_DEPTH || depth > AGENTS_MD_MAX_DEPTH) continue;
119
- const dirSegments = segments.slice(0, -1);
120
- if (dirSegments.some(seg => AGENTS_MD_EXCLUDED_DIRS.has(seg) || seg.startsWith("."))) continue;
121
- files.push(rel);
122
- if (files.length >= limit) break;
123
- }
124
- return Array.from(new Set(files)).sort().slice(0, limit);
125
- } catch {
126
- return [];
127
- }
128
- }
129
-
130
- export async function buildAgentsMdSearch(cwd: string): Promise<AgentsMdSearch> {
131
- const files = await listAgentsMdFiles(cwd, AGENTS_MD_LIMIT);
132
- return {
133
- scopePath: ".",
134
- limit: AGENTS_MD_LIMIT,
135
- pattern: `AGENTS.md depth ${AGENTS_MD_MIN_DEPTH}-${AGENTS_MD_MAX_DEPTH}`,
136
- files,
137
- };
138
- }
139
89
 
140
90
  async function getGpuModel(): Promise<string | null> {
141
91
  switch (process.platform) {
@@ -409,8 +359,6 @@ export interface BuildSystemPromptOptions {
409
359
  alwaysApplyRules?: AlwaysApplyRule[];
410
360
  /** Whether secret obfuscation is active. When true, explains the redaction format in the prompt. */
411
361
  secretsEnabled?: boolean;
412
- /** Pre-loaded AGENTS.md search (skips discovery if provided). May be a Promise to allow early kick-off. */
413
- agentsMdSearch?: AgentsMdSearch | Promise<AgentsMdSearch>;
414
362
  /** Pre-loaded workspace tree (skips discovery if provided). May be a Promise to allow early kick-off. */
415
363
  workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
416
364
  }
@@ -444,7 +392,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
444
392
  mcpDiscoveryServerSummaries = [],
445
393
  eagerTasks = false,
446
394
  secretsEnabled = false,
447
- agentsMdSearch: providedAgentsMdSearch,
448
395
  workspaceTree: providedWorkspaceTree,
449
396
  } = options;
450
397
  const resolvedCwd = cwd ?? getProjectDir();
@@ -454,18 +401,13 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
454
401
  resolvedAppendPrompt: undefined as string | undefined,
455
402
  systemPromptCustomization: null as string | null,
456
403
  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
404
  skills: providedSkills ?? ([] as Skill[]),
464
405
  workspaceTree: {
465
406
  rootPath: resolvedCwd,
466
407
  rendered: "",
467
408
  truncated: false,
468
409
  totalLines: 0,
410
+ agentsMdFiles: [],
469
411
  } satisfies WorkspaceTree,
470
412
  };
471
413
 
@@ -503,14 +445,12 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
503
445
  const contextFilesPromise = providedContextFiles
504
446
  ? Promise.resolve(providedContextFiles)
505
447
  : logger.time("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
506
- const agentsMdSearchPromise =
507
- providedAgentsMdSearch !== undefined
508
- ? Promise.resolve(providedAgentsMdSearch)
509
- : logger.time("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
510
448
  const workspaceTreePromise =
511
449
  providedWorkspaceTree !== undefined
512
450
  ? Promise.resolve(providedWorkspaceTree)
513
- : logger.time("buildWorkspaceTree", buildWorkspaceTree, resolvedCwd);
451
+ : logger.time("buildWorkspaceTree", () =>
452
+ buildWorkspaceTree(resolvedCwd, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }),
453
+ );
514
454
  const skillsPromise: Promise<Skill[]> =
515
455
  providedSkills !== undefined
516
456
  ? Promise.resolve(providedSkills)
@@ -518,33 +458,30 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
518
458
  ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
519
459
  : Promise.resolve([]);
520
460
 
521
- const [
522
- resolvedCustomPrompt,
523
- resolvedAppendPrompt,
524
- systemPromptCustomization,
525
- contextFiles,
526
- agentsMdSearch,
527
- skills,
528
- workspaceTree,
529
- ] = await Promise.all([
530
- withDeadline(
531
- "customPrompt",
532
- resolvePromptInput(customPrompt, "system prompt"),
533
- prepDefaults.resolvedCustomPrompt,
534
- ),
535
- withDeadline(
536
- "appendSystemPrompt",
537
- resolvePromptInput(appendSystemPrompt, "append system prompt"),
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),
547
- ]);
461
+ const [resolvedCustomPrompt, resolvedAppendPrompt, systemPromptCustomization, contextFiles, skills, workspaceTree] =
462
+ await Promise.all([
463
+ withDeadline(
464
+ "customPrompt",
465
+ resolvePromptInput(customPrompt, "system prompt"),
466
+ prepDefaults.resolvedCustomPrompt,
467
+ ),
468
+ withDeadline(
469
+ "appendSystemPrompt",
470
+ resolvePromptInput(appendSystemPrompt, "append system prompt"),
471
+ prepDefaults.resolvedAppendPrompt,
472
+ ),
473
+ withDeadline(
474
+ "loadSystemPromptFiles",
475
+ systemPromptCustomizationPromise,
476
+ prepDefaults.systemPromptCustomization,
477
+ ),
478
+ withDeadline("loadProjectContextFiles", contextFilesPromise, prepDefaults.contextFiles).then(
479
+ dedupeExactContextFiles,
480
+ ),
481
+ withDeadline("loadSkills", skillsPromise, prepDefaults.skills),
482
+ withDeadline("buildWorkspaceTree", workspaceTreePromise, prepDefaults.workspaceTree),
483
+ ]);
484
+ const agentsMdFiles = Array.from(new Set(workspaceTree.agentsMdFiles)).sort().slice(0, AGENTS_MD_LIMIT);
548
485
 
549
486
  if (timedOut.length > 0) {
550
487
  logger.warn("System prompt preparation steps timed out; using minimal fallback for those steps", {
@@ -568,7 +505,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
568
505
 
569
506
  const date = new Date().toISOString().slice(0, 10);
570
507
  const dateTime = date;
571
- const promptCwd = resolvedCwd.replace(/\\/g, "/");
508
+ const promptCwd = shortenPath(resolvedCwd.replace(/\\/g, "/"));
572
509
 
573
510
  // Build tool metadata for system prompt rendering
574
511
  // Priority: explicit list > tools map > defaults
@@ -606,7 +543,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
606
543
  const injectedAlwaysApplyRules = dedupeAlwaysApplyRules(alwaysApplyRules, promptSources);
607
544
 
608
545
  const environment = await logger.time("getEnvironmentInfo", getEnvironmentInfo);
609
- const reportToolIssueToolName = toolPromptNames.get("report_tool_issue") ?? "report_tool_issue";
610
546
  const data = {
611
547
  systemPromptCustomization: effectiveSystemPromptCustomization,
612
548
  customPrompt: resolvedCustomPrompt,
@@ -617,7 +553,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
617
553
  toolRefs,
618
554
  environment,
619
555
  contextFiles,
620
- agentsMdSearch,
556
+ agentsMdSearch: { files: agentsMdFiles },
621
557
  workspaceTree,
622
558
  skills: filteredSkills,
623
559
  rules: rules ?? [],
@@ -633,18 +569,16 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
633
569
  eagerTasks,
634
570
  secretsEnabled,
635
571
  };
636
- let rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
637
-
638
- // When autoqa is active the report_tool_issue tool is in the tool set — nudge the agent.
639
- if (toolNames.includes("report_tool_issue")) {
640
- 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>`;
641
- }
642
-
572
+ const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
643
573
  const systemPrompt = [rendered];
644
574
  const projectPrompt = resolvedCustomPrompt ? "" : prompt.render(projectPromptTemplate, data).trim();
645
575
  if (projectPrompt) {
646
576
  systemPrompt.push(projectPrompt);
647
577
  }
578
+ const nowPrompt = prompt.render(nowPromptTemplate, data).trim();
579
+ if (nowPrompt) {
580
+ systemPrompt.push(nowPrompt);
581
+ }
648
582
 
649
583
  return { systemPrompt };
650
584
  }
@@ -28,7 +28,6 @@ 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";
32
31
  import { type ContextFileEntry, truncateTail } from "../tools";
33
32
  import { jtdToJsonSchema, normalizeSchema } from "../tools/jtd-to-json-schema";
34
33
  import { ToolAbortError } from "../tools/tool-errors";
@@ -140,6 +139,7 @@ export interface ExecutorOptions {
140
139
  agent: AgentDefinition;
141
140
  task: string;
142
141
  assignment?: string;
142
+ context?: string;
143
143
  description?: string;
144
144
  index: number;
145
145
  id: string;
@@ -165,7 +165,6 @@ export interface ExecutorOptions {
165
165
  contextFiles?: ContextFileEntry[];
166
166
  skills?: Skill[];
167
167
  promptTemplates?: PromptTemplate[];
168
- agentsMdSearch?: AgentsMdSearch;
169
168
  workspaceTree?: WorkspaceTree;
170
169
  mcpManager?: MCPManager;
171
170
  authStorage?: AuthStorage;
@@ -941,8 +940,13 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
941
940
  checkAbort();
942
941
  const authStorage = options.authStorage ?? (await discoverAuthStorage());
943
942
  checkAbort();
943
+ const registryFromParent = options.modelRegistry !== undefined;
944
944
  const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
945
- await modelRegistry.refresh();
945
+ if (!registryFromParent) {
946
+ await modelRegistry.refresh();
947
+ } else {
948
+ logger.debug("runSubagent: reusing parent modelRegistry; skipping refresh");
949
+ }
946
950
  checkAbort();
947
951
 
948
952
  const {
@@ -990,19 +994,21 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
990
994
  contextFiles: options.contextFiles,
991
995
  skills: options.skills,
992
996
  promptTemplates: options.promptTemplates,
993
- agentsMdSearch: options.agentsMdSearch,
994
997
  workspaceTree: options.workspaceTree,
995
- systemPrompt: defaultPrompt => [
996
- prompt.render(subagentSystemPromptTemplate, {
997
- base: defaultPrompt.join("\n\n"),
998
+ systemPrompt: defaultPrompt => {
999
+ const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
998
1000
  agent: agent.systemPrompt,
1001
+ context: options.context?.trim() ?? "",
999
1002
  worktree: worktree ?? "",
1000
1003
  outputSchema: normalizedOutputSchema,
1001
1004
  contextFile: options.contextFile,
1002
1005
  ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
1003
1006
  ircSelfId: ircEnabled ? id : "",
1004
- }),
1005
- ],
1007
+ });
1008
+ return defaultPrompt.length === 0
1009
+ ? [subagentPrompt]
1010
+ : [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
1011
+ },
1006
1012
  sessionManager,
1007
1013
  hasUI: false,
1008
1014
  spawns: spawnsEnv,