@oh-my-pi/pi-coding-agent 14.9.9 → 15.0.1
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 +123 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +9 -9
- package/scripts/build-binary.ts +5 -0
- package/scripts/format-prompts.ts +1 -1
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +11 -29
- package/src/commands/acp.ts +24 -0
- package/src/commands/launch.ts +6 -4
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +13 -2
- package/src/config/model-resolver.ts +31 -4
- package/src/config/settings-schema.ts +102 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/edit/index.ts +22 -1
- package/src/edit/modes/patch.ts +10 -0
- package/src/edit/modes/replace.ts +3 -0
- package/src/edit/renderer.ts +17 -1
- package/src/eval/js/context-manager.ts +1 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +122 -50
- package/src/eval/js/shared/runtime.ts +31 -4
- package/src/eval/js/tool-bridge.ts +43 -21
- package/src/eval/py/executor.ts +5 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/runner.ts +55 -2
- package/src/extensibility/extensions/types.ts +98 -221
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +42 -1
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +9 -10
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/issue-pr-protocol.ts +577 -0
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +6 -3
- package/src/internal-urls/types.ts +22 -1
- package/src/main.ts +24 -11
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +412 -71
- package/src/modes/acp/acp-client-bridge.ts +152 -0
- package/src/modes/acp/acp-event-mapper.ts +180 -15
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/read-tool-group.ts +29 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +55 -4
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +27 -10
- package/src/modes/controllers/event-controller.ts +60 -18
- package/src/modes/controllers/extension-ui-controller.ts +8 -2
- package/src/modes/controllers/input-controller.ts +85 -39
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +675 -39
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/rpc-mode.ts +30 -88
- package/src/modes/runtime-init.ts +115 -0
- package/src/modes/theme/defaults/dark-poimandres.json +2 -0
- package/src/modes/theme/defaults/light-poimandres.json +2 -0
- package/src/modes/theme/theme.ts +18 -6
- package/src/modes/types.ts +20 -5
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +25 -6
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/agents/designer.md +5 -5
- package/src/prompts/agents/explore.md +7 -7
- package/src/prompts/agents/init.md +9 -9
- package/src/prompts/agents/librarian.md +14 -14
- package/src/prompts/agents/plan.md +4 -4
- package/src/prompts/agents/reviewer.md +5 -5
- package/src/prompts/agents/task.md +10 -10
- package/src/prompts/commands/orchestrate.md +2 -2
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/memories/consolidation.md +2 -2
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +2 -2
- package/src/prompts/system/custom-system-prompt.md +2 -2
- package/src/prompts/system/eager-todo.md +6 -6
- package/src/prompts/system/handoff-document.md +1 -1
- package/src/prompts/system/plan-mode-active.md +25 -24
- package/src/prompts/system/plan-mode-approved.md +4 -4
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +3 -3
- package/src/prompts/system/project-prompt.md +4 -4
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/subagent-yield-reminder.md +4 -4
- package/src/prompts/system/system-prompt.md +72 -71
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/tools/apply-patch.md +1 -1
- package/src/prompts/tools/ast-edit.md +3 -3
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/browser.md +3 -3
- package/src/prompts/tools/checkpoint.md +3 -3
- package/src/prompts/tools/find.md +3 -3
- package/src/prompts/tools/github.md +2 -5
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +104 -116
- package/src/prompts/tools/image-gen.md +3 -3
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +6 -6
- package/src/prompts/tools/read.md +8 -7
- package/src/prompts/tools/replace.md +5 -5
- package/src/prompts/tools/resolve.md +6 -5
- package/src/prompts/tools/retain.md +1 -1
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search.md +2 -2
- package/src/prompts/tools/ssh.md +2 -2
- package/src/prompts/tools/task.md +12 -6
- package/src/prompts/tools/web-search.md +2 -2
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +81 -17
- package/src/session/agent-session.ts +656 -125
- package/src/session/blob-store.ts +36 -3
- package/src/session/client-bridge.ts +81 -0
- package/src/session/compaction/errors.ts +31 -0
- package/src/session/compaction/index.ts +1 -0
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/acp-builtins.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +717 -116
- package/src/slash-commands/helpers/context-report.ts +39 -0
- package/src/slash-commands/helpers/format.ts +23 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +193 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +91 -0
- package/src/slash-commands/types.ts +126 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/executor.ts +27 -10
- package/src/task/index.ts +20 -1
- package/src/task/render.ts +27 -18
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +203 -6
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +21 -10
- package/src/tools/eval.ts +3 -1
- package/src/tools/fetch.ts +15 -4
- package/src/tools/find.ts +39 -39
- package/src/tools/gh-renderer.ts +0 -12
- package/src/tools/gh.ts +689 -182
- package/src/tools/github-cache.ts +548 -0
- package/src/tools/index.ts +25 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/output-meta.ts +176 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +605 -239
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/write.ts +67 -10
- package/src/tui/code-cell.ts +70 -2
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/gemini.ts +35 -95
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { parse as babelParse } from "@babel/parser";
|
|
2
2
|
|
|
3
|
-
// Static ESM `import` declarations are not valid inside vm.runInContext (script-mode parsing)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Static ESM `import` declarations are not valid inside vm.runInContext (script-mode parsing),
|
|
4
|
+
// and dynamic `import(...)` would otherwise resolve specifiers against the worker module's URL
|
|
5
|
+
// instead of the session cwd. We rewrite both forms so they route through the worker-injected
|
|
6
|
+
// `__omp_import__` helper, which resolves the specifier against the active session cwd. A real
|
|
7
|
+
// parser keeps imports embedded in string literals, template literals, or comments intact.
|
|
7
8
|
|
|
8
9
|
type BabelImportDeclaration = {
|
|
9
10
|
type: "ImportDeclaration";
|
|
@@ -25,26 +26,78 @@ type BabelLexicalDecl =
|
|
|
25
26
|
| { type: "VariableDeclaration"; kind: "const" | "let" | "var"; start: number; end: number }
|
|
26
27
|
| { type: "ClassDeclaration"; start: number; end: number; id: { start: number; end: number; name: string } | null };
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
type BabelExpressionStatement = {
|
|
30
|
+
type: "ExpressionStatement";
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
expression?: { type?: string };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type BabelProgramNode = BabelImportDeclaration | BabelLexicalDecl | BabelExpressionStatement | { type: string };
|
|
37
|
+
|
|
38
|
+
type BabelNode = { type: string; start: number; end: number; [key: string]: unknown };
|
|
39
|
+
|
|
40
|
+
function parseProgram(code: string): { program: { body: ReadonlyArray<BabelProgramNode> } } | null {
|
|
41
|
+
try {
|
|
42
|
+
return babelParse(code, {
|
|
43
|
+
sourceType: "module",
|
|
44
|
+
allowAwaitOutsideFunction: true,
|
|
45
|
+
allowReturnOutsideFunction: true,
|
|
46
|
+
allowImportExportEverywhere: true,
|
|
47
|
+
allowNewTargetOutsideFunction: true,
|
|
48
|
+
allowSuperOutsideMethod: true,
|
|
49
|
+
allowUndeclaredExports: true,
|
|
50
|
+
errorRecovery: true,
|
|
51
|
+
}) as unknown as { program: { body: ReadonlyArray<BabelProgramNode> } };
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildOmpImportCall(sourceLiteral: string, optionsLiteral: string | undefined): string {
|
|
29
58
|
// Route every static import through the worker-injected `__omp_import__` helper so the
|
|
30
59
|
// specifier resolves against the session cwd (and `with`-attribute imports keep working).
|
|
31
|
-
return
|
|
60
|
+
return optionsLiteral ? `__omp_import__(${sourceLiteral}, ${optionsLiteral})` : `__omp_import__(${sourceLiteral})`;
|
|
32
61
|
}
|
|
33
62
|
|
|
34
|
-
|
|
63
|
+
// Walks every node in `root`, depth-first, invoking `visit` on each one. Skips Babel's
|
|
64
|
+
// non-AST bookkeeping fields so we don't recurse into source locations or comment arrays.
|
|
65
|
+
function walkNodes(root: unknown, visit: (node: BabelNode) => void): void {
|
|
66
|
+
const stack: unknown[] = [root];
|
|
67
|
+
while (stack.length > 0) {
|
|
68
|
+
const current = stack.pop();
|
|
69
|
+
if (!current || typeof current !== "object") continue;
|
|
70
|
+
if (Array.isArray(current)) {
|
|
71
|
+
for (let i = current.length - 1; i >= 0; i--) stack.push(current[i]);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const node = current as Record<string, unknown>;
|
|
75
|
+
if (typeof node.type === "string") visit(node as unknown as BabelNode);
|
|
76
|
+
for (const key in node) {
|
|
77
|
+
if (key === "loc" || key === "extra" || key === "range") continue;
|
|
78
|
+
if (key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
|
|
79
|
+
const value = node[key];
|
|
80
|
+
if (value && typeof value === "object") stack.push(value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildOptionsLiteral(node: BabelImportDeclaration): string | undefined {
|
|
35
86
|
const attrs = node.attributes;
|
|
36
87
|
if (!attrs || attrs.length === 0) return undefined;
|
|
37
88
|
const pairs = attrs.map(attr => {
|
|
38
89
|
const key = attr.key.type === "Identifier" ? attr.key.name : JSON.stringify(attr.key.value);
|
|
39
90
|
return `${key}: ${JSON.stringify(attr.value.value)}`;
|
|
40
91
|
});
|
|
41
|
-
|
|
92
|
+
// Native dynamic import takes options as `{ with: { ... } }`. `__omp_import__` forwards the
|
|
93
|
+
// options bag verbatim, so we wrap the attribute pairs accordingly.
|
|
94
|
+
return `{ with: { ${pairs.join(", ")} } }`;
|
|
42
95
|
}
|
|
43
96
|
|
|
44
97
|
function rewriteImportNode(node: BabelImportDeclaration): string {
|
|
45
98
|
const sourceLiteral = JSON.stringify(node.source.value);
|
|
46
|
-
const
|
|
47
|
-
const importCall =
|
|
99
|
+
const optionsLiteral = buildOptionsLiteral(node);
|
|
100
|
+
const importCall = buildOmpImportCall(sourceLiteral, optionsLiteral);
|
|
48
101
|
|
|
49
102
|
let defaultName: string | undefined;
|
|
50
103
|
let namespaceName: string | undefined;
|
|
@@ -73,37 +126,43 @@ function rewriteImportNode(node: BabelImportDeclaration): string {
|
|
|
73
126
|
return `await ${importCall};`;
|
|
74
127
|
}
|
|
75
128
|
|
|
76
|
-
export function
|
|
129
|
+
export function rewriteImports(code: string): string {
|
|
77
130
|
if (!code.includes("import")) return code;
|
|
78
131
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
ast = babelParse(code, {
|
|
82
|
-
sourceType: "module",
|
|
83
|
-
allowAwaitOutsideFunction: true,
|
|
84
|
-
allowReturnOutsideFunction: true,
|
|
85
|
-
allowImportExportEverywhere: true,
|
|
86
|
-
allowNewTargetOutsideFunction: true,
|
|
87
|
-
allowSuperOutsideMethod: true,
|
|
88
|
-
allowUndeclaredExports: true,
|
|
89
|
-
errorRecovery: true,
|
|
90
|
-
}) as unknown as typeof ast;
|
|
91
|
-
} catch {
|
|
132
|
+
const ast = parseProgram(code);
|
|
133
|
+
if (!ast) {
|
|
92
134
|
// Parser bailed entirely — let the VM surface the real syntax error.
|
|
93
135
|
return code;
|
|
94
136
|
}
|
|
95
137
|
|
|
96
|
-
|
|
138
|
+
type Edit = { start: number; end: number; text: string };
|
|
139
|
+
const edits: Edit[] = [];
|
|
140
|
+
|
|
141
|
+
// Top-level static `import` declarations become `await __omp_import__(...)` calls.
|
|
97
142
|
for (const node of ast.program.body) {
|
|
98
|
-
if (node.type
|
|
143
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
144
|
+
const decl = node as unknown as BabelImportDeclaration;
|
|
145
|
+
edits.push({ start: decl.start, end: decl.end, text: rewriteImportNode(decl) });
|
|
99
146
|
}
|
|
100
|
-
|
|
147
|
+
|
|
148
|
+
// Dynamic `import(...)` expressions (anywhere) get their callee swapped for `__omp_import__`
|
|
149
|
+
// so the specifier resolves against the session cwd instead of the worker module's URL.
|
|
150
|
+
walkNodes(ast, node => {
|
|
151
|
+
if (node.type !== "CallExpression") return;
|
|
152
|
+
const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
|
|
153
|
+
const callee = call.callee;
|
|
154
|
+
if (!callee || callee.type !== "Import" || typeof callee.start !== "number" || typeof callee.end !== "number")
|
|
155
|
+
return;
|
|
156
|
+
edits.push({ start: callee.start, end: callee.end, text: "__omp_import__" });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (edits.length === 0) return code;
|
|
101
160
|
|
|
102
161
|
// Splice from the back so earlier offsets stay valid.
|
|
103
|
-
|
|
162
|
+
edits.sort((a, b) => b.start - a.start);
|
|
104
163
|
let result = code;
|
|
105
|
-
for (const
|
|
106
|
-
result = result.slice(0,
|
|
164
|
+
for (const edit of edits) {
|
|
165
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
107
166
|
}
|
|
108
167
|
return result;
|
|
109
168
|
}
|
|
@@ -121,22 +180,11 @@ export function rewriteStaticImports(code: string): string {
|
|
|
121
180
|
* Nested declarations (inside functions, blocks, classes) are left alone \u2014 they're
|
|
122
181
|
* scoped to their enclosing function/block regardless of `var` vs `let`/`const`.
|
|
123
182
|
*/
|
|
124
|
-
|
|
183
|
+
function demoteTopLevelLexicals(code: string): string {
|
|
125
184
|
if (!/\b(?:const|let|class)\b/.test(code)) return code;
|
|
126
185
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
ast = babelParse(code, {
|
|
130
|
-
sourceType: "module",
|
|
131
|
-
allowAwaitOutsideFunction: true,
|
|
132
|
-
allowReturnOutsideFunction: true,
|
|
133
|
-
allowImportExportEverywhere: true,
|
|
134
|
-
allowNewTargetOutsideFunction: true,
|
|
135
|
-
allowSuperOutsideMethod: true,
|
|
136
|
-
allowUndeclaredExports: true,
|
|
137
|
-
errorRecovery: true,
|
|
138
|
-
}) as unknown as typeof ast;
|
|
139
|
-
} catch {
|
|
186
|
+
const ast = parseProgram(code);
|
|
187
|
+
if (!ast) {
|
|
140
188
|
return code;
|
|
141
189
|
}
|
|
142
190
|
|
|
@@ -172,6 +220,24 @@ export function demoteTopLevelLexicals(code: string): string {
|
|
|
172
220
|
return result;
|
|
173
221
|
}
|
|
174
222
|
|
|
223
|
+
function returnFinalExpression(code: string): { source: string; returned: boolean } {
|
|
224
|
+
const ast = parseProgram(code);
|
|
225
|
+
const body = ast?.program.body;
|
|
226
|
+
if (!body) return { source: code, returned: false };
|
|
227
|
+
let lastIndex = body.length - 1;
|
|
228
|
+
while (lastIndex >= 0 && body[lastIndex]?.type === "EmptyStatement") lastIndex--;
|
|
229
|
+
const last = lastIndex >= 0 ? body[lastIndex] : undefined;
|
|
230
|
+
if (last?.type !== "ExpressionStatement") return { source: code, returned: false };
|
|
231
|
+
|
|
232
|
+
const expression = last as BabelExpressionStatement;
|
|
233
|
+
const prefix = code.slice(0, expression.start);
|
|
234
|
+
const statement = code.slice(expression.start, expression.end);
|
|
235
|
+
const suffix = code.slice(expression.end);
|
|
236
|
+
const semicolonMatch = statement.match(/;\s*$/);
|
|
237
|
+
const trimmedStatement = semicolonMatch ? statement.slice(0, semicolonMatch.index) : statement;
|
|
238
|
+
return { source: `${prefix}__omp_set_final_expr__((${trimmedStatement}));${suffix}`, returned: true };
|
|
239
|
+
}
|
|
240
|
+
|
|
175
241
|
/**
|
|
176
242
|
* Strip TypeScript syntax (type annotations, `interface`, `as`, `satisfies`, generics in
|
|
177
243
|
* call expressions, etc.) before the import/lexical rewriters parse the code. We use Bun's
|
|
@@ -182,7 +248,7 @@ export function demoteTopLevelLexicals(code: string): string {
|
|
|
182
248
|
* common case avoids an extra transpile pass. We detect "looks like TS" with a cheap regex
|
|
183
249
|
* before invoking the transpiler.
|
|
184
250
|
*/
|
|
185
|
-
|
|
251
|
+
function stripTypeScript(code: string): string {
|
|
186
252
|
if (!LOOKS_LIKE_TS.test(code)) return code;
|
|
187
253
|
try {
|
|
188
254
|
return new Bun.Transpiler({ loader: "ts" }).transformSync(code);
|
|
@@ -198,14 +264,20 @@ export function stripTypeScript(code: string): string {
|
|
|
198
264
|
const LOOKS_LIKE_TS =
|
|
199
265
|
/(?:\binterface\s+\w|\btype\s+\w+\s*=|\b(?:as|satisfies)\s+(?:[A-Z]|\bconst\b)|:\s*(?:string|number|boolean|any|unknown|void|never|object|[A-Z]\w*)\b|<\s*[A-Z]\w*\s*[,>])/;
|
|
200
266
|
|
|
201
|
-
export function wrapCode(code: string): { source: string; asyncWrapped: boolean } {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
267
|
+
export function wrapCode(code: string): { source: string; asyncWrapped: boolean; finalExpressionReturned: boolean } {
|
|
268
|
+
const stripped = stripTypeScript(code);
|
|
269
|
+
const finalExpression = returnFinalExpression(stripped);
|
|
270
|
+
const rewritten = {
|
|
271
|
+
source: demoteTopLevelLexicals(rewriteImports(finalExpression.source)),
|
|
272
|
+
returned: finalExpression.returned,
|
|
273
|
+
};
|
|
274
|
+
const needsAsyncWrapper = /\bawait\b|\breturn\b/.test(rewritten.source);
|
|
204
275
|
if (!needsAsyncWrapper) {
|
|
205
|
-
return { source: rewritten, asyncWrapped: false };
|
|
276
|
+
return { source: rewritten.source, asyncWrapped: false, finalExpressionReturned: rewritten.returned };
|
|
206
277
|
}
|
|
207
278
|
return {
|
|
208
|
-
source: `(async () => {\n${rewritten}\n})()`,
|
|
279
|
+
source: `(async () => {\n${rewritten.source}\n})()`,
|
|
209
280
|
asyncWrapped: true,
|
|
281
|
+
finalExpressionReturned: rewritten.returned,
|
|
210
282
|
};
|
|
211
283
|
}
|
|
@@ -4,6 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import * as util from "node:util";
|
|
6
6
|
|
|
7
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
|
+
|
|
7
9
|
import { ToolError } from "../../../tools/tool-errors";
|
|
8
10
|
import { createHelpers, type HelperBundle } from "./helpers";
|
|
9
11
|
import { awaitMaybePromise, indirectEval } from "./indirect-eval";
|
|
@@ -48,6 +50,8 @@ export class JsRuntime {
|
|
|
48
50
|
readonly sessionId: string;
|
|
49
51
|
#env: Map<string, string>;
|
|
50
52
|
#getHooks: () => RuntimeHooks | null;
|
|
53
|
+
#finalExpressionSet = false;
|
|
54
|
+
#finalExpressionValue: unknown;
|
|
51
55
|
|
|
52
56
|
constructor(opts: RuntimeOptions) {
|
|
53
57
|
this.#cwd = opts.initialCwd;
|
|
@@ -82,8 +86,20 @@ export class JsRuntime {
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
async run(code: string, filename?: string): Promise<unknown> {
|
|
89
|
+
this.#finalExpressionSet = false;
|
|
90
|
+
this.#finalExpressionValue = undefined;
|
|
85
91
|
const wrapped = wrapCode(code);
|
|
86
92
|
const value = indirectEval(wrapped.source, filename);
|
|
93
|
+
if (wrapped.finalExpressionReturned) {
|
|
94
|
+
const awaited = await awaitMaybePromise(value);
|
|
95
|
+
if (this.#finalExpressionSet) {
|
|
96
|
+
const finalValue = this.#finalExpressionValue;
|
|
97
|
+
this.#finalExpressionSet = false;
|
|
98
|
+
this.#finalExpressionValue = undefined;
|
|
99
|
+
return await awaitMaybePromise(finalValue);
|
|
100
|
+
}
|
|
101
|
+
return awaited;
|
|
102
|
+
}
|
|
87
103
|
return await awaitMaybePromise(value);
|
|
88
104
|
}
|
|
89
105
|
|
|
@@ -97,7 +113,14 @@ export class JsRuntime {
|
|
|
97
113
|
hooks.onDisplay({ type: "image", data: record.data, mimeType: record.mimeType });
|
|
98
114
|
return;
|
|
99
115
|
}
|
|
100
|
-
|
|
116
|
+
try {
|
|
117
|
+
hooks.onDisplay({ type: "json", data: structuredClone(value) });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.debug("js displayValue: value is not structured-cloneable, falling back to text", {
|
|
120
|
+
error: err instanceof Error ? err.message : String(err),
|
|
121
|
+
});
|
|
122
|
+
hooks.onText(`${Object.prototype.toString.call(value)}\n`);
|
|
123
|
+
}
|
|
101
124
|
return;
|
|
102
125
|
}
|
|
103
126
|
hooks.onText(`${String(value)}\n`);
|
|
@@ -112,9 +135,9 @@ export class JsRuntime {
|
|
|
112
135
|
if (!hooks) throw new ToolError("Tool calls are only valid inside an active run");
|
|
113
136
|
return await hooks.callTool(name, args);
|
|
114
137
|
},
|
|
115
|
-
__omp_import__: async (source: string,
|
|
138
|
+
__omp_import__: async (source: string, options?: ImportCallOptions) => {
|
|
116
139
|
const target = resolveImportSpecifier(this.#cwd, source);
|
|
117
|
-
return
|
|
140
|
+
return options !== undefined ? await import(target, options) : await import(target);
|
|
118
141
|
},
|
|
119
142
|
__omp_emit_status__: (op: string, data: Record<string, unknown> = {}) => {
|
|
120
143
|
const event: JsStatusEvent = { op, ...data };
|
|
@@ -126,6 +149,10 @@ export class JsRuntime {
|
|
|
126
149
|
this.#getHooks()?.onText(text.endsWith("\n") ? text : `${text}\n`);
|
|
127
150
|
},
|
|
128
151
|
__omp_display__: (value: unknown) => this.displayValue(value),
|
|
152
|
+
__omp_set_final_expr__: (value: unknown) => {
|
|
153
|
+
this.#finalExpressionSet = true;
|
|
154
|
+
this.#finalExpressionValue = value;
|
|
155
|
+
},
|
|
129
156
|
webcrypto: crypto,
|
|
130
157
|
// `process` is intentionally not overridden — user code gets the host worker's real
|
|
131
158
|
// `process` object. Subsetting it caused segfaults in workers that share state with
|
|
@@ -152,7 +179,7 @@ function buildRequire(cwd: string): NodeJS.Require {
|
|
|
152
179
|
}
|
|
153
180
|
|
|
154
181
|
/**
|
|
155
|
-
* Resolve an import specifier emitted by `
|
|
182
|
+
* Resolve an import specifier emitted by `rewriteImports` against the active session
|
|
156
183
|
* cwd. Relative paths (`./`, `../`, `/`) and bare specifiers (`pkg`, `@scope/pkg`) both go
|
|
157
184
|
* through `Bun.resolveSync` rooted at the cwd so user-pasted ESM behaves as if it lived in
|
|
158
185
|
* the project — not next to the worker module. URL-like specifiers (`file://`, `data:`,
|
|
@@ -17,7 +17,17 @@ type ToolValue =
|
|
|
17
17
|
text: string;
|
|
18
18
|
details?: unknown;
|
|
19
19
|
images?: Array<{ mimeType: string; data: string }>;
|
|
20
|
+
hasError?: boolean;
|
|
20
21
|
};
|
|
22
|
+
function toolResultHasError(result: AgentToolResult): boolean {
|
|
23
|
+
if ((result as { isError?: unknown }).isError === true) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (!(result.details && typeof result.details === "object")) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return (result.details as { isError?: unknown }).isError === true;
|
|
30
|
+
}
|
|
21
31
|
|
|
22
32
|
function getTool(session: ToolSession, name: string): AgentTool {
|
|
23
33
|
const tool = session.getToolByName?.(name);
|
|
@@ -38,7 +48,13 @@ function normalizeArgs(args: unknown): unknown {
|
|
|
38
48
|
return record;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
function summarizeToolResult(
|
|
51
|
+
function summarizeToolResult(
|
|
52
|
+
name: string,
|
|
53
|
+
args: unknown,
|
|
54
|
+
result: AgentToolResult,
|
|
55
|
+
text: string,
|
|
56
|
+
hasError: boolean,
|
|
57
|
+
): JsStatusEvent {
|
|
42
58
|
const record = (args && typeof args === "object" ? (args as Record<string, unknown>) : {}) as Record<
|
|
43
59
|
string,
|
|
44
60
|
unknown
|
|
@@ -46,39 +62,41 @@ function summarizeToolResult(name: string, args: unknown, result: AgentToolResul
|
|
|
46
62
|
const details = (
|
|
47
63
|
result.details && typeof result.details === "object" ? (result.details as Record<string, unknown>) : {}
|
|
48
64
|
) as Record<string, unknown>;
|
|
65
|
+
const withError = (event: JsStatusEvent): JsStatusEvent =>
|
|
66
|
+
hasError ? { ...event, hasError: true, error: text.slice(0, 500) } : event;
|
|
49
67
|
|
|
50
68
|
switch (name) {
|
|
51
69
|
case "read":
|
|
52
|
-
return { op: "read", path: record.path, chars: text.length, preview: text.slice(0, 500) };
|
|
70
|
+
return withError({ op: "read", path: record.path, chars: text.length, preview: text.slice(0, 500) });
|
|
53
71
|
case "write":
|
|
54
|
-
return {
|
|
72
|
+
return withError({
|
|
55
73
|
op: "write",
|
|
56
74
|
path: record.path,
|
|
57
75
|
chars: typeof record.content === "string" ? record.content.length : 0,
|
|
58
|
-
};
|
|
76
|
+
});
|
|
59
77
|
case "grep":
|
|
60
|
-
return {
|
|
78
|
+
return withError({
|
|
61
79
|
op: "grep",
|
|
62
80
|
pattern: record.pattern,
|
|
63
81
|
path: record.path,
|
|
64
82
|
count: details.matchCount ?? undefined,
|
|
65
|
-
};
|
|
83
|
+
});
|
|
66
84
|
case "find":
|
|
67
|
-
return {
|
|
85
|
+
return withError({
|
|
68
86
|
op: "find",
|
|
69
87
|
pattern: record.pattern,
|
|
70
88
|
count: details.fileCount ?? undefined,
|
|
71
89
|
matches: Array.isArray(details.files) ? details.files.slice(0, 20) : undefined,
|
|
72
|
-
};
|
|
90
|
+
});
|
|
73
91
|
case "bash":
|
|
74
|
-
return {
|
|
92
|
+
return withError({
|
|
75
93
|
op: "run",
|
|
76
94
|
cmd: record.command,
|
|
77
95
|
code: typeof details.exitCode === "number" ? details.exitCode : undefined,
|
|
78
96
|
output: text.slice(0, 500),
|
|
79
|
-
};
|
|
97
|
+
});
|
|
80
98
|
default:
|
|
81
|
-
return { op: name, chars: text.length };
|
|
99
|
+
return withError({ op: name, chars: text.length });
|
|
82
100
|
}
|
|
83
101
|
}
|
|
84
102
|
|
|
@@ -97,21 +115,25 @@ export async function callSessionTool(name: string, args: unknown, options: Tool
|
|
|
97
115
|
content.type === "image" && typeof content.mimeType === "string" && typeof content.data === "string",
|
|
98
116
|
);
|
|
99
117
|
const text = textBlocks.map(block => block.text).join("");
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
const hasError = toolResultHasError(result);
|
|
119
|
+
options.emitStatus?.(summarizeToolResult(name, normalizedArgs, result, text, hasError));
|
|
120
|
+
if (result.details === undefined && imageBlocks.length === 0 && !hasError) {
|
|
102
121
|
return text;
|
|
103
122
|
}
|
|
104
|
-
|
|
123
|
+
const value: Exclude<ToolValue, string> = {
|
|
105
124
|
text,
|
|
106
125
|
details: result.details,
|
|
107
|
-
images:
|
|
108
|
-
imageBlocks.length > 0
|
|
109
|
-
? imageBlocks.map(block => ({
|
|
110
|
-
mimeType: block.mimeType,
|
|
111
|
-
data: block.data,
|
|
112
|
-
}))
|
|
113
|
-
: undefined,
|
|
114
126
|
};
|
|
127
|
+
if (imageBlocks.length > 0) {
|
|
128
|
+
value.images = imageBlocks.map(block => ({
|
|
129
|
+
mimeType: block.mimeType,
|
|
130
|
+
data: block.data,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
if (hasError) {
|
|
134
|
+
value.hasError = true;
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
115
137
|
} catch (error) {
|
|
116
138
|
options.emitStatus?.({
|
|
117
139
|
op: name,
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { Settings } from "../../config/settings";
|
|
2
3
|
import { OutputSink } from "../../session/streaming-output";
|
|
3
4
|
import type { ToolSession } from "../../tools";
|
|
5
|
+
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../../tools/output-meta";
|
|
4
6
|
import type { JsStatusEvent } from "../js/shared/types";
|
|
5
7
|
import type { KernelDisplayOutput } from "./display";
|
|
6
8
|
import {
|
|
@@ -815,10 +817,13 @@ async function executeWithKernel(
|
|
|
815
817
|
code: string,
|
|
816
818
|
options: PythonExecutorOptions | undefined,
|
|
817
819
|
): Promise<PythonResult> {
|
|
820
|
+
const settings = await Settings.init();
|
|
818
821
|
const sink = new OutputSink({
|
|
819
822
|
onChunk: options?.onChunk,
|
|
820
823
|
artifactPath: options?.artifactPath,
|
|
821
824
|
artifactId: options?.artifactId,
|
|
825
|
+
headBytes: resolveOutputSinkHeadBytes(settings),
|
|
826
|
+
maxColumns: resolveOutputMaxColumns(settings),
|
|
822
827
|
});
|
|
823
828
|
const displayOutputs: KernelDisplayOutput[] = [];
|
|
824
829
|
const deadlineMs = getExecutionDeadlineMs(options);
|
package/src/exa/factory.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { TObject, TProperties } from "@sinclair/typebox";
|
|
5
5
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
6
|
-
import { callExaTool, findApiKey, formatSearchResults, isSearchResponse } from "./mcp-client";
|
|
6
|
+
import { callExaTool, findApiKey, formatGenericResponse, formatSearchResults, isSearchResponse } from "./mcp-client";
|
|
7
7
|
import type { ExaRenderDetails } from "./types";
|
|
8
8
|
|
|
9
9
|
/** Creates an Exa tool with standardized API key handling, error wrapping, and optional search response formatting. */
|
|
@@ -44,7 +44,7 @@ export function createExaTool(
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return {
|
|
47
|
-
content: [{ type: "text" as const, text:
|
|
47
|
+
content: [{ type: "text" as const, text: formatGenericResponse(response) }],
|
|
48
48
|
details: { raw: response, toolName: name },
|
|
49
49
|
};
|
|
50
50
|
} catch (error) {
|
package/src/exa/mcp-client.ts
CHANGED
|
@@ -174,6 +174,79 @@ export function formatSearchResults(data: ExaSearchResponse): string {
|
|
|
174
174
|
|
|
175
175
|
return output.trim();
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Format a non-search MCP response as human-readable text.
|
|
179
|
+
* Handles objects, arrays, primitives, and common MCP response shapes.
|
|
180
|
+
*/
|
|
181
|
+
export function formatGenericResponse(data: unknown): string {
|
|
182
|
+
if (data === null || data === undefined) return "No result.";
|
|
183
|
+
if (typeof data === "string") return data;
|
|
184
|
+
if (typeof data === "number" || typeof data === "boolean") return String(data);
|
|
185
|
+
|
|
186
|
+
if (Array.isArray(data)) {
|
|
187
|
+
if (data.length === 0) return "(empty)";
|
|
188
|
+
const parts: string[] = [];
|
|
189
|
+
for (let i = 0; i < data.length; i++) {
|
|
190
|
+
const item = data[i];
|
|
191
|
+
if (typeof item === "object" && item !== null) {
|
|
192
|
+
const record = item as Record<string, unknown>;
|
|
193
|
+
const title = (record.title ?? record.name ?? record.id ?? `Item ${i + 1}`) as string;
|
|
194
|
+
parts.push(`\n### ${title}`);
|
|
195
|
+
for (const [k, v] of Object.entries(record)) {
|
|
196
|
+
if (["title", "name", "id"].includes(k)) continue;
|
|
197
|
+
parts.push(`- **${k}:** ${formatValue(v)}`);
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
parts.push(`- ${formatValue(item)}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return parts.join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (typeof data === "object") {
|
|
207
|
+
const record = data as Record<string, unknown>;
|
|
208
|
+
if (record.content && Array.isArray(record.content)) {
|
|
209
|
+
// MCP-style content array — extract text blocks
|
|
210
|
+
const texts = record.content
|
|
211
|
+
.filter(
|
|
212
|
+
(c: unknown): c is { type: string; text?: string } =>
|
|
213
|
+
typeof c === "object" && c !== null && (c as Record<string, unknown>)?.type === "text",
|
|
214
|
+
)
|
|
215
|
+
.map(c => c.text ?? "")
|
|
216
|
+
.filter(Boolean);
|
|
217
|
+
if (texts.length > 0) return texts.join("\n");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const lines: string[] = [];
|
|
221
|
+
for (const [k, v] of Object.entries(record)) {
|
|
222
|
+
if (k === "content") continue; // handled above
|
|
223
|
+
if (v === null || v === undefined) continue;
|
|
224
|
+
if (typeof v === "object") {
|
|
225
|
+
const formatted = formatGenericResponse(v);
|
|
226
|
+
if (formatted) lines.push(`- **${k}:**\n${indent(formatted, 2)}`);
|
|
227
|
+
} else {
|
|
228
|
+
lines.push(`- **${k}:** ${formatValue(v)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return lines.join("\n") || "(empty)";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return String(data);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function formatValue(v: unknown): string {
|
|
238
|
+
if (v === null || v === undefined) return "—";
|
|
239
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
240
|
+
return String(v);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function indent(text: string, spaces: number): string {
|
|
244
|
+
const pad = " ".repeat(spaces);
|
|
245
|
+
return text
|
|
246
|
+
.split("\n")
|
|
247
|
+
.map(line => pad + line)
|
|
248
|
+
.join("\n");
|
|
249
|
+
}
|
|
177
250
|
|
|
178
251
|
/** Check if result is a search response */
|
|
179
252
|
export function isSearchResponse(data: unknown): data is ExaSearchResponse {
|
|
@@ -260,7 +333,7 @@ export class MCPWrappedTool implements CustomTool<TSchema, ExaRenderDetails> {
|
|
|
260
333
|
}
|
|
261
334
|
|
|
262
335
|
return {
|
|
263
|
-
content: [{ type: "text" as const, text:
|
|
336
|
+
content: [{ type: "text" as const, text: formatGenericResponse(response) }],
|
|
264
337
|
details: { raw: response, toolName: this.config.name },
|
|
265
338
|
};
|
|
266
339
|
} catch (error) {
|
|
@@ -7,6 +7,7 @@ import * as fs from "node:fs/promises";
|
|
|
7
7
|
import { executeShell, type MinimizerOptions, Shell } from "@oh-my-pi/pi-natives";
|
|
8
8
|
import { Settings, type ShellMinimizerSettings } from "../config/settings";
|
|
9
9
|
import { OutputSink } from "../session/streaming-output";
|
|
10
|
+
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
10
11
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
11
12
|
import { NON_INTERACTIVE_ENV } from "./non-interactive-env";
|
|
12
13
|
|
|
@@ -64,7 +65,8 @@ async function resolveShellCwd(cwd: string | undefined): Promise<string | undefi
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
/** Translate `ShellMinimizerSettings` into native `MinimizerOptions`, or `undefined` when disabled. */
|
|
69
|
+
export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerOptions | undefined {
|
|
68
70
|
if (!group.enabled) return undefined;
|
|
69
71
|
return {
|
|
70
72
|
enabled: true,
|
|
@@ -94,6 +96,8 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
94
96
|
onChunk: options?.onChunk,
|
|
95
97
|
artifactPath: options?.artifactPath,
|
|
96
98
|
artifactId: options?.artifactId,
|
|
99
|
+
headBytes: resolveOutputSinkHeadBytes(settings),
|
|
100
|
+
maxColumns: resolveOutputMaxColumns(settings),
|
|
97
101
|
// Throttle the streaming preview callback to avoid saturating the
|
|
98
102
|
// event loop when commands produce massive output (e.g. seq 1 50M).
|
|
99
103
|
chunkThrottleMs: options?.onChunk ? 50 : 0,
|