@oh-my-pi/pi-coding-agent 16.3.14 → 16.4.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 +35 -0
- package/dist/cli.js +3053 -3158
- package/dist/types/advisor/config.d.ts +7 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +28 -15
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +7 -2
- package/dist/types/dap/config.d.ts +13 -1
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/lsp/config.d.ts +7 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/transports/stdio.d.ts +8 -3
- package/dist/types/modes/components/hook-selector.d.ts +2 -0
- package/dist/types/modes/theme/defaults/index.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +3 -2
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +5 -3
- package/dist/types/session/session-context.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +4 -0
- package/dist/types/thinking.d.ts +6 -3
- package/dist/types/utils/file-display-mode.d.ts +1 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/config.test.ts +84 -0
- package/src/advisor/config.ts +28 -0
- package/src/cli/args.ts +1 -0
- package/src/cli/flag-tables.ts +3 -0
- package/src/config/model-registry.ts +1 -1
- package/src/config/model-resolver.ts +14 -12
- package/src/config/models-config-schema.ts +3 -2
- package/src/config/settings-schema.ts +5 -2
- package/src/dap/config.ts +136 -39
- package/src/dap/defaults.json +1 -1
- package/src/eval/js/shared/runtime.ts +20 -4
- package/src/eval/js/worker-core.ts +9 -2
- package/src/exec/bash-executor.ts +8 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/lsp/config.ts +27 -13
- package/src/lsp/index.ts +114 -29
- package/src/main.ts +21 -1
- package/src/mcp/transports/stdio.test.ts +12 -0
- package/src/mcp/transports/stdio.ts +14 -8
- package/src/modes/components/hook-selector.ts +9 -2
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/input-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/theme/defaults/dark-poimandres.json +6 -5
- package/src/modes/theme/defaults/light-poimandres.json +6 -5
- package/src/modes/theme/theme-schema.json +6 -2
- package/src/modes/theme/theme.ts +24 -18
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/{explore.md → scout.md} +3 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/debug.md +4 -4
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/task.md +2 -2
- package/src/sdk.ts +68 -45
- package/src/session/agent-session.ts +215 -64
- package/src/session/session-context.test.ts +83 -0
- package/src/session/session-context.ts +1 -0
- package/src/session/session-entries.ts +4 -0
- package/src/session/session-manager.ts +9 -1
- package/src/system-prompt.test.ts +20 -11
- package/src/task/agents.ts +2 -5
- package/src/task/executor.ts +111 -74
- package/src/thinking.ts +16 -7
- package/src/tools/ask.ts +51 -13
- package/src/tools/browser/cmux/cmux-tab.ts +21 -12
- package/src/tools/debug.ts +41 -11
- package/src/utils/file-display-mode.ts +1 -1
- package/src/prompts/agents/tester.md +0 -111
package/src/lsp/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { $which, isRecord, logger, pathIsWithin } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { $which, isRecord, logger, pathIsWithin, type WhichOptions } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { YAML } from "bun";
|
|
6
6
|
import { getConfigDirPaths } from "../config";
|
|
7
7
|
import { type ClaudePluginRoot, getPreloadedPluginRoots } from "../discovery/helpers";
|
|
@@ -249,7 +249,7 @@ const LOCAL_BIN_PATHS: Array<{ markers: string[]; binDir: string }> = [
|
|
|
249
249
|
{ markers: ["Gemfile", "Gemfile.lock"], binDir: "vendor/bundle/bin" },
|
|
250
250
|
{ markers: ["Gemfile", "Gemfile.lock"], binDir: "bin" },
|
|
251
251
|
// Go - check project-local bin
|
|
252
|
-
{ markers: ["go.mod", "go.sum"], binDir: "bin" },
|
|
252
|
+
{ markers: ["go.mod", "go.sum", "go.work"], binDir: "bin" },
|
|
253
253
|
];
|
|
254
254
|
|
|
255
255
|
const WINDOWS_LOCAL_EXECUTABLE_EXTENSIONS = [".exe", ".cmd", ".bat"] as const;
|
|
@@ -267,6 +267,21 @@ function resolveLocalCommand(basePath: string): string | null {
|
|
|
267
267
|
return null;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
function resolveCommandFromLocalRoot(command: string, cwd: string): string | null {
|
|
271
|
+
for (const { markers, binDir } of LOCAL_BIN_PATHS) {
|
|
272
|
+
if (!hasRootMarkers(cwd, markers)) continue;
|
|
273
|
+
const resolved = resolveLocalCommand(path.join(cwd, binDir, command));
|
|
274
|
+
if (resolved) return resolved;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Controls project-local and PATH executable lookup. */
|
|
280
|
+
export interface ResolveCommandOptions extends Pick<WhichOptions, "cache" | "PATH"> {
|
|
281
|
+
/** Ordered project roots checked before PATH; defaults to the command cwd. */
|
|
282
|
+
localRoots?: readonly string[];
|
|
283
|
+
}
|
|
284
|
+
|
|
270
285
|
/**
|
|
271
286
|
* Resolve a command to an executable path.
|
|
272
287
|
* Checks project-local bin directories first, then falls back to $PATH.
|
|
@@ -275,20 +290,19 @@ function resolveLocalCommand(basePath: string): string | null {
|
|
|
275
290
|
* @param cwd - Working directory to search from
|
|
276
291
|
* @returns Absolute path to the executable, or null if not found
|
|
277
292
|
*/
|
|
278
|
-
export function resolveCommand(command: string, cwd: string): string | null {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const resolvedLocalPath = resolveLocalCommand(localPath);
|
|
284
|
-
if (resolvedLocalPath) {
|
|
285
|
-
return resolvedLocalPath;
|
|
286
|
-
}
|
|
293
|
+
export function resolveCommand(command: string, cwd: string, options?: ResolveCommandOptions): string | null {
|
|
294
|
+
if (options?.localRoots) {
|
|
295
|
+
for (const root of options.localRoots) {
|
|
296
|
+
const resolved = resolveCommandFromLocalRoot(command, root);
|
|
297
|
+
if (resolved) return resolved;
|
|
287
298
|
}
|
|
299
|
+
} else {
|
|
300
|
+
const resolved = resolveCommandFromLocalRoot(command, cwd);
|
|
301
|
+
if (resolved) return resolved;
|
|
288
302
|
}
|
|
289
303
|
|
|
290
|
-
|
|
291
|
-
return $which(command);
|
|
304
|
+
if (!options) return $which(command);
|
|
305
|
+
return $which(command, { cache: options.cache, PATH: options.PATH });
|
|
292
306
|
}
|
|
293
307
|
|
|
294
308
|
interface ConfigSource {
|
package/src/lsp/index.ts
CHANGED
|
@@ -573,8 +573,79 @@ interface ProjectType {
|
|
|
573
573
|
description: string;
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
+
/** Convert a `go.work` use directory into the package pattern `go build` needs. */
|
|
577
|
+
function goWorkspaceBuildPattern(diskPath: string): string | null {
|
|
578
|
+
const trimmed = diskPath.trim();
|
|
579
|
+
if (!trimmed) return null;
|
|
580
|
+
|
|
581
|
+
const isAbsolute = path.isAbsolute(trimmed) || path.win32.isAbsolute(trimmed);
|
|
582
|
+
const normalized = trimmed.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
583
|
+
const dir = normalized || ".";
|
|
584
|
+
if (dir === ".") return "./...";
|
|
585
|
+
if (dir.endsWith("/...")) return dir;
|
|
586
|
+
if (isAbsolute || dir.startsWith("./") || dir.startsWith("../")) return `${dir}/...`;
|
|
587
|
+
return `./${dir}/...`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** Parse `go work edit -json` output into per-module package patterns. */
|
|
591
|
+
function parseGoWorkspaceBuildPatterns(output: string): string[] {
|
|
592
|
+
let parsed: unknown;
|
|
593
|
+
try {
|
|
594
|
+
parsed = JSON.parse(output);
|
|
595
|
+
} catch {
|
|
596
|
+
return [];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!parsed || typeof parsed !== "object" || !("Use" in parsed) || !Array.isArray(parsed.Use)) return [];
|
|
600
|
+
|
|
601
|
+
const patterns = new Set<string>();
|
|
602
|
+
for (const entry of parsed.Use) {
|
|
603
|
+
if (!entry || typeof entry !== "object" || !("DiskPath" in entry) || typeof entry.DiskPath !== "string") {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const pattern = goWorkspaceBuildPattern(entry.DiskPath);
|
|
607
|
+
if (pattern) patterns.add(pattern);
|
|
608
|
+
}
|
|
609
|
+
return [...patterns];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Resolve the `go build` command for a `go.work` workspace. */
|
|
613
|
+
async function resolveGoWorkspaceDiagnosticsCommand(cwd: string, signal?: AbortSignal): Promise<string[]> {
|
|
614
|
+
const fallback = ["go", "build", "./..."];
|
|
615
|
+
try {
|
|
616
|
+
const proc = Bun.spawn(["go", "work", "edit", "-json"], {
|
|
617
|
+
cwd,
|
|
618
|
+
stdout: "pipe",
|
|
619
|
+
stderr: "pipe",
|
|
620
|
+
windowsHide: true,
|
|
621
|
+
});
|
|
622
|
+
const abortHandler = () => {
|
|
623
|
+
proc.kill();
|
|
624
|
+
};
|
|
625
|
+
if (signal) {
|
|
626
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
const [stdout] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
631
|
+
const exitCode = await proc.exited;
|
|
632
|
+
throwIfAborted(signal);
|
|
633
|
+
if (exitCode !== 0) return fallback;
|
|
634
|
+
const patterns = parseGoWorkspaceBuildPatterns(stdout);
|
|
635
|
+
return patterns.length > 0 ? ["go", "build", ...patterns] : fallback;
|
|
636
|
+
} finally {
|
|
637
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
if (signal?.aborted) {
|
|
641
|
+
throw new ToolAbortError();
|
|
642
|
+
}
|
|
643
|
+
return fallback;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
576
647
|
/** Detect project type from root markers */
|
|
577
|
-
function detectProjectType(cwd: string): ProjectType {
|
|
648
|
+
async function detectProjectType(cwd: string, signal?: AbortSignal): Promise<ProjectType> {
|
|
578
649
|
// Check for Rust (Cargo.toml)
|
|
579
650
|
if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
|
|
580
651
|
return { type: "rust", command: ["cargo", "check", "--message-format=short"], description: "Rust (cargo check)" };
|
|
@@ -585,6 +656,15 @@ function detectProjectType(cwd: string): ProjectType {
|
|
|
585
656
|
return { type: "typescript", command: ["npx", "tsc", "--noEmit"], description: "TypeScript (tsc --noEmit)" };
|
|
586
657
|
}
|
|
587
658
|
|
|
659
|
+
// Check for Go workspaces before single-module Go projects.
|
|
660
|
+
if (fs.existsSync(path.join(cwd, "go.work"))) {
|
|
661
|
+
return {
|
|
662
|
+
type: "go",
|
|
663
|
+
command: await resolveGoWorkspaceDiagnosticsCommand(cwd, signal),
|
|
664
|
+
description: "Go workspace (go build)",
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
588
668
|
// Check for Go (go.mod)
|
|
589
669
|
if (fs.existsSync(path.join(cwd, "go.mod"))) {
|
|
590
670
|
return { type: "go", command: ["go", "build", "./..."], description: "Go (go build)" };
|
|
@@ -604,47 +684,52 @@ async function runWorkspaceDiagnostics(
|
|
|
604
684
|
signal?: AbortSignal,
|
|
605
685
|
): Promise<{ output: string; projectType: ProjectType }> {
|
|
606
686
|
throwIfAborted(signal);
|
|
607
|
-
const projectType = detectProjectType(cwd);
|
|
687
|
+
const projectType = await detectProjectType(cwd, signal);
|
|
608
688
|
if (!projectType.command) {
|
|
609
689
|
return {
|
|
610
|
-
output: `Cannot detect project type. Supported: Rust (Cargo.toml), TypeScript (tsconfig.json), Go (go.mod), Python (pyproject.toml)`,
|
|
690
|
+
output: `Cannot detect project type. Supported: Rust (Cargo.toml), TypeScript (tsconfig.json), Go (go.work/go.mod), Python (pyproject.toml)`,
|
|
611
691
|
projectType,
|
|
612
692
|
};
|
|
613
693
|
}
|
|
614
|
-
const proc = Bun.spawn(projectType.command, {
|
|
615
|
-
cwd,
|
|
616
|
-
stdout: "pipe",
|
|
617
|
-
stderr: "pipe",
|
|
618
|
-
windowsHide: true,
|
|
619
|
-
});
|
|
620
|
-
const abortHandler = () => {
|
|
621
|
-
proc.kill();
|
|
622
|
-
};
|
|
623
|
-
if (signal) {
|
|
624
|
-
signal.addEventListener("abort", abortHandler, { once: true });
|
|
625
|
-
}
|
|
626
|
-
|
|
627
694
|
try {
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
695
|
+
const proc = Bun.spawn(projectType.command, {
|
|
696
|
+
cwd,
|
|
697
|
+
stdout: "pipe",
|
|
698
|
+
stderr: "pipe",
|
|
699
|
+
windowsHide: true,
|
|
700
|
+
});
|
|
701
|
+
const abortHandler = () => {
|
|
702
|
+
proc.kill();
|
|
703
|
+
};
|
|
704
|
+
if (signal) {
|
|
705
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
634
706
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
const [stdout, stderr] = await Promise.all([
|
|
710
|
+
new Response(proc.stdout).text(),
|
|
711
|
+
new Response(proc.stderr).text(),
|
|
712
|
+
]);
|
|
713
|
+
await proc.exited;
|
|
714
|
+
throwIfAborted(signal);
|
|
715
|
+
const combined = (stdout + stderr).trim();
|
|
716
|
+
if (!combined) {
|
|
717
|
+
return { output: "No issues found", projectType };
|
|
718
|
+
}
|
|
719
|
+
// Limit output length
|
|
720
|
+
const lines = combined.split("\n");
|
|
721
|
+
if (lines.length > 50) {
|
|
722
|
+
return { output: `${lines.slice(0, 50).join("\n")}\n[…${lines.length - 50}ln elided…]`, projectType };
|
|
723
|
+
}
|
|
724
|
+
return { output: combined, projectType };
|
|
725
|
+
} finally {
|
|
726
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
639
727
|
}
|
|
640
|
-
return { output: combined, projectType };
|
|
641
728
|
} catch (e) {
|
|
642
729
|
if (signal?.aborted) {
|
|
643
730
|
throw new ToolAbortError();
|
|
644
731
|
}
|
|
645
732
|
return { output: `Failed to run ${projectType.command.join(" ")}: ${e}`, projectType };
|
|
646
|
-
} finally {
|
|
647
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
648
733
|
}
|
|
649
734
|
}
|
|
650
735
|
|
package/src/main.ts
CHANGED
|
@@ -789,7 +789,8 @@ export function applyResolvedSystemPromptInputs(
|
|
|
789
789
|
}
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
-
|
|
792
|
+
/** Builds startup session options from parsed CLI flags, scoped models, and resolved session lineage. */
|
|
793
|
+
export async function buildSessionOptions(
|
|
793
794
|
parsed: Args,
|
|
794
795
|
scopedModels: ScopedModel[],
|
|
795
796
|
sessionManager: SessionManager | undefined,
|
|
@@ -820,6 +821,25 @@ async function buildSessionOptions(
|
|
|
820
821
|
if (parsed.providerSessionId) {
|
|
821
822
|
options.providerSessionId = parsed.providerSessionId;
|
|
822
823
|
}
|
|
824
|
+
if (parsed.providerPromptCacheKey) {
|
|
825
|
+
options.providerPromptCacheKey = parsed.providerPromptCacheKey;
|
|
826
|
+
options.providerPromptCacheKeySource = "explicit";
|
|
827
|
+
} else {
|
|
828
|
+
const header = sessionManager?.getHeader();
|
|
829
|
+
const scopedModelOverride = scopedModels.length > 0 && !parsed.continue && !parsed.resume;
|
|
830
|
+
const forkCacheShapeChanged =
|
|
831
|
+
scopedModelOverride ||
|
|
832
|
+
parsed.model !== undefined ||
|
|
833
|
+
parsed.thinking !== undefined ||
|
|
834
|
+
parsed.systemPrompt !== undefined ||
|
|
835
|
+
parsed.appendSystemPrompt !== undefined ||
|
|
836
|
+
parsed.tools !== undefined ||
|
|
837
|
+
parsed.noTools === true;
|
|
838
|
+
if (!forkCacheShapeChanged && header?.providerPromptCacheKey) {
|
|
839
|
+
options.providerPromptCacheKey = header.providerPromptCacheKey;
|
|
840
|
+
options.providerPromptCacheKeySource = "fork";
|
|
841
|
+
}
|
|
842
|
+
}
|
|
823
843
|
|
|
824
844
|
// Model from CLI
|
|
825
845
|
// - supports --provider <name> --model <pattern>
|
|
@@ -31,6 +31,18 @@ describe("resolveStdioSpawnCommand", () => {
|
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
it("keeps Darwin stdio MCP servers attached so TCC Apple Events prompts can resolve", async () => {
|
|
35
|
+
await expect(
|
|
36
|
+
resolveStdioSpawnCommand(
|
|
37
|
+
{ command: "xcrun", args: ["mcpbridge"] },
|
|
38
|
+
{ cwd: process.cwd(), env: {}, platform: "darwin" },
|
|
39
|
+
),
|
|
40
|
+
).resolves.toEqual({
|
|
41
|
+
cmd: ["xcrun", "mcpbridge"],
|
|
42
|
+
detached: false,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
34
46
|
it("detaches off-Windows MCP servers so terminal job-control signals cannot stop them", async () => {
|
|
35
47
|
await expect(
|
|
36
48
|
resolveStdioSpawnCommand(
|
|
@@ -37,13 +37,18 @@ export interface StdioSpawnCommand {
|
|
|
37
37
|
*/
|
|
38
38
|
windowsHide?: boolean;
|
|
39
39
|
/**
|
|
40
|
-
* Run the subprocess in its own session.
|
|
40
|
+
* Run the subprocess in its own session when the platform can safely do so.
|
|
41
41
|
*
|
|
42
|
-
* POSIX: `true`. Detach → `setsid`, so the MCP process tree has
|
|
43
|
-
* controlling terminal and terminal job-control signals (Ctrl+Z SIGTSTP,
|
|
42
|
+
* Linux/other POSIX: `true`. Detach → `setsid`, so the MCP process tree has
|
|
43
|
+
* no controlling terminal and terminal job-control signals (Ctrl+Z SIGTSTP,
|
|
44
44
|
* background-read SIGTTIN) cannot stop stdio servers such as
|
|
45
45
|
* `chrome-devtools-mcp` and leave our read loop blocked on silent pipes.
|
|
46
46
|
*
|
|
47
|
+
* macOS: `false`. LaunchServices/TCC attributes Apple Events automation to
|
|
48
|
+
* the responsible terminal process only while the child stays in the
|
|
49
|
+
* inherited session; detaching via `setsid` prevents the permission prompt
|
|
50
|
+
* for servers such as `xcrun mcpbridge` (#4987).
|
|
51
|
+
*
|
|
47
52
|
* Windows: `false`. There is no SIGTSTP/SIGTTIN to escape, and Windows
|
|
48
53
|
* wrapper chains must stay in the OMP console session so nested console
|
|
49
54
|
* grandchildren keep stdout routed through our pipe (#3544).
|
|
@@ -247,7 +252,7 @@ export async function resolveStdioSpawnCommand(
|
|
|
247
252
|
options: ResolveStdioSpawnOptions,
|
|
248
253
|
): Promise<StdioSpawnCommand> {
|
|
249
254
|
const args = config.args ?? [];
|
|
250
|
-
if (options.platform !== "win32") return { cmd: [config.command, ...args], detached:
|
|
255
|
+
if (options.platform !== "win32") return { cmd: [config.command, ...args], detached: options.platform !== "darwin" };
|
|
251
256
|
|
|
252
257
|
const windowsHide = options.hostHasInheritableConsole === undefined ? true : !options.hostHasInheritableConsole;
|
|
253
258
|
const resolved = await resolveWindowsCommandPath(config.command, options.cwd, options.env);
|
|
@@ -366,10 +371,11 @@ export class StdioTransport implements MCPTransport {
|
|
|
366
371
|
});
|
|
367
372
|
|
|
368
373
|
// Platform-derived session and console-window handling come from
|
|
369
|
-
// `resolveStdioSpawnCommand`: POSIX
|
|
370
|
-
// escape terminal job-control signals (SIGTSTP, SIGTTIN);
|
|
371
|
-
//
|
|
372
|
-
//
|
|
374
|
+
// `resolveStdioSpawnCommand`: Linux/other POSIX detach into their own
|
|
375
|
+
// session to escape terminal job-control signals (SIGTSTP, SIGTTIN);
|
|
376
|
+
// macOS stays attached so TCC can prompt for Apple Events automation;
|
|
377
|
+
// Windows stays attached, and only hides the child when the host has no
|
|
378
|
+
// console to share. See `StdioSpawnCommand`.
|
|
373
379
|
this.#process = spawn({
|
|
374
380
|
cmd: spawnCommand.cmd,
|
|
375
381
|
cwd,
|
|
@@ -61,6 +61,8 @@ export interface HookSelectorOptions {
|
|
|
61
61
|
tui?: TUI;
|
|
62
62
|
timeout?: number;
|
|
63
63
|
onTimeout?: () => void;
|
|
64
|
+
onTimeoutStart?: () => void;
|
|
65
|
+
onTimeoutReset?: () => void;
|
|
64
66
|
initialIndex?: number;
|
|
65
67
|
outline?: boolean;
|
|
66
68
|
maxVisible?: number;
|
|
@@ -178,6 +180,7 @@ export class HookSelectorComponent extends Container {
|
|
|
178
180
|
#onLeftCallback: (() => void) | undefined;
|
|
179
181
|
#onRightCallback: (() => void) | undefined;
|
|
180
182
|
#onExternalEditorCallback: (() => void) | undefined;
|
|
183
|
+
#onTimeoutResetCallback: (() => void) | undefined;
|
|
181
184
|
#slider: HookSelectorSlider | undefined;
|
|
182
185
|
#sliderIndex: number = 0;
|
|
183
186
|
#sliderComponent: Text | undefined;
|
|
@@ -213,6 +216,7 @@ export class HookSelectorComponent extends Container {
|
|
|
213
216
|
this.#onLeftCallback = opts?.onLeft;
|
|
214
217
|
this.#onRightCallback = opts?.onRight;
|
|
215
218
|
this.#onExternalEditorCallback = opts?.onExternalEditor;
|
|
219
|
+
this.#onTimeoutResetCallback = opts?.onTimeoutReset;
|
|
216
220
|
if (opts?.slider && opts.slider.segments.length > 0) {
|
|
217
221
|
this.#slider = opts.slider;
|
|
218
222
|
this.#sliderIndex = Math.max(0, Math.min(opts.slider.index, opts.slider.segments.length - 1));
|
|
@@ -232,6 +236,7 @@ export class HookSelectorComponent extends Container {
|
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
if (opts?.timeout && opts.timeout > 0 && opts.tui) {
|
|
239
|
+
opts.onTimeoutStart?.();
|
|
235
240
|
this.#countdown = new CountdownTimer(
|
|
236
241
|
opts.timeout,
|
|
237
242
|
opts.tui,
|
|
@@ -633,8 +638,10 @@ export class HookSelectorComponent extends Container {
|
|
|
633
638
|
}
|
|
634
639
|
|
|
635
640
|
handleInput(keyData: string): void {
|
|
636
|
-
|
|
637
|
-
|
|
641
|
+
if (this.#countdown) {
|
|
642
|
+
this.#countdown.reset();
|
|
643
|
+
this.#onTimeoutResetCallback?.();
|
|
644
|
+
}
|
|
638
645
|
|
|
639
646
|
if (matchesSelectCancel(keyData)) {
|
|
640
647
|
this.#onCancelCallback();
|
|
@@ -61,6 +61,7 @@ export class ExtensionUiController {
|
|
|
61
61
|
async initHooksAndCustomTools(): Promise<void> {
|
|
62
62
|
// Create and set hook & tool UI context
|
|
63
63
|
const uiContext: ExtensionUIContext = {
|
|
64
|
+
timeoutStartsOnPresentation: true,
|
|
64
65
|
select: (title, options, dialogOptions) => this.showCollabAwareSelector(title, options, dialogOptions),
|
|
65
66
|
confirm: (title, message, _dialogOptions) => this.showHookConfirm(title, message),
|
|
66
67
|
input: (title, placeholder, dialogOptions) => this.showHookInput(title, placeholder, dialogOptions),
|
|
@@ -624,6 +625,8 @@ export class ExtensionUiController {
|
|
|
624
625
|
initialIndex: dialogOptions?.initialIndex,
|
|
625
626
|
timeout: dialogOptions?.timeout,
|
|
626
627
|
onTimeout: dialogOptions?.onTimeout,
|
|
628
|
+
onTimeoutStart: dialogOptions?.onTimeoutStart,
|
|
629
|
+
onTimeoutReset: dialogOptions?.onTimeoutReset,
|
|
627
630
|
tui: this.ctx.ui,
|
|
628
631
|
outline: dialogOptions?.outline,
|
|
629
632
|
disabledIndices: dialogOptions?.disabledIndices,
|
|
@@ -1024,10 +1024,10 @@ export class InputController {
|
|
|
1024
1024
|
// leaves wrappers and pipeline peers running and the terminal
|
|
1025
1025
|
// hung — exactly the failure shape we're fixing. Stopping the whole
|
|
1026
1026
|
// group keeps the shell's job-control view consistent. Long-lived
|
|
1027
|
-
// children that must survive the suspend (MCP stdio
|
|
1028
|
-
// the `detached: true` spawn in
|
|
1029
|
-
// brush external command via brush's
|
|
1030
|
-
// `crates/vendor/brush-core/src/commands.rs`) are
|
|
1027
|
+
// children that must survive the suspend (Linux/other POSIX MCP stdio
|
|
1028
|
+
// servers via the platform-specific `detached: true` spawn in
|
|
1029
|
+
// `mcp/transports/stdio.ts`, every brush external command via brush's
|
|
1030
|
+
// per-child `setsid` in `crates/vendor/brush-core/src/commands.rs`) are
|
|
1031
1031
|
// their own sessions, so pgid=0 does not reach them.
|
|
1032
1032
|
process.kill(0, "SIGSTOP");
|
|
1033
1033
|
} catch (err) {
|
|
@@ -1179,7 +1179,7 @@ export class SelectorController {
|
|
|
1179
1179
|
},
|
|
1180
1180
|
onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
|
|
1181
1181
|
});
|
|
1182
|
-
|
|
1182
|
+
this.ctx.session.modelRegistry.refreshInBackground();
|
|
1183
1183
|
const block = new TranscriptBlock();
|
|
1184
1184
|
block.addChild(
|
|
1185
1185
|
new Text(theme.fg("success", `${theme.status.success} Successfully logged in to ${providerId}`), 1, 0),
|
|
@@ -122,11 +122,12 @@
|
|
|
122
122
|
"status.pending": "◌",
|
|
123
123
|
"nav.cursor": "▸",
|
|
124
124
|
"nav.selected": "▴",
|
|
125
|
-
"thinking.minimal": "
|
|
126
|
-
"thinking.low": "
|
|
127
|
-
"thinking.medium": "
|
|
128
|
-
"thinking.high": "
|
|
129
|
-
"thinking.xhigh": "
|
|
125
|
+
"thinking.minimal": "∘",
|
|
126
|
+
"thinking.low": "◌",
|
|
127
|
+
"thinking.medium": "◍",
|
|
128
|
+
"thinking.high": "◎",
|
|
129
|
+
"thinking.xhigh": "◉",
|
|
130
|
+
"thinking.max": "●",
|
|
130
131
|
"icon.model": "◇",
|
|
131
132
|
"icon.plan": "◈",
|
|
132
133
|
"icon.goal": "⊙",
|
|
@@ -122,11 +122,12 @@
|
|
|
122
122
|
"status.pending": "◌",
|
|
123
123
|
"nav.cursor": "▸",
|
|
124
124
|
"nav.selected": "▴",
|
|
125
|
-
"thinking.minimal": "
|
|
126
|
-
"thinking.low": "
|
|
127
|
-
"thinking.medium": "
|
|
128
|
-
"thinking.high": "
|
|
129
|
-
"thinking.xhigh": "
|
|
125
|
+
"thinking.minimal": "∘",
|
|
126
|
+
"thinking.low": "◌",
|
|
127
|
+
"thinking.medium": "◍",
|
|
128
|
+
"thinking.high": "◎",
|
|
129
|
+
"thinking.xhigh": "◉",
|
|
130
|
+
"thinking.max": "●",
|
|
130
131
|
"icon.model": "◇",
|
|
131
132
|
"icon.plan": "◈",
|
|
132
133
|
"icon.goal": "⊙",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"colors": {
|
|
35
35
|
"type": "object",
|
|
36
|
-
"description": "Theme color definitions (all required)",
|
|
36
|
+
"description": "Theme color definitions (all required except thinkingMax)",
|
|
37
37
|
"required": [
|
|
38
38
|
"accent",
|
|
39
39
|
"border",
|
|
@@ -301,7 +301,11 @@
|
|
|
301
301
|
},
|
|
302
302
|
"thinkingXhigh": {
|
|
303
303
|
"$ref": "#/$defs/colorValue",
|
|
304
|
-
"description": "Thinking level border: xhigh
|
|
304
|
+
"description": "Thinking level border: xhigh"
|
|
305
|
+
},
|
|
306
|
+
"thinkingMax": {
|
|
307
|
+
"$ref": "#/$defs/colorValue",
|
|
308
|
+
"description": "Thinking level border: max (optional; falls back to thinkingXhigh when omitted)"
|
|
305
309
|
},
|
|
306
310
|
"bashMode": {
|
|
307
311
|
"$ref": "#/$defs/colorValue",
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -142,6 +142,7 @@ export type SymbolKey =
|
|
|
142
142
|
| "thinking.medium"
|
|
143
143
|
| "thinking.high"
|
|
144
144
|
| "thinking.xhigh"
|
|
145
|
+
| "thinking.max"
|
|
145
146
|
| "thinking.autoPending"
|
|
146
147
|
// Checkboxes
|
|
147
148
|
| "checkbox.checked"
|
|
@@ -344,11 +345,12 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
344
345
|
// Compaction divider
|
|
345
346
|
"icon.camera": "📷",
|
|
346
347
|
// Thinking levels
|
|
347
|
-
"thinking.minimal": "
|
|
348
|
-
"thinking.low": "
|
|
349
|
-
"thinking.medium": "
|
|
350
|
-
"thinking.high": "
|
|
351
|
-
"thinking.xhigh": "
|
|
348
|
+
"thinking.minimal": "○ min",
|
|
349
|
+
"thinking.low": "◔ low",
|
|
350
|
+
"thinking.medium": "◑ med",
|
|
351
|
+
"thinking.high": "◒ high",
|
|
352
|
+
"thinking.xhigh": "◕ xhigh",
|
|
353
|
+
"thinking.max": "◉ max",
|
|
352
354
|
"thinking.autoPending": "⟳",
|
|
353
355
|
// Checkboxes
|
|
354
356
|
"checkbox.checked": "☑",
|
|
@@ -639,19 +641,15 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
639
641
|
"icon.mic": "\uf130",
|
|
640
642
|
// Compaction divider - fa-camera-retro
|
|
641
643
|
"icon.camera": "\uf083",
|
|
642
|
-
// Thinking
|
|
643
|
-
|
|
644
|
-
"thinking.
|
|
645
|
-
|
|
646
|
-
"thinking.
|
|
647
|
-
|
|
648
|
-
"thinking.
|
|
649
|
-
//
|
|
650
|
-
"thinking.
|
|
651
|
-
// pick: 🧠 xhi | alt: xhi xhi
|
|
652
|
-
"thinking.xhigh": "\u{F06D} xhi",
|
|
653
|
-
// pick: (fa-circle-o-notch) | alt: (nf-md-cached) ⟳
|
|
654
|
-
"thinking.autoPending": "\uf1ce",
|
|
644
|
+
// Thinking levels — increasing circle slices, with fire reserved for max.
|
|
645
|
+
"thinking.minimal": "\u{F0A9E} min",
|
|
646
|
+
"thinking.low": "\u{F0A9F} low",
|
|
647
|
+
"thinking.medium": "\u{F0AA1} med",
|
|
648
|
+
"thinking.high": "\u{F0AA3} high",
|
|
649
|
+
"thinking.xhigh": "\u{F0AA5} xhi",
|
|
650
|
+
"thinking.max": "\u{F06D} max",
|
|
651
|
+
// Auto mode uses shuffle until the model resolves its thinking level.
|
|
652
|
+
"thinking.autoPending": "\u{F074}",
|
|
655
653
|
// Checkboxes
|
|
656
654
|
// pick: | alt:
|
|
657
655
|
"checkbox.checked": "\uf14a",
|
|
@@ -868,6 +866,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
868
866
|
"thinking.medium": "[med]",
|
|
869
867
|
"thinking.high": "[high]",
|
|
870
868
|
"thinking.xhigh": "[xhi]",
|
|
869
|
+
"thinking.max": "[max]",
|
|
871
870
|
"thinking.autoPending": "[~]",
|
|
872
871
|
// Checkboxes
|
|
873
872
|
"checkbox.checked": "[x]",
|
|
@@ -1059,6 +1058,7 @@ const themeColorsSchema = type({
|
|
|
1059
1058
|
thinkingMedium: "string | number",
|
|
1060
1059
|
thinkingHigh: "string | number",
|
|
1061
1060
|
thinkingXhigh: "string | number",
|
|
1061
|
+
"thinkingMax?": "string | number",
|
|
1062
1062
|
bashMode: "string | number",
|
|
1063
1063
|
pythonMode: "string | number",
|
|
1064
1064
|
statusLineBg: "string | number",
|
|
@@ -1163,6 +1163,7 @@ export type ThemeColor =
|
|
|
1163
1163
|
| "thinkingMedium"
|
|
1164
1164
|
| "thinkingHigh"
|
|
1165
1165
|
| "thinkingXhigh"
|
|
1166
|
+
| "thinkingMax"
|
|
1166
1167
|
| "bashMode"
|
|
1167
1168
|
| "pythonMode"
|
|
1168
1169
|
| "statusLineSep"
|
|
@@ -1225,6 +1226,7 @@ const THEME_COLOR_RECORD = {
|
|
|
1225
1226
|
thinkingMedium: true,
|
|
1226
1227
|
thinkingHigh: true,
|
|
1227
1228
|
thinkingXhigh: true,
|
|
1229
|
+
thinkingMax: true,
|
|
1228
1230
|
bashMode: true,
|
|
1229
1231
|
pythonMode: true,
|
|
1230
1232
|
statusLineSep: true,
|
|
@@ -1675,6 +1677,9 @@ export class Theme {
|
|
|
1675
1677
|
return (str: string) => this.fg("thinkingHigh", str);
|
|
1676
1678
|
case "xhigh":
|
|
1677
1679
|
return (str: string) => this.fg("thinkingXhigh", str);
|
|
1680
|
+
case "max":
|
|
1681
|
+
// thinkingMax is optional; themes without it resolve to the xhigh color.
|
|
1682
|
+
return (str: string) => this.fg(this.#fgColors.thinkingMax ? "thinkingMax" : "thinkingXhigh", str);
|
|
1678
1683
|
default:
|
|
1679
1684
|
return (str: string) => this.fg("thinkingOff", str);
|
|
1680
1685
|
}
|
|
@@ -1861,6 +1866,7 @@ export class Theme {
|
|
|
1861
1866
|
medium: this.#symbols["thinking.medium"],
|
|
1862
1867
|
high: this.#symbols["thinking.high"],
|
|
1863
1868
|
xhigh: this.#symbols["thinking.xhigh"],
|
|
1869
|
+
max: this.#symbols["thinking.max"],
|
|
1864
1870
|
autoPending: this.#symbols["thinking.autoPending"],
|
|
1865
1871
|
};
|
|
1866
1872
|
}
|
|
@@ -4,7 +4,7 @@ description: Generate AGENTS.md for current codebase
|
|
|
4
4
|
thinking-level: medium
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Generate AGENTS.md by launching multiple `
|
|
7
|
+
Generate AGENTS.md by launching multiple `scout` agents in parallel (via `task` tool) scanning different areas (core src, tests, configs/build, scripts/docs), then synthesize findings into a single file.
|
|
8
8
|
|
|
9
9
|
<structure>
|
|
10
10
|
- **Project Overview**: Brief description of project purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: plan
|
|
3
3
|
description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls.
|
|
4
4
|
tools: read, grep, glob, bash, lsp, web_search, ast_grep
|
|
5
|
-
spawns:
|
|
5
|
+
spawns: scout
|
|
6
6
|
model: pi/plan, pi/slow
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ Analyze the codebase and the user's request. Produce a detailed implementation p
|
|
|
19
19
|
4. Identify types, interfaces, contracts
|
|
20
20
|
5. Note dependencies between components
|
|
21
21
|
|
|
22
|
-
You MUST spawn `
|
|
22
|
+
You MUST spawn `scout` agents for independent areas and synthesize findings.
|
|
23
23
|
|
|
24
24
|
## Phase 3: Design
|
|
25
25
|
1. List concrete changes (files, functions, types)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: Fast read-only
|
|
2
|
+
name: scout
|
|
3
|
+
description: MUST be used for exploratory codebase research, rapid code analysis, and broad pattern searches. Fast read-only scout returning compressed context for handoff.
|
|
4
4
|
tools: read, grep, glob, web_search
|
|
5
5
|
model: pi/smol
|
|
6
6
|
thinking-level: medium
|
|
7
7
|
read-summarize: false
|
|
8
|
+
blocking: true
|
|
8
9
|
output:
|
|
9
10
|
properties:
|
|
10
11
|
summary:
|