@oh-my-pi/pi-coding-agent 13.14.0 → 13.15.2
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 +140 -0
- package/package.json +10 -8
- package/src/autoresearch/command-initialize.md +34 -0
- package/src/autoresearch/command-resume.md +17 -0
- package/src/autoresearch/contract.ts +332 -0
- package/src/autoresearch/dashboard.ts +447 -0
- package/src/autoresearch/git.ts +243 -0
- package/src/autoresearch/helpers.ts +458 -0
- package/src/autoresearch/index.ts +693 -0
- package/src/autoresearch/prompt.md +227 -0
- package/src/autoresearch/resume-message.md +16 -0
- package/src/autoresearch/state.ts +386 -0
- package/src/autoresearch/tools/init-experiment.ts +310 -0
- package/src/autoresearch/tools/log-experiment.ts +833 -0
- package/src/autoresearch/tools/run-experiment.ts +640 -0
- package/src/autoresearch/types.ts +218 -0
- package/src/cli/args.ts +8 -2
- package/src/cli/initial-message.ts +58 -0
- package/src/config/keybindings.ts +417 -212
- package/src/config/model-registry.ts +1 -0
- package/src/config/model-resolver.ts +57 -9
- package/src/config/settings-schema.ts +38 -10
- package/src/config/settings.ts +1 -4
- package/src/exec/bash-executor.ts +7 -5
- package/src/export/html/template.css +43 -13
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.html +1 -0
- package/src/export/html/template.js +107 -0
- package/src/extensibility/extensions/types.ts +31 -8
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/lsp/index.ts +1 -1
- package/src/main.ts +44 -44
- package/src/mcp/oauth-discovery.ts +1 -1
- package/src/modes/acp/acp-agent.ts +957 -0
- package/src/modes/acp/acp-event-mapper.ts +531 -0
- package/src/modes/acp/acp-mode.ts +13 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/components/agent-dashboard.ts +5 -4
- package/src/modes/components/bash-execution.ts +40 -11
- package/src/modes/components/custom-editor.ts +47 -47
- package/src/modes/components/extensions/extension-dashboard.ts +2 -1
- package/src/modes/components/history-search.ts +2 -1
- package/src/modes/components/hook-editor.ts +2 -1
- package/src/modes/components/hook-input.ts +8 -7
- package/src/modes/components/hook-selector.ts +15 -10
- package/src/modes/components/keybinding-hints.ts +9 -9
- package/src/modes/components/login-dialog.ts +3 -3
- package/src/modes/components/mcp-add-wizard.ts +2 -1
- package/src/modes/components/model-selector.ts +14 -3
- package/src/modes/components/oauth-selector.ts +2 -1
- package/src/modes/components/python-execution.ts +2 -3
- package/src/modes/components/session-selector.ts +2 -1
- package/src/modes/components/settings-selector.ts +2 -1
- package/src/modes/components/status-line-segment-editor.ts +2 -1
- package/src/modes/components/tool-execution.ts +4 -5
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/components/user-message-selector.ts +3 -8
- package/src/modes/components/user-message.ts +16 -0
- package/src/modes/controllers/command-controller.ts +0 -2
- package/src/modes/controllers/extension-ui-controller.ts +89 -4
- package/src/modes/controllers/input-controller.ts +29 -23
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +17 -5
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/prompt-action-autocomplete.ts +7 -7
- package/src/modes/rpc/rpc-mode.ts +7 -2
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/theme/theme.ts +53 -44
- package/src/modes/types.ts +9 -2
- package/src/modes/utils/hotkeys-markdown.ts +19 -19
- package/src/modes/utils/keybinding-matchers.ts +21 -0
- package/src/modes/utils/ui-helpers.ts +1 -1
- package/src/patch/hashline.ts +139 -127
- package/src/patch/index.ts +77 -59
- package/src/patch/shared.ts +19 -11
- package/src/prompts/tools/hashline.md +43 -116
- package/src/sdk.ts +34 -17
- package/src/session/agent-session.ts +123 -30
- package/src/session/session-manager.ts +32 -31
- package/src/session/streaming-output.ts +87 -37
- package/src/tools/ask.ts +56 -30
- package/src/tools/bash-interactive.ts +2 -6
- package/src/tools/bash-interceptor.ts +1 -39
- package/src/tools/bash-skill-urls.ts +1 -1
- package/src/tools/browser.ts +1 -1
- package/src/tools/gemini-image.ts +1 -1
- package/src/tools/python.ts +2 -2
- package/src/tools/resolve.ts +1 -1
- package/src/utils/child-process.ts +88 -0
package/src/tools/ask.ts
CHANGED
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
|
-
import type
|
|
20
|
-
import { TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
19
|
+
import { type Component, Container, Markdown, renderInlineMarkdown, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
21
20
|
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
22
21
|
import { type Static, Type } from "@sinclair/typebox";
|
|
23
22
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
24
23
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
25
|
-
import { type Theme, theme } from "../modes/theme/theme";
|
|
24
|
+
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
26
25
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
27
26
|
import { renderStatusLine } from "../tui";
|
|
28
27
|
import type { ToolSession } from ".";
|
|
@@ -574,10 +573,13 @@ interface AskRenderArgs {
|
|
|
574
573
|
export const askToolRenderer = {
|
|
575
574
|
renderCall(args: AskRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
576
575
|
const label = formatTitle("Ask", uiTheme);
|
|
576
|
+
const mdTheme = getMarkdownTheme();
|
|
577
|
+
const accentStyle = { color: (t: string) => uiTheme.fg("accent", t) };
|
|
577
578
|
|
|
578
579
|
// Multi-part questions
|
|
579
580
|
if (args.questions && args.questions.length > 0) {
|
|
580
|
-
|
|
581
|
+
const container = new Container();
|
|
582
|
+
container.addChild(new Text(`${label} ${uiTheme.fg("muted", `${args.questions.length} questions`)}`, 0, 0));
|
|
581
583
|
|
|
582
584
|
for (let i = 0; i < args.questions.length; i++) {
|
|
583
585
|
const q = args.questions[i];
|
|
@@ -585,25 +587,29 @@ export const askToolRenderer = {
|
|
|
585
587
|
const qBranch = isLastQ ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
586
588
|
const continuation = isLastQ ? " " : uiTheme.tree.vertical;
|
|
587
589
|
|
|
588
|
-
// Question line with metadata
|
|
589
590
|
const meta: string[] = [];
|
|
590
591
|
if (q.multi) meta.push("multi");
|
|
591
592
|
if (q.options?.length) meta.push(`options:${q.options.length}`);
|
|
592
593
|
const metaStr = meta.length > 0 ? uiTheme.fg("dim", ` · ${meta.join(" · ")}`) : "";
|
|
593
594
|
|
|
594
|
-
|
|
595
|
+
container.addChild(
|
|
596
|
+
new Text(` ${uiTheme.fg("dim", qBranch)} ${uiTheme.fg("dim", `[${q.id}]`)}${metaStr}`, 0, 0),
|
|
597
|
+
);
|
|
598
|
+
container.addChild(new Markdown(q.question, 3, 0, mdTheme, accentStyle));
|
|
595
599
|
|
|
596
|
-
// Options under question
|
|
597
600
|
if (q.options?.length) {
|
|
601
|
+
let optText = "";
|
|
598
602
|
for (let j = 0; j < q.options.length; j++) {
|
|
599
603
|
const opt = q.options[j];
|
|
600
604
|
const isLastOpt = j === q.options.length - 1;
|
|
601
605
|
const optBranch = isLastOpt ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
602
|
-
|
|
606
|
+
const optLabel = renderInlineMarkdown(opt.label, mdTheme, t => uiTheme.fg("muted", t));
|
|
607
|
+
optText += `\n ${uiTheme.fg("dim", continuation)} ${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} ${optLabel}`;
|
|
603
608
|
}
|
|
609
|
+
container.addChild(new Text(optText, 0, 0));
|
|
604
610
|
}
|
|
605
611
|
}
|
|
606
|
-
return
|
|
612
|
+
return container;
|
|
607
613
|
}
|
|
608
614
|
|
|
609
615
|
// Single question
|
|
@@ -611,22 +617,26 @@ export const askToolRenderer = {
|
|
|
611
617
|
return new Text(formatErrorMessage("No question provided", uiTheme), 0, 0);
|
|
612
618
|
}
|
|
613
619
|
|
|
614
|
-
|
|
620
|
+
const container = new Container();
|
|
615
621
|
const meta: string[] = [];
|
|
616
622
|
if (args.multi) meta.push("multi");
|
|
617
623
|
if (args.options?.length) meta.push(`options:${args.options.length}`);
|
|
618
|
-
|
|
624
|
+
container.addChild(new Text(`${label}${formatMeta(meta, uiTheme)}`, 0, 0));
|
|
625
|
+
container.addChild(new Markdown(args.question, 1, 0, mdTheme, accentStyle));
|
|
619
626
|
|
|
620
627
|
if (args.options?.length) {
|
|
628
|
+
let optText = "";
|
|
621
629
|
for (let i = 0; i < args.options.length; i++) {
|
|
622
630
|
const opt = args.options[i];
|
|
623
631
|
const isLast = i === args.options.length - 1;
|
|
624
632
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
625
|
-
|
|
633
|
+
const optLabel = renderInlineMarkdown(opt.label, mdTheme, t => uiTheme.fg("muted", t));
|
|
634
|
+
optText += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} ${optLabel}`;
|
|
626
635
|
}
|
|
636
|
+
container.addChild(new Text(optText, 0, 0));
|
|
627
637
|
}
|
|
628
638
|
|
|
629
|
-
return
|
|
639
|
+
return container;
|
|
630
640
|
},
|
|
631
641
|
|
|
632
642
|
renderResult(
|
|
@@ -635,6 +645,9 @@ export const askToolRenderer = {
|
|
|
635
645
|
uiTheme: Theme,
|
|
636
646
|
): Component {
|
|
637
647
|
const { details } = result;
|
|
648
|
+
const mdTheme = getMarkdownTheme();
|
|
649
|
+
const accentStyle = { color: (t: string) => uiTheme.fg("accent", t) };
|
|
650
|
+
|
|
638
651
|
if (!details) {
|
|
639
652
|
const txt = result.content[0];
|
|
640
653
|
const fallback = txt?.type === "text" && txt.text ? txt.text : "";
|
|
@@ -655,7 +668,8 @@ export const askToolRenderer = {
|
|
|
655
668
|
},
|
|
656
669
|
uiTheme,
|
|
657
670
|
);
|
|
658
|
-
|
|
671
|
+
const container = new Container();
|
|
672
|
+
container.addChild(new Text(header, 0, 0));
|
|
659
673
|
|
|
660
674
|
for (let i = 0; i < details.results.length; i++) {
|
|
661
675
|
const r = details.results[i];
|
|
@@ -667,22 +681,31 @@ export const askToolRenderer = {
|
|
|
667
681
|
? uiTheme.styledSymbol("status.success", "success")
|
|
668
682
|
: uiTheme.styledSymbol("status.warning", "warning");
|
|
669
683
|
|
|
670
|
-
|
|
684
|
+
container.addChild(
|
|
685
|
+
new Text(` ${uiTheme.fg("dim", branch)} ${statusIcon} ${uiTheme.fg("dim", `[${r.id}]`)}`, 0, 0),
|
|
686
|
+
);
|
|
687
|
+
container.addChild(new Markdown(r.question, 3, 0, mdTheme, accentStyle));
|
|
671
688
|
|
|
689
|
+
let answerText = "";
|
|
672
690
|
if (r.customInput) {
|
|
673
|
-
|
|
691
|
+
answerText = `${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", r.customInput)}`;
|
|
674
692
|
} else if (r.selectedOptions.length > 0) {
|
|
675
693
|
for (let j = 0; j < r.selectedOptions.length; j++) {
|
|
676
694
|
const isLast = j === r.selectedOptions.length - 1;
|
|
677
695
|
const optBranch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
678
|
-
|
|
696
|
+
const selectedLabel = renderInlineMarkdown(r.selectedOptions[j], mdTheme, t =>
|
|
697
|
+
uiTheme.fg("toolOutput", t),
|
|
698
|
+
);
|
|
699
|
+
answerText += `\n${continuation}${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${selectedLabel}`;
|
|
679
700
|
}
|
|
680
701
|
} else {
|
|
681
|
-
|
|
702
|
+
answerText = `${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`;
|
|
703
|
+
}
|
|
704
|
+
if (answerText) {
|
|
705
|
+
container.addChild(new Text(answerText, 0, 0));
|
|
682
706
|
}
|
|
683
707
|
}
|
|
684
|
-
|
|
685
|
-
return new Text(text, 0, 0);
|
|
708
|
+
return container;
|
|
686
709
|
}
|
|
687
710
|
|
|
688
711
|
// Single question result
|
|
@@ -693,25 +716,28 @@ export const askToolRenderer = {
|
|
|
693
716
|
}
|
|
694
717
|
|
|
695
718
|
const hasSelection = details.customInput || (details.selectedOptions && details.selectedOptions.length > 0);
|
|
696
|
-
const header = renderStatusLine(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
);
|
|
700
|
-
|
|
701
|
-
let text = header;
|
|
719
|
+
const header = renderStatusLine({ icon: hasSelection ? "success" : "warning", title: "Ask" }, uiTheme);
|
|
720
|
+
const container = new Container();
|
|
721
|
+
container.addChild(new Text(header, 0, 0));
|
|
722
|
+
container.addChild(new Markdown(details.question, 1, 0, mdTheme, accentStyle));
|
|
702
723
|
|
|
724
|
+
let answerText = "";
|
|
703
725
|
if (details.customInput) {
|
|
704
|
-
|
|
726
|
+
answerText = ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", details.customInput)}`;
|
|
705
727
|
} else if (details.selectedOptions && details.selectedOptions.length > 0) {
|
|
706
728
|
for (let i = 0; i < details.selectedOptions.length; i++) {
|
|
707
729
|
const isLast = i === details.selectedOptions.length - 1;
|
|
708
730
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
709
|
-
|
|
731
|
+
const selectedLabel = renderInlineMarkdown(details.selectedOptions[i], mdTheme, t =>
|
|
732
|
+
uiTheme.fg("toolOutput", t),
|
|
733
|
+
);
|
|
734
|
+
answerText += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${selectedLabel}`;
|
|
710
735
|
}
|
|
711
736
|
} else {
|
|
712
|
-
|
|
737
|
+
answerText = ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`;
|
|
713
738
|
}
|
|
739
|
+
container.addChild(new Text(answerText, 0, 0));
|
|
714
740
|
|
|
715
|
-
return
|
|
741
|
+
return container;
|
|
716
742
|
},
|
|
717
743
|
};
|
|
@@ -295,7 +295,6 @@ export async function runInteractiveBashPty(
|
|
|
295
295
|
},
|
|
296
296
|
): Promise<BashInteractiveResult> {
|
|
297
297
|
const sink = new OutputSink({ artifactPath: options.artifactPath, artifactId: options.artifactId });
|
|
298
|
-
let pendingChunks = Promise.resolve();
|
|
299
298
|
const result = await ui.custom<BashInteractiveResult>(
|
|
300
299
|
(tui, uiTheme, _keybindings, done) => {
|
|
301
300
|
const session = new PtySession();
|
|
@@ -309,7 +308,6 @@ export async function runInteractiveBashPty(
|
|
|
309
308
|
tui.requestRender();
|
|
310
309
|
void (async () => {
|
|
311
310
|
await component.flushOutput();
|
|
312
|
-
await pendingChunks;
|
|
313
311
|
const summary = await sink.dump();
|
|
314
312
|
done({
|
|
315
313
|
exitCode: run.exitCode,
|
|
@@ -362,15 +360,13 @@ export async function runInteractiveBashPty(
|
|
|
362
360
|
if (finished || err || !chunk) return;
|
|
363
361
|
component.appendOutput(chunk);
|
|
364
362
|
const normalizedChunk = normalizeCaptureChunk(chunk);
|
|
365
|
-
|
|
363
|
+
sink.push(normalizedChunk);
|
|
366
364
|
tui.requestRender();
|
|
367
365
|
},
|
|
368
366
|
)
|
|
369
367
|
.then(finalize)
|
|
370
368
|
.catch(error => {
|
|
371
|
-
|
|
372
|
-
.then(() => sink.push(`PTY error: ${error instanceof Error ? error.message : String(error)}\n`))
|
|
373
|
-
.catch(() => {});
|
|
369
|
+
sink.push(`PTY error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
374
370
|
finalize({ exitCode: undefined, cancelled: false, timedOut: false });
|
|
375
371
|
});
|
|
376
372
|
return component;
|
|
@@ -5,45 +5,7 @@
|
|
|
5
5
|
* this interceptor provides helpful error messages directing them to use
|
|
6
6
|
* the specialized tools instead.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
9
|
-
|
|
10
|
-
export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
|
|
11
|
-
{
|
|
12
|
-
pattern: "^\\s*(cat|head|tail|less|more)\\s+",
|
|
13
|
-
tool: "read",
|
|
14
|
-
message: "Use the `read` tool instead of cat/head/tail. It provides better context and handles binary files.",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
pattern: "^\\s*(grep|rg|ripgrep|ag|ack)\\s+",
|
|
18
|
-
tool: "grep",
|
|
19
|
-
message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
pattern: "^\\s*(find|fd|locate)\\s+.*(-name|-iname|-type|--type|-glob)",
|
|
23
|
-
tool: "find",
|
|
24
|
-
message: "Use the `find` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
pattern: "^\\s*sed\\s+(-i|--in-place)",
|
|
28
|
-
tool: "edit",
|
|
29
|
-
message: "Use the `edit` tool instead of sed -i. It provides diff preview and fuzzy matching.",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
pattern: "^\\s*perl\\s+.*-[pn]?i",
|
|
33
|
-
tool: "edit",
|
|
34
|
-
message: "Use the `edit` tool instead of perl -i. It provides diff preview and fuzzy matching.",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
pattern: "^\\s*awk\\s+.*-i\\s+inplace",
|
|
38
|
-
tool: "edit",
|
|
39
|
-
message: "Use the `edit` tool instead of awk -i inplace. It provides diff preview and fuzzy matching.",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
pattern: "^\\s*(echo|printf|cat\\s*<<)\\s+.*[^|]>\\s*\\S",
|
|
43
|
-
tool: "write",
|
|
44
|
-
message: "Use the `write` tool instead of echo/cat redirection. It handles encoding and provides confirmation.",
|
|
45
|
-
},
|
|
46
|
-
];
|
|
8
|
+
import { type BashInterceptorRule, DEFAULT_BASH_INTERCEPTOR_RULES } from "../config/settings-schema";
|
|
47
9
|
|
|
48
10
|
export interface InterceptionResult {
|
|
49
11
|
/** If true, the bash command should be blocked */
|
|
@@ -131,7 +131,7 @@ async function resolveInternalUrlToPath(
|
|
|
131
131
|
return resolvedLocalPath;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
if (!internalRouter
|
|
134
|
+
if (!internalRouter?.canHandle(url)) {
|
|
135
135
|
throw new ToolError(
|
|
136
136
|
`Cannot resolve ${scheme}:// URL in bash command: ${url}\n` +
|
|
137
137
|
"Internal URL router is unavailable for this protocol in the current session.",
|
package/src/tools/browser.ts
CHANGED
|
@@ -564,7 +564,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
564
564
|
if (this.#page && !this.#page.isClosed()) {
|
|
565
565
|
return this.#page;
|
|
566
566
|
}
|
|
567
|
-
if (!this.#browser
|
|
567
|
+
if (!this.#browser?.isConnected()) {
|
|
568
568
|
return this.#resetBrowser(params);
|
|
569
569
|
}
|
|
570
570
|
this.#page = await this.#browser.newPage();
|
|
@@ -287,7 +287,7 @@ async function loadImageFromUrl(imageUrl: string, signal?: AbortSignal): Promise
|
|
|
287
287
|
throw new Error(`Image download failed (${response.status}): ${rawText}`);
|
|
288
288
|
}
|
|
289
289
|
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
290
|
-
if (!contentType
|
|
290
|
+
if (!contentType?.startsWith("image/")) {
|
|
291
291
|
throw new Error(`Unsupported image type from URL: ${imageUrl}`);
|
|
292
292
|
}
|
|
293
293
|
const buffer = await response.bytes();
|
package/src/tools/python.ts
CHANGED
|
@@ -289,8 +289,8 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
|
289
289
|
const executorOptions: PythonExecutorOptions = {
|
|
290
290
|
...baseExecutorOptions,
|
|
291
291
|
reset: isFirstCell ? reset : false,
|
|
292
|
-
onChunk:
|
|
293
|
-
|
|
292
|
+
onChunk: chunk => {
|
|
293
|
+
outputSink!.push(chunk);
|
|
294
294
|
},
|
|
295
295
|
};
|
|
296
296
|
|
package/src/tools/resolve.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
|
|
|
54
54
|
): Promise<AgentToolResult<ResolveToolDetails>> {
|
|
55
55
|
return untilAborted(signal, async () => {
|
|
56
56
|
const store = this.session.pendingActionStore;
|
|
57
|
-
if (!store
|
|
57
|
+
if (!store?.hasPending) {
|
|
58
58
|
throw new ToolError("No pending action to resolve. Nothing to apply or discard.");
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const EXIT_STDIO_GRACE_MS = 100;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wait for a child process to terminate without hanging on inherited stdio handles.
|
|
7
|
+
*
|
|
8
|
+
* Daemonized descendants can inherit the child's stdout/stderr pipe handles. In that
|
|
9
|
+
* case the child emits `exit`, but `close` can hang forever even though the original
|
|
10
|
+
* process is already gone. We wait briefly for stdio to end, then forcibly stop
|
|
11
|
+
* tracking the inherited handles.
|
|
12
|
+
*/
|
|
13
|
+
export function waitForChildProcess(child: ChildProcess): Promise<number | null> {
|
|
14
|
+
const { promise, resolve, reject } = Promise.withResolvers<number | null>();
|
|
15
|
+
|
|
16
|
+
let settled = false;
|
|
17
|
+
let exited = false;
|
|
18
|
+
let exitCode: number | null = null;
|
|
19
|
+
let postExitTimer: NodeJS.Timeout | undefined;
|
|
20
|
+
let stdoutEnded = child.stdout === null;
|
|
21
|
+
let stderrEnded = child.stderr === null;
|
|
22
|
+
|
|
23
|
+
const cleanup = () => {
|
|
24
|
+
if (postExitTimer) {
|
|
25
|
+
clearTimeout(postExitTimer);
|
|
26
|
+
postExitTimer = undefined;
|
|
27
|
+
}
|
|
28
|
+
child.removeListener("error", onError);
|
|
29
|
+
child.removeListener("exit", onExit);
|
|
30
|
+
child.removeListener("close", onClose);
|
|
31
|
+
child.stdout?.removeListener("end", onStdoutEnd);
|
|
32
|
+
child.stderr?.removeListener("end", onStderrEnd);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const finalize = (code: number | null) => {
|
|
36
|
+
if (settled) return;
|
|
37
|
+
settled = true;
|
|
38
|
+
cleanup();
|
|
39
|
+
child.stdout?.destroy();
|
|
40
|
+
child.stderr?.destroy();
|
|
41
|
+
resolve(code);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const maybeFinalizeAfterExit = () => {
|
|
45
|
+
if (!exited || settled) return;
|
|
46
|
+
if (stdoutEnded && stderrEnded) {
|
|
47
|
+
finalize(exitCode);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const onStdoutEnd = () => {
|
|
52
|
+
stdoutEnded = true;
|
|
53
|
+
maybeFinalizeAfterExit();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const onStderrEnd = () => {
|
|
57
|
+
stderrEnded = true;
|
|
58
|
+
maybeFinalizeAfterExit();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const onError = (err: Error) => {
|
|
62
|
+
if (settled) return;
|
|
63
|
+
settled = true;
|
|
64
|
+
cleanup();
|
|
65
|
+
reject(err);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const onExit = (code: number | null) => {
|
|
69
|
+
exited = true;
|
|
70
|
+
exitCode = code;
|
|
71
|
+
maybeFinalizeAfterExit();
|
|
72
|
+
if (!settled) {
|
|
73
|
+
postExitTimer = setTimeout(() => finalize(code), EXIT_STDIO_GRACE_MS);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const onClose = (code: number | null) => {
|
|
78
|
+
finalize(code);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
child.stdout?.once("end", onStdoutEnd);
|
|
82
|
+
child.stderr?.once("end", onStderrEnd);
|
|
83
|
+
child.once("error", onError);
|
|
84
|
+
child.once("exit", onExit);
|
|
85
|
+
child.once("close", onClose);
|
|
86
|
+
|
|
87
|
+
return promise;
|
|
88
|
+
}
|