@oh-my-pi/pi-coding-agent 16.3.15 → 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 +28 -0
- package/dist/cli.js +3361 -3357
- 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/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 -3
- 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/tools/ask.ts
CHANGED
|
@@ -380,6 +380,7 @@ interface AskSingleQuestionOptions {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
interface UIContext {
|
|
383
|
+
timeoutStartsOnPresentation?: boolean;
|
|
383
384
|
select(
|
|
384
385
|
prompt: string,
|
|
385
386
|
options: ExtensionUISelectItem[],
|
|
@@ -389,6 +390,8 @@ interface UIContext {
|
|
|
389
390
|
signal?: AbortSignal;
|
|
390
391
|
outline?: boolean;
|
|
391
392
|
onTimeout?: () => void;
|
|
393
|
+
onTimeoutStart?: () => void;
|
|
394
|
+
onTimeoutReset?: () => void;
|
|
392
395
|
onLeft?: () => void;
|
|
393
396
|
onRight?: () => void;
|
|
394
397
|
helpText?: string;
|
|
@@ -432,12 +435,30 @@ async function askSingleQuestion(
|
|
|
432
435
|
const helpText = navigation
|
|
433
436
|
? "up/down navigate enter select ←/→ question esc cancel"
|
|
434
437
|
: "up/down navigate enter select esc cancel";
|
|
438
|
+
const timeoutMs = typeof timeout === "number" && timeout > 0 ? timeout : undefined;
|
|
439
|
+
const timeoutController = timeoutMs === undefined ? undefined : new AbortController();
|
|
440
|
+
const dialogSignal =
|
|
441
|
+
signal && timeoutController
|
|
442
|
+
? AbortSignal.any([signal, timeoutController.signal])
|
|
443
|
+
: (timeoutController?.signal ?? signal);
|
|
444
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
445
|
+
let timeoutStartedMs = Date.now();
|
|
446
|
+
const armFallbackTimeout = (durationMs: number) => {
|
|
447
|
+
clearTimeout(timeoutId);
|
|
448
|
+
timeoutStartedMs = Date.now();
|
|
449
|
+
timeoutId = setTimeout(() => {
|
|
450
|
+
timeoutTriggered = true;
|
|
451
|
+
timeoutController?.abort();
|
|
452
|
+
}, durationMs);
|
|
453
|
+
};
|
|
435
454
|
const dialogOptions = {
|
|
436
455
|
initialIndex,
|
|
437
456
|
timeout,
|
|
438
|
-
signal,
|
|
457
|
+
signal: dialogSignal,
|
|
439
458
|
outline: true,
|
|
440
459
|
onTimeout,
|
|
460
|
+
onTimeoutStart: timeoutMs === undefined ? undefined : () => armFallbackTimeout(timeoutMs),
|
|
461
|
+
onTimeoutReset: timeoutMs === undefined ? undefined : () => armFallbackTimeout(timeoutMs),
|
|
441
462
|
helpText,
|
|
442
463
|
selectionMarker: marker?.selectionMarker,
|
|
443
464
|
checkedIndices: marker?.checkedIndices,
|
|
@@ -453,19 +474,32 @@ async function askSingleQuestion(
|
|
|
453
474
|
}
|
|
454
475
|
: undefined,
|
|
455
476
|
};
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
477
|
+
try {
|
|
478
|
+
const runSelect = () => {
|
|
479
|
+
const selection = ui.select(prompt, optionsToShow, dialogOptions);
|
|
480
|
+
if (timeoutMs !== undefined && !ui.timeoutStartsOnPresentation) {
|
|
481
|
+
armFallbackTimeout(timeoutMs);
|
|
482
|
+
}
|
|
483
|
+
return selection;
|
|
484
|
+
};
|
|
485
|
+
const choice = dialogSignal ? await untilAborted(dialogSignal, runSelect) : await runSelect();
|
|
486
|
+
if (!timeoutTriggered && choice === undefined && typeof timeout === "number") {
|
|
487
|
+
// Fallback for UI surfaces that enforce `timeout` without invoking
|
|
488
|
+
// `onTimeout`: their auto-cancel resolves right at the deadline. A
|
|
489
|
+
// cancel arriving well past the deadline is a deliberate user Esc on
|
|
490
|
+
// a surface that kept the dialog open — keep treating it as a cancel.
|
|
491
|
+
const elapsed = Date.now() - timeoutStartedMs;
|
|
492
|
+
timeoutTriggered = elapsed >= timeout && elapsed <= timeout + TIMEOUT_DETECTION_TOLERANCE_MS;
|
|
493
|
+
}
|
|
494
|
+
return { choice, timedOut: timeoutTriggered, navigation: navigationAction };
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (timeoutTriggered && error instanceof Error && error.name === "AbortError") {
|
|
497
|
+
return { choice: undefined, timedOut: true, navigation: navigationAction };
|
|
498
|
+
}
|
|
499
|
+
throw error;
|
|
500
|
+
} finally {
|
|
501
|
+
clearTimeout(timeoutId);
|
|
467
502
|
}
|
|
468
|
-
return { choice, timedOut: timeoutTriggered, navigation: navigationAction };
|
|
469
503
|
};
|
|
470
504
|
|
|
471
505
|
const promptForCustomInput = async (
|
|
@@ -614,6 +648,9 @@ async function askSingleQuestion(
|
|
|
614
648
|
customInput = undefined;
|
|
615
649
|
break;
|
|
616
650
|
}
|
|
651
|
+
if (timedOut && selectedOptions.length === 0 && customInput === undefined) {
|
|
652
|
+
selectedOptions = getAutoSelectionOnTimeout(questionOptions, recommended);
|
|
653
|
+
}
|
|
617
654
|
if (navigation?.allowForward) {
|
|
618
655
|
return { selectedOptions, customInput, timedOut, navigation: "forward" };
|
|
619
656
|
}
|
|
@@ -743,6 +780,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
743
780
|
|
|
744
781
|
const extensionUi = context.ui;
|
|
745
782
|
const ui: UIContext = {
|
|
783
|
+
timeoutStartsOnPresentation: extensionUi.timeoutStartsOnPresentation,
|
|
746
784
|
select: (prompt, options, dialogOptions) => extensionUi.select(prompt, options, dialogOptions),
|
|
747
785
|
editor: (title, prefill, dialogOptions, editorOptions) =>
|
|
748
786
|
extensionUi.editor(title, prefill, dialogOptions, editorOptions),
|
|
@@ -1288,20 +1288,13 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1288
1288
|
const screenshots: ScreenshotResult[] = [];
|
|
1289
1289
|
const runId = crypto.randomUUID();
|
|
1290
1290
|
tab.setRunContext({ session: opts.snapshot, displays, screenshots, signal, timeoutMs: opts.timeoutMs });
|
|
1291
|
-
const runtime = tab.ensureRuntime(opts.snapshot);
|
|
1292
|
-
runtime.setCwd(opts.snapshot.cwd);
|
|
1293
|
-
const runTab = bindBrowserRunFacade(tab, signal);
|
|
1294
|
-
runtime.setRunScope({
|
|
1295
|
-
page: bindBrowserRunFacade(tab.page, signal),
|
|
1296
|
-
browser: bindBrowserRunFacade(tab.browser, signal),
|
|
1297
|
-
tab: runTab,
|
|
1298
|
-
assert: (cond: unknown, text?: string): void => {
|
|
1299
|
-
if (!cond) throw new ToolError(text ?? "Assertion failed");
|
|
1300
|
-
},
|
|
1301
|
-
wait: (ms: number): Promise<void> => waitForBrowserRun(ms, signal),
|
|
1302
|
-
});
|
|
1303
1291
|
|
|
1304
1292
|
const { promise: cancelRejection, reject } = Promise.withResolvers<never>();
|
|
1293
|
+
// If the synchronous setup below throws (same-realm ownership conflict)
|
|
1294
|
+
// while `signal` is already aborted, `Promise.race` never attaches a
|
|
1295
|
+
// handler to this promise; keep its armed rejection from surfacing as an
|
|
1296
|
+
// unhandled rejection — the postmortem-fatal path this run guards against.
|
|
1297
|
+
cancelRejection.catch(() => {});
|
|
1305
1298
|
const onAbort = (): void => {
|
|
1306
1299
|
if (timeoutSignal.aborted) {
|
|
1307
1300
|
reject(new ToolError(`Browser code execution timed out after ${opts.timeoutMs}ms`));
|
|
@@ -1317,6 +1310,22 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1317
1310
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
1318
1311
|
|
|
1319
1312
|
try {
|
|
1313
|
+
const runtime = tab.ensureRuntime(opts.snapshot);
|
|
1314
|
+
// setCwd is non-exclusive; setRunScope/run still assert same-realm ownership.
|
|
1315
|
+
// Keep both inside try so a concurrent in-process eval/browser run surfaces as
|
|
1316
|
+
// a rejected promise the supervisor can report, never an unhandled rejection.
|
|
1317
|
+
runtime.setCwd(opts.snapshot.cwd);
|
|
1318
|
+
const runTab = bindBrowserRunFacade(tab, signal);
|
|
1319
|
+
runtime.setRunScope({
|
|
1320
|
+
page: bindBrowserRunFacade(tab.page, signal),
|
|
1321
|
+
browser: bindBrowserRunFacade(tab.browser, signal),
|
|
1322
|
+
tab: runTab,
|
|
1323
|
+
assert: (cond: unknown, text?: string): void => {
|
|
1324
|
+
if (!cond) throw new ToolError(text ?? "Assertion failed");
|
|
1325
|
+
},
|
|
1326
|
+
wait: (ms: number): Promise<void> => waitForBrowserRun(ms, signal),
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1320
1329
|
const hooks: RuntimeHooks = {
|
|
1321
1330
|
onText: chunk => {
|
|
1322
1331
|
throwIfAborted(signal);
|
package/src/tools/debug.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type DapThread,
|
|
32
32
|
type DapVariable,
|
|
33
33
|
dapSessionManager,
|
|
34
|
+
getAdapterConfigs,
|
|
34
35
|
getAvailableAdapters,
|
|
35
36
|
type LaunchProgramKind,
|
|
36
37
|
resolveLaunchOverrides,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
formatStatusIcon,
|
|
51
52
|
PREVIEW_LIMITS,
|
|
52
53
|
replaceTabs,
|
|
54
|
+
shortenPath,
|
|
53
55
|
TRUNCATE_LENGTHS,
|
|
54
56
|
truncateToWidth,
|
|
55
57
|
} from "./render-utils";
|
|
@@ -106,9 +108,9 @@ const debugActionSchema = type.enumerated(
|
|
|
106
108
|
);
|
|
107
109
|
const debugSchema = type({
|
|
108
110
|
action: debugActionSchema,
|
|
109
|
-
"program?": type("string").describe("
|
|
111
|
+
"program?": type("string").describe("debug target path; Delve accepts Go package directories"),
|
|
110
112
|
"args?": type("string[]").describe("program arguments"),
|
|
111
|
-
"adapter?": type("string").describe("
|
|
113
|
+
"adapter?": type("string").describe("configured adapter id (gdb, lldb-dap, debugpy, dlv, rdbg, or dap.json entry)"),
|
|
112
114
|
cwd: "string?",
|
|
113
115
|
"file?": type("string").describe("source file"),
|
|
114
116
|
"line?": type("number").describe("source line"),
|
|
@@ -494,7 +496,33 @@ function buildOutcomeText(outcome: DapContinueOutcome, timeoutSec: number, verb:
|
|
|
494
496
|
|
|
495
497
|
function getConfiguredAdapters(cwd: string): string {
|
|
496
498
|
const adapters = getAvailableAdapters(cwd).map(adapter => adapter.name);
|
|
497
|
-
|
|
499
|
+
const names = adapters.length > 0 ? adapters.join(", ") : "none";
|
|
500
|
+
return truncateToWidth(replaceTabs(names), TRUNCATE_LENGTHS.LONG);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const ADAPTER_UNAVAILABLE_MESSAGES: Readonly<Record<string, string>> = {
|
|
504
|
+
debugpy: "adapter 'debugpy' is not available: python not found in PATH",
|
|
505
|
+
dlv: "adapter 'dlv' is not available: install with 'go install github.com/go-delve/delve/cmd/dlv@latest'",
|
|
506
|
+
rdbg: "adapter 'rdbg' is not available: install with 'gem install debug'",
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const ADAPTER_CANONICAL_COMMANDS: Readonly<Record<string, string>> = {
|
|
510
|
+
debugpy: "python",
|
|
511
|
+
dlv: "dlv",
|
|
512
|
+
rdbg: "rdbg",
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
function formatAdapterUnavailable(adapterName: string, command: string, cwd: string): string {
|
|
516
|
+
const displayName = truncateToWidth(replaceTabs(adapterName), TRUNCATE_LENGTHS.SHORT);
|
|
517
|
+
const canonicalCommand = ADAPTER_CANONICAL_COMMANDS[adapterName] ?? adapterName;
|
|
518
|
+
if (command !== canonicalCommand) {
|
|
519
|
+
const displayCommand = truncateToWidth(replaceTabs(shortenPath(command)), TRUNCATE_LENGTHS.CONTENT);
|
|
520
|
+
return `adapter '${displayName}' is not available: configured command '${displayCommand}' did not resolve. Check the DAP adapter config for this workspace.`;
|
|
521
|
+
}
|
|
522
|
+
return (
|
|
523
|
+
ADAPTER_UNAVAILABLE_MESSAGES[adapterName] ??
|
|
524
|
+
`adapter '${displayName}' is not available. Installed adapters: ${getConfiguredAdapters(cwd)}`
|
|
525
|
+
);
|
|
498
526
|
}
|
|
499
527
|
|
|
500
528
|
async function classifyLaunchProgram(program: string): Promise<LaunchProgramKind> {
|
|
@@ -515,7 +543,7 @@ function validateLaunchProgram(
|
|
|
515
543
|
if (programKind !== "directory" || adapter.acceptsDirectoryProgram) return;
|
|
516
544
|
const displayPath = formatPathRelativeToCwd(program, cwd, { trailingSlash: true });
|
|
517
545
|
throw new ToolError(
|
|
518
|
-
`launch program resolves to a directory: ${displayPath}. Pass an executable file path
|
|
546
|
+
`launch program resolves to a directory: ${displayPath}. Pass an executable file path or choose an adapter that supports package directories.`,
|
|
519
547
|
);
|
|
520
548
|
}
|
|
521
549
|
|
|
@@ -711,15 +739,16 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
|
|
|
711
739
|
const commandCwd = params.cwd ? resolveToCwd(params.cwd, this.session.cwd) : this.session.cwd;
|
|
712
740
|
const program = resolveToCwd(params.program, commandCwd);
|
|
713
741
|
const programKind = await classifyLaunchProgram(program);
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
742
|
+
const selection = selectLaunchAdapter(program, commandCwd, params.adapter, programKind);
|
|
743
|
+
if (selection.kind === "unavailable") {
|
|
744
|
+
throw new ToolError(formatAdapterUnavailable(selection.adapterName, selection.command, commandCwd));
|
|
745
|
+
}
|
|
746
|
+
if (selection.kind === "none") {
|
|
719
747
|
throw new ToolError(
|
|
720
748
|
`No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
|
|
721
749
|
);
|
|
722
750
|
}
|
|
751
|
+
const { adapter } = selection;
|
|
723
752
|
validateLaunchProgram(program, commandCwd, programKind, adapter);
|
|
724
753
|
const extraLaunchArguments = resolveLaunchOverrides(adapter, program, programKind);
|
|
725
754
|
const snapshot = await dapSessionManager.launch(
|
|
@@ -738,8 +767,9 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
|
|
|
738
767
|
const commandCwd = params.cwd ? resolveToCwd(params.cwd, this.session.cwd) : this.session.cwd;
|
|
739
768
|
const adapter = selectAttachAdapter(commandCwd, params.adapter, params.port);
|
|
740
769
|
if (!adapter) {
|
|
741
|
-
if (params.adapter
|
|
742
|
-
|
|
770
|
+
if (params.adapter) {
|
|
771
|
+
const command = getAdapterConfigs(commandCwd)[params.adapter]?.command ?? params.adapter;
|
|
772
|
+
throw new ToolError(formatAdapterUnavailable(params.adapter, command, commandCwd));
|
|
743
773
|
}
|
|
744
774
|
throw new ToolError(
|
|
745
775
|
`No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
|
|
@@ -21,7 +21,7 @@ export interface FileDisplayModeSession {
|
|
|
21
21
|
/**
|
|
22
22
|
* Computes effective line display mode from session settings/env.
|
|
23
23
|
* Hashline mode takes precedence and implies line-addressed output everywhere.
|
|
24
|
-
* Hashlines are suppressed when the edit tool is not available (e.g.
|
|
24
|
+
* Hashlines are suppressed when the edit tool is not available (e.g. scout agents),
|
|
25
25
|
* when the caller signals a `raw` read, and when the source is `immutable`
|
|
26
26
|
* (e.g. internal URLs like artifact://, agent://, memory:// — there is no edit
|
|
27
27
|
* path that could consume the anchors). Raw output is returned as-is.
|