@oh-my-pi/pi-coding-agent 16.5.0 → 16.5.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 +26 -0
- package/dist/cli.js +3336 -3318
- package/dist/types/advisor/advise-tool.d.ts +12 -1
- package/dist/types/advisor/runtime.d.ts +41 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-cli.d.ts +3 -0
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +8 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +1 -0
- package/dist/types/eval/bridge-timeout.d.ts +9 -1
- package/dist/types/eval/js/context-manager.d.ts +5 -3
- package/dist/types/eval/js/process-entry.d.ts +6 -0
- package/dist/types/eval/js/worker-core.d.ts +15 -1
- package/dist/types/eval/py/spawn-options.d.ts +10 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -7
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +26 -6
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +12 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/exit-diagnostics.d.ts +11 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +11 -0
- package/dist/types/subprocess/worker-client.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -0
- package/dist/types/web/search/provider.d.ts +10 -3
- package/dist/types/web/search/providers/codex.d.ts +5 -4
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +830 -42
- package/src/advisor/advise-tool.ts +17 -1
- package/src/advisor/runtime.ts +288 -67
- package/src/autolearn/controller.ts +15 -3
- package/src/cli/args.ts +12 -0
- package/src/cli/auth-broker-cli.ts +30 -11
- package/src/cli/auth-gateway-cli.ts +5 -1
- package/src/cli/dry-balance-cli.ts +14 -4
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +62 -11
- package/src/cli/usage-cli.ts +58 -5
- package/src/cli/usage-error.ts +7 -0
- package/src/cli.ts +23 -1
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commands/token.ts +3 -1
- package/src/config/api-key-resolver.ts +12 -3
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +21 -7
- package/src/config/models-config.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +106 -0
- package/src/eval/__tests__/js-context-manager.test.ts +158 -1
- package/src/eval/__tests__/kernel-spawn.test.ts +12 -0
- package/src/eval/__tests__/process-entry-import.test.ts +27 -0
- package/src/eval/agent-bridge.ts +121 -116
- package/src/eval/bridge-timeout.ts +20 -2
- package/src/eval/executor-base.ts +85 -7
- package/src/eval/jl/kernel.ts +2 -1
- package/src/eval/js/context-manager.ts +109 -32
- package/src/eval/js/process-entry.ts +27 -0
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/js/worker-core.ts +70 -9
- package/src/eval/js/worker-entry.ts +1 -1
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/spawn-options.ts +13 -0
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/eval/rb/kernel.ts +2 -1
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/plugins/manager.ts +21 -0
- package/src/internal-urls/memory-protocol.ts +13 -9
- package/src/lsp/client.ts +7 -1
- package/src/main.ts +29 -0
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/modes/components/chat-transcript-builder.ts +22 -1
- package/src/modes/components/status-line/component.ts +10 -1
- package/src/modes/components/transcript-container.ts +110 -7
- package/src/modes/controllers/command-controller.ts +12 -4
- package/src/modes/controllers/event-controller.ts +80 -15
- package/src/modes/controllers/selector-controller.ts +15 -3
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/rpc/host-tools.ts +15 -0
- package/src/modes/rpc/rpc-mode.ts +123 -48
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +54 -0
- package/src/modes/utils/ui-helpers.ts +27 -1
- package/src/modes/workflow.ts +6 -5
- package/src/prompts/advisor/system.md +1 -0
- package/src/sdk.ts +33 -3
- package/src/session/agent-session.ts +239 -17
- package/src/session/exit-diagnostics.ts +108 -0
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/helpers/active-oauth-account.ts +22 -2
- package/src/slash-commands/helpers/logout.ts +23 -3
- package/src/slash-commands/helpers/usage-report.ts +14 -2
- package/src/subprocess/worker-client.ts +9 -2
- package/src/task/executor.ts +8 -0
- package/src/task/render.test.ts +36 -0
- package/src/task/render.ts +55 -43
- package/src/tools/bash-skill-urls.ts +4 -1
- package/src/tools/bash.ts +1 -0
- package/src/tools/write.ts +82 -9
- package/src/tools/yield.ts +29 -1
- package/src/web/search/index.ts +39 -22
- package/src/web/search/provider.ts +33 -16
- package/src/web/search/providers/codex.ts +68 -21
package/src/eval/agent-bridge.ts
CHANGED
|
@@ -449,130 +449,135 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
449
449
|
// runtime is parked waiting for the result, and the cell timeout must
|
|
450
450
|
// not abort us mid-cherry-pick or mid-nested-commit. The clock restarts
|
|
451
451
|
// only after we hand control back to the runtime.
|
|
452
|
-
const { result, mergeSummary, changesApplied } = await withBridgeTimeoutPause(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const preferredBackend = isIsolated ? parseIsolationMode(isolationMode) : undefined;
|
|
463
|
-
|
|
464
|
-
const result = await (async () => {
|
|
465
|
-
if (!isolationContext) {
|
|
466
|
-
return taskExecutor.runSubprocess(baseRunOptions);
|
|
467
|
-
}
|
|
468
|
-
const taskStart = Date.now();
|
|
469
|
-
return runIsolatedSubprocess({
|
|
470
|
-
baseOptions: baseRunOptions,
|
|
471
|
-
context: isolationContext,
|
|
472
|
-
preferredBackend,
|
|
473
|
-
agentId: id,
|
|
474
|
-
mergeMode,
|
|
475
|
-
artifactsDir,
|
|
476
|
-
description: trimToUndefined(parsed.label),
|
|
477
|
-
buildCommitMessage,
|
|
478
|
-
buildFailureResult: err => {
|
|
452
|
+
const { result, mergeSummary, changesApplied } = await withBridgeTimeoutPause(
|
|
453
|
+
options.emitStatus,
|
|
454
|
+
async () => {
|
|
455
|
+
let isolationContext: IsolationContext | null = null;
|
|
456
|
+
if (isIsolated) {
|
|
457
|
+
try {
|
|
458
|
+
isolationContext = await prepareIsolationContext(options.session.cwd);
|
|
459
|
+
} catch (err) {
|
|
479
460
|
const message = err instanceof Error ? err.message : String(err);
|
|
480
|
-
|
|
481
|
-
index: 0,
|
|
482
|
-
id,
|
|
483
|
-
agent: effectiveAgent.name,
|
|
484
|
-
agentSource: effectiveAgent.source,
|
|
485
|
-
task: renderSubagentPrompt(assignment),
|
|
486
|
-
assignment,
|
|
487
|
-
description: trimToUndefined(parsed.label),
|
|
488
|
-
exitCode: 1,
|
|
489
|
-
output: "",
|
|
490
|
-
stderr: message,
|
|
491
|
-
truncated: false,
|
|
492
|
-
durationMs: Date.now() - taskStart,
|
|
493
|
-
tokens: 0,
|
|
494
|
-
requests: 0,
|
|
495
|
-
modelOverride,
|
|
496
|
-
error: message,
|
|
497
|
-
};
|
|
498
|
-
},
|
|
499
|
-
});
|
|
500
|
-
})();
|
|
501
|
-
|
|
502
|
-
if (result.exitCode !== 0 || result.error || result.aborted) {
|
|
503
|
-
const failureMessage = buildSubagentFailureMessage(agentName, result);
|
|
504
|
-
const recoveryHint = isIsolated ? await buildIsolationRecoveryHint(result, artifactsDir) : "";
|
|
505
|
-
throw new ToolError(`${failureMessage}${recoveryHint}`);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
let mergeSummary = "";
|
|
509
|
-
let changesApplied: boolean | null = null;
|
|
510
|
-
if (isIsolated && isolationContext) {
|
|
511
|
-
if (applyChanges) {
|
|
512
|
-
const outcome = await mergeIsolatedChanges({
|
|
513
|
-
result,
|
|
514
|
-
repoRoot: isolationContext.repoRoot,
|
|
515
|
-
mergeMode,
|
|
516
|
-
});
|
|
517
|
-
mergeSummary = outcome.summary;
|
|
518
|
-
changesApplied = outcome.changesApplied;
|
|
519
|
-
if (outcome.changesApplied === false) {
|
|
520
|
-
const summaryText = outcome.summary.trim();
|
|
521
|
-
const recoveryHint = await buildIsolationRecoveryHint(result, artifactsDir);
|
|
522
|
-
throw new ToolError(
|
|
523
|
-
`agent() isolated apply failed for ${result.id}${summaryText ? `: ${summaryText}` : ""}${recoveryHint}`,
|
|
524
|
-
);
|
|
461
|
+
throw new ToolError(`Isolated agent() execution requires a git repository. ${message}`);
|
|
525
462
|
}
|
|
463
|
+
}
|
|
464
|
+
const preferredBackend = isIsolated ? parseIsolationMode(isolationMode) : undefined;
|
|
526
465
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
466
|
+
const result = await (async () => {
|
|
467
|
+
if (!isolationContext) {
|
|
468
|
+
return taskExecutor.runSubprocess(baseRunOptions);
|
|
469
|
+
}
|
|
470
|
+
const taskStart = Date.now();
|
|
471
|
+
return runIsolatedSubprocess({
|
|
472
|
+
baseOptions: baseRunOptions,
|
|
473
|
+
context: isolationContext,
|
|
474
|
+
preferredBackend,
|
|
475
|
+
agentId: id,
|
|
530
476
|
mergeMode,
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
477
|
+
artifactsDir,
|
|
478
|
+
description: trimToUndefined(parsed.label),
|
|
479
|
+
buildCommitMessage,
|
|
480
|
+
buildFailureResult: err => {
|
|
481
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
482
|
+
return {
|
|
483
|
+
index: 0,
|
|
484
|
+
id,
|
|
485
|
+
agent: effectiveAgent.name,
|
|
486
|
+
agentSource: effectiveAgent.source,
|
|
487
|
+
task: renderSubagentPrompt(assignment),
|
|
488
|
+
assignment,
|
|
489
|
+
description: trimToUndefined(parsed.label),
|
|
490
|
+
exitCode: 1,
|
|
491
|
+
output: "",
|
|
492
|
+
stderr: message,
|
|
493
|
+
truncated: false,
|
|
494
|
+
durationMs: Date.now() - taskStart,
|
|
495
|
+
tokens: 0,
|
|
496
|
+
requests: 0,
|
|
497
|
+
modelOverride,
|
|
498
|
+
error: message,
|
|
499
|
+
};
|
|
500
|
+
},
|
|
534
501
|
});
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
mergeSummary =
|
|
502
|
+
})();
|
|
503
|
+
|
|
504
|
+
if (result.exitCode !== 0 || result.error || result.aborted) {
|
|
505
|
+
const failureMessage = buildSubagentFailureMessage(agentName, result);
|
|
506
|
+
const recoveryHint = isIsolated ? await buildIsolationRecoveryHint(result, artifactsDir) : "";
|
|
507
|
+
throw new ToolError(`${failureMessage}${recoveryHint}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let mergeSummary = "";
|
|
511
|
+
let changesApplied: boolean | null = null;
|
|
512
|
+
if (isIsolated && isolationContext) {
|
|
513
|
+
if (applyChanges) {
|
|
514
|
+
const outcome = await mergeIsolatedChanges({
|
|
515
|
+
result,
|
|
516
|
+
repoRoot: isolationContext.repoRoot,
|
|
517
|
+
mergeMode,
|
|
518
|
+
});
|
|
519
|
+
mergeSummary = outcome.summary;
|
|
520
|
+
changesApplied = outcome.changesApplied;
|
|
521
|
+
if (outcome.changesApplied === false) {
|
|
522
|
+
const summaryText = outcome.summary.trim();
|
|
523
|
+
const recoveryHint = await buildIsolationRecoveryHint(result, artifactsDir);
|
|
524
|
+
throw new ToolError(
|
|
525
|
+
`agent() isolated apply failed for ${result.id}${summaryText ? `: ${summaryText}` : ""}${recoveryHint}`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const nestedSummary = await applyEligibleNestedPatches({
|
|
530
|
+
result,
|
|
531
|
+
repoRoot: isolationContext.repoRoot,
|
|
532
|
+
mergeMode,
|
|
533
|
+
changesApplied: outcome.changesApplied,
|
|
534
|
+
mergedBranchForNestedPatches: outcome.mergedBranchForNestedPatches,
|
|
535
|
+
commitMessage: buildCommitMessage(),
|
|
536
|
+
});
|
|
537
|
+
mergeSummary += nestedSummary;
|
|
538
|
+
if (structured && nestedSummary.trim()) {
|
|
539
|
+
const recoveryHint = await buildIsolationRecoveryHint(
|
|
540
|
+
{ ...result, patchPath: undefined, branchName: undefined },
|
|
541
|
+
artifactsDir,
|
|
542
|
+
);
|
|
543
|
+
throw new ToolError(
|
|
544
|
+
`agent() isolated nested patch apply failed for ${result.id}: ${plainIsolationSummary(nestedSummary)}${recoveryHint}`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
} else if (result.branchName) {
|
|
548
|
+
mergeSummary = `\n\nIsolation: changes captured on branch \`${result.branchName}\` (apply=false). Not merged.`;
|
|
549
|
+
} else if (result.patchPath) {
|
|
550
|
+
mergeSummary = `\n\nIsolation: changes captured at \`${result.patchPath}\` (apply=false). Not applied.`;
|
|
553
551
|
} else {
|
|
554
|
-
|
|
552
|
+
const nestedPatches = result.nestedPatches ?? [];
|
|
553
|
+
if (nestedPatches.length > 0) {
|
|
554
|
+
mergeSummary = `\n\nIsolation: changes captured for ${nestedPatches.length} nested repositor${nestedPatches.length === 1 ? "y" : "ies"} (apply=false). Not applied.`;
|
|
555
|
+
} else {
|
|
556
|
+
mergeSummary = "\n\nIsolation: no changes captured.";
|
|
557
|
+
}
|
|
555
558
|
}
|
|
556
559
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
560
|
+
|
|
561
|
+
// Clean up the temp artifacts dir we created for this call only when the
|
|
562
|
+
// caller will not need files from it later. Keep it when the runtime helper
|
|
563
|
+
// will return an `agent://` handle (the `.md`/`.jsonl` backing files live
|
|
564
|
+
// here) and on `apply=false` (`changesApplied === null`) where the caller
|
|
565
|
+
// consumes `details.patchPath` / `details.branchName` /
|
|
566
|
+
// `details.nestedPatches` out of band. Failed isolated applies throw
|
|
567
|
+
// earlier with a recovery hint, so they never reach this gate.
|
|
568
|
+
const shouldCleanupTempArtifacts =
|
|
569
|
+
tempArtifactsDir && !parsed.handle && (!isIsolated || changesApplied === true);
|
|
570
|
+
if (shouldCleanupTempArtifacts) {
|
|
571
|
+
await fs.rm(artifactsDir, { recursive: true, force: true });
|
|
572
|
+
unregisterArtifactsDir?.();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
options.session.recordEvalSubagentUsage?.(result.usage?.output ?? 0);
|
|
576
|
+
|
|
577
|
+
return { result, mergeSummary, changesApplied };
|
|
578
|
+
},
|
|
579
|
+
{ deferExternalAbort: true },
|
|
580
|
+
);
|
|
576
581
|
|
|
577
582
|
return {
|
|
578
583
|
text: structured ? result.output : result.output + mergeSummary,
|
|
@@ -26,6 +26,15 @@ export function isEvalTimeoutControlEvent(event: JsStatusEvent): boolean {
|
|
|
26
26
|
return event.op === EVAL_TIMEOUT_PAUSE_OP || event.op === EVAL_TIMEOUT_RESUME_OP;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Optional behavior for a timeout pause around a host bridge call. */
|
|
30
|
+
export interface BridgeTimeoutPauseOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Marks the pause as an `agent()` call whose already-started work must finish
|
|
33
|
+
* before an external eval abort reaches the kernel.
|
|
34
|
+
*/
|
|
35
|
+
deferExternalAbort?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
/**
|
|
30
39
|
* Run {@link operation} while suspending the eval watchdog through
|
|
31
40
|
* {@link emitStatus}. A no-op wrapper when no status sink is wired.
|
|
@@ -33,12 +42,21 @@ export function isEvalTimeoutControlEvent(event: JsStatusEvent): boolean {
|
|
|
33
42
|
export async function withBridgeTimeoutPause<T>(
|
|
34
43
|
emitStatus: ((event: JsStatusEvent) => void) | undefined,
|
|
35
44
|
operation: () => Promise<T>,
|
|
45
|
+
options?: BridgeTimeoutPauseOptions,
|
|
36
46
|
): Promise<T> {
|
|
37
47
|
if (!emitStatus) return operation();
|
|
38
|
-
emitStatus(
|
|
48
|
+
emitStatus(
|
|
49
|
+
options?.deferExternalAbort
|
|
50
|
+
? { op: EVAL_TIMEOUT_PAUSE_OP, deferExternalAbort: true }
|
|
51
|
+
: { op: EVAL_TIMEOUT_PAUSE_OP },
|
|
52
|
+
);
|
|
39
53
|
try {
|
|
40
54
|
return await operation();
|
|
41
55
|
} finally {
|
|
42
|
-
emitStatus(
|
|
56
|
+
emitStatus(
|
|
57
|
+
options?.deferExternalAbort
|
|
58
|
+
? { op: EVAL_TIMEOUT_RESUME_OP, deferExternalAbort: true }
|
|
59
|
+
: { op: EVAL_TIMEOUT_RESUME_OP },
|
|
60
|
+
);
|
|
43
61
|
}
|
|
44
62
|
}
|
|
@@ -3,7 +3,7 @@ import { Settings } from "../config/settings";
|
|
|
3
3
|
import { OutputSink } from "../session/streaming-output";
|
|
4
4
|
import type { ToolSession } from "../tools";
|
|
5
5
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
6
|
-
import { isEvalTimeoutControlEvent } from "./bridge-timeout";
|
|
6
|
+
import { EVAL_TIMEOUT_PAUSE_OP, EVAL_TIMEOUT_RESUME_OP, isEvalTimeoutControlEvent } from "./bridge-timeout";
|
|
7
7
|
import type { JsStatusEvent } from "./js/shared/types";
|
|
8
8
|
import type { KernelDisplayOutput } from "./py/display";
|
|
9
9
|
import { registerPyToolBridge } from "./py/tool-bridge";
|
|
@@ -158,6 +158,77 @@ export async function waitForPromiseWithCancellation<T>(
|
|
|
158
158
|
return await resultPromise;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
interface BridgeAbortShield {
|
|
162
|
+
signal: AbortSignal | undefined;
|
|
163
|
+
abortRequested: boolean;
|
|
164
|
+
timedOut: boolean;
|
|
165
|
+
handleStatus?: (event: JsStatusEvent) => void;
|
|
166
|
+
dispose?: () => void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function createBridgeAbortShield(source: AbortSignal | undefined): BridgeAbortShield {
|
|
170
|
+
const shield: BridgeAbortShield = {
|
|
171
|
+
signal: undefined,
|
|
172
|
+
abortRequested: false,
|
|
173
|
+
timedOut: false,
|
|
174
|
+
};
|
|
175
|
+
if (!source) return shield;
|
|
176
|
+
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
let pauseDepth = 0;
|
|
179
|
+
let abortReason: unknown;
|
|
180
|
+
let removeAbortListener: (() => void) | undefined;
|
|
181
|
+
|
|
182
|
+
const requestAbort = (reason: unknown): void => {
|
|
183
|
+
shield.abortRequested = true;
|
|
184
|
+
shield.timedOut =
|
|
185
|
+
shield.timedOut ||
|
|
186
|
+
(typeof DOMException !== "undefined" && reason instanceof DOMException
|
|
187
|
+
? reason.name === "TimeoutError"
|
|
188
|
+
: reason instanceof Error && reason.name === "TimeoutError");
|
|
189
|
+
abortReason = reason;
|
|
190
|
+
if (pauseDepth > 0 || controller.signal.aborted) return;
|
|
191
|
+
controller.abort(reason);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const onAbort = (): void => {
|
|
195
|
+
const reason = source.reason;
|
|
196
|
+
requestAbort(reason);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
shield.signal = controller.signal;
|
|
200
|
+
shield.handleStatus = (event: JsStatusEvent): void => {
|
|
201
|
+
if (event.deferExternalAbort !== true) return;
|
|
202
|
+
if (event.op === EVAL_TIMEOUT_PAUSE_OP) {
|
|
203
|
+
pauseDepth++;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (event.op !== EVAL_TIMEOUT_RESUME_OP || pauseDepth === 0) return;
|
|
207
|
+
pauseDepth--;
|
|
208
|
+
if (shield.abortRequested && !controller.signal.aborted) controller.abort(abortReason);
|
|
209
|
+
};
|
|
210
|
+
shield.dispose = (): void => {
|
|
211
|
+
removeAbortListener?.();
|
|
212
|
+
removeAbortListener = undefined;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (source.aborted) {
|
|
216
|
+
requestAbort(source.reason);
|
|
217
|
+
} else {
|
|
218
|
+
source.addEventListener("abort", onAbort, { once: true });
|
|
219
|
+
removeAbortListener = () => {
|
|
220
|
+
source.removeEventListener("abort", onAbort);
|
|
221
|
+
};
|
|
222
|
+
if (source.aborted) {
|
|
223
|
+
source.removeEventListener("abort", onAbort);
|
|
224
|
+
removeAbortListener = undefined;
|
|
225
|
+
requestAbort(source.reason);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return shield;
|
|
230
|
+
}
|
|
231
|
+
|
|
161
232
|
export function createCancelledKernelResult(output: string): KernelExecutionResult {
|
|
162
233
|
const outputBytes = Buffer.byteLength(output, "utf-8");
|
|
163
234
|
const outputLines = output.length > 0 ? 1 : 0;
|
|
@@ -304,9 +375,11 @@ export async function executeWithKernelBase<
|
|
|
304
375
|
const displayOutputs: KernelDisplayOutput[] = [];
|
|
305
376
|
const deadlineMs = (resolveDeadlineMs ?? getExecutionDeadlineMs)(options);
|
|
306
377
|
let executionTimeoutMs: number | undefined;
|
|
378
|
+
const abortShield = createBridgeAbortShield(options?.signal);
|
|
307
379
|
|
|
308
380
|
const collectDisplay = (output: KernelDisplayOutput): void => {
|
|
309
381
|
if (output.type === "status") {
|
|
382
|
+
abortShield.handleStatus?.(output.event);
|
|
310
383
|
options?.onStatus?.(output.event);
|
|
311
384
|
if (!isJulia && isEvalTimeoutControlEvent(output.event)) return;
|
|
312
385
|
}
|
|
@@ -320,8 +393,11 @@ export async function executeWithKernelBase<
|
|
|
320
393
|
options?.toolSession && options?.bridgeSessionId
|
|
321
394
|
? registerPyToolBridge(options.bridgeSessionId, runId, {
|
|
322
395
|
toolSession: options.toolSession,
|
|
323
|
-
signal:
|
|
396
|
+
signal: abortShield.signal,
|
|
324
397
|
emitStatus,
|
|
398
|
+
abortRequested: () => {
|
|
399
|
+
return abortShield.abortRequested;
|
|
400
|
+
},
|
|
325
401
|
})
|
|
326
402
|
: null;
|
|
327
403
|
|
|
@@ -338,14 +414,15 @@ export async function executeWithKernelBase<
|
|
|
338
414
|
cwd: options?.cwd,
|
|
339
415
|
env: buildKernelEnvPatch(options ?? ({} as TOptions)),
|
|
340
416
|
id: runId,
|
|
341
|
-
signal:
|
|
417
|
+
signal: abortShield.signal,
|
|
342
418
|
timeoutMs: executionTimeoutMs,
|
|
343
419
|
onChunk: text => sink.push(text),
|
|
344
420
|
onDisplay: output => collectDisplay(output),
|
|
345
421
|
});
|
|
346
422
|
|
|
347
|
-
if (result.cancelled) {
|
|
348
|
-
const
|
|
423
|
+
if (result.cancelled || abortShield.abortRequested) {
|
|
424
|
+
const timedOut = result.timedOut || abortShield.timedOut;
|
|
425
|
+
const annotation = timedOut
|
|
349
426
|
? formatKernelTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs, result.kernelKilled ?? false)
|
|
350
427
|
: undefined;
|
|
351
428
|
const dumped = await sink.dump(annotation);
|
|
@@ -397,8 +474,8 @@ export async function executeWithKernelBase<
|
|
|
397
474
|
stdinRequested: false,
|
|
398
475
|
};
|
|
399
476
|
} catch (err) {
|
|
400
|
-
if (isCancellationError(err, cancelledErrorClass) ||
|
|
401
|
-
const timedOut = isTimedOutCancellation(err, cancelledErrorClass,
|
|
477
|
+
if (isCancellationError(err, cancelledErrorClass) || abortShield.abortRequested || abortShield.signal?.aborted) {
|
|
478
|
+
const timedOut = abortShield.timedOut || isTimedOutCancellation(err, cancelledErrorClass, abortShield.signal);
|
|
402
479
|
const dumped = await sink.dump(
|
|
403
480
|
timedOut ? formatTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs) : undefined,
|
|
404
481
|
);
|
|
@@ -421,5 +498,6 @@ export async function executeWithKernelBase<
|
|
|
421
498
|
throw error;
|
|
422
499
|
} finally {
|
|
423
500
|
unregisterBridge?.();
|
|
501
|
+
abortShield.dispose?.();
|
|
424
502
|
}
|
|
425
503
|
}
|
package/src/eval/jl/kernel.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { $ } from "bun";
|
|
|
13
13
|
import { Settings } from "../../config/settings";
|
|
14
14
|
import { BaseKernel, getRemainingTimeMs, type KernelStartOptions } from "../kernel-base";
|
|
15
15
|
import type { KernelDisplayOutput } from "../py/display";
|
|
16
|
-
import { hostHasInheritableConsole, shouldHideKernelWindow } from "../py/spawn-options";
|
|
16
|
+
import { hostHasInheritableConsole, shouldDetachKernel, shouldHideKernelWindow } from "../py/spawn-options";
|
|
17
17
|
import { JULIA_PRELUDE } from "./prelude";
|
|
18
18
|
import RUNNER_SCRIPT from "./runner.jl" with { type: "text" };
|
|
19
19
|
import {
|
|
@@ -187,6 +187,7 @@ export class JuliaKernel extends BaseKernel<KernelExecuteOptions> {
|
|
|
187
187
|
[runtime.juliaPath, "--startup-file=no", "--history-file=no", "--color=no", "--project=@.", scriptPath],
|
|
188
188
|
{
|
|
189
189
|
cwd: options.cwd,
|
|
190
|
+
detached: shouldDetachKernel(process.platform),
|
|
190
191
|
env: spawnEnv,
|
|
191
192
|
stdin: "pipe",
|
|
192
193
|
stdout: "pipe",
|