@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.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 +54 -0
- package/README.md +1 -1
- package/dist/cli.js +4659 -4261
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +316 -47
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +118 -755
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +5 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/usage-cli.ts +10 -1
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +209 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +56 -15
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +157 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +210 -136
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1892 -12226
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.ts +6 -3
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +0 -46
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
package/src/config/settings.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
15
|
import * as os from "node:os";
|
|
16
16
|
import * as path from "node:path";
|
|
17
|
+
import { configureCredentialRedaction } from "@oh-my-pi/pi-ai/providers/transform-messages";
|
|
17
18
|
import { configureProviderMaxInFlightRequests } from "@oh-my-pi/pi-ai/stream";
|
|
18
19
|
import {
|
|
19
20
|
getAgentDbPath,
|
|
@@ -33,7 +34,9 @@ import type { ModelRole } from "../config/model-roles";
|
|
|
33
34
|
import { loadCapability } from "../discovery";
|
|
34
35
|
import { isLightTheme, setAutoThemeMapping, setColorBlindMode, setSymbolPreset } from "../modes/theme/theme";
|
|
35
36
|
import { AgentStorage } from "../session/agent-storage";
|
|
37
|
+
import { AUTO_IMAGE_PROVIDER_ORDER, isImageProviderId } from "../tools/image-providers";
|
|
36
38
|
import { type EditMode, normalizeEditMode } from "../utils/edit-mode";
|
|
39
|
+
import { isSearchProviderId, SEARCH_PROVIDER_ORDER } from "../web/search/types";
|
|
37
40
|
import { withFileLock } from "./file-lock";
|
|
38
41
|
import {
|
|
39
42
|
type BashInterceptorRule,
|
|
@@ -361,6 +364,7 @@ export class Settings {
|
|
|
361
364
|
if (options.configFiles) configFiles.push(...options.configFiles);
|
|
362
365
|
this.#configFiles = configFiles.map(file => path.resolve(this.#cwd, expandTilde(file)));
|
|
363
366
|
this.#persist = !options.inMemory && options.readOnly !== true;
|
|
367
|
+
liveSettingsInstances.add(new WeakRef(this));
|
|
364
368
|
|
|
365
369
|
if (options.overrides) {
|
|
366
370
|
for (const [key, value] of Object.entries(options.overrides)) {
|
|
@@ -534,6 +538,23 @@ export class Settings {
|
|
|
534
538
|
}
|
|
535
539
|
}
|
|
536
540
|
|
|
541
|
+
/** Set once this instance is discarded; background saves become no-ops. */
|
|
542
|
+
#savesCancelled = false;
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Drop pending debounced saves and refuse any further background writes.
|
|
546
|
+
* Used when an instance is being discarded (test teardown): an armed timer
|
|
547
|
+
* or a chained in-flight save on a dropped instance would otherwise fire
|
|
548
|
+
* later and race the successor's file locks.
|
|
549
|
+
*/
|
|
550
|
+
cancelPendingSaves(): void {
|
|
551
|
+
this.#savesCancelled = true;
|
|
552
|
+
clearTimeout(this.#saveTimer);
|
|
553
|
+
this.#saveTimer = undefined;
|
|
554
|
+
clearTimeout(this.#projectSaveTimer);
|
|
555
|
+
this.#projectSaveTimer = undefined;
|
|
556
|
+
}
|
|
557
|
+
|
|
537
558
|
/**
|
|
538
559
|
* Flush any pending saves to disk.
|
|
539
560
|
* Call before exit to ensure all changes are persisted.
|
|
@@ -1595,6 +1616,45 @@ export class Settings {
|
|
|
1595
1616
|
delete raw["mcp.discoveryMode"];
|
|
1596
1617
|
delete raw["mcp.discoveryDefaultServers"];
|
|
1597
1618
|
|
|
1619
|
+
// providers.webSearch / providers.image (single preferred provider) →
|
|
1620
|
+
// providers.webSearchOrder / providers.imageOrder (priority lists). A
|
|
1621
|
+
// concrete legacy choice becomes the head of the new list with every
|
|
1622
|
+
// remaining provider appended in its built-in order, so the old
|
|
1623
|
+
// preference stays #1 and the fallback chain is written out explicitly.
|
|
1624
|
+
// "auto" (or an unknown id) just drops the key — the default chain.
|
|
1625
|
+
const providerPrefsObj = raw.providers as Record<string, unknown> | undefined;
|
|
1626
|
+
const migrateProviderPreference = (
|
|
1627
|
+
legacyKey: string,
|
|
1628
|
+
orderKey: string,
|
|
1629
|
+
expand: (value: string) => string[] | undefined,
|
|
1630
|
+
): void => {
|
|
1631
|
+
const flatLegacyKey = `providers.${legacyKey}`;
|
|
1632
|
+
const legacy = providerPrefsObj?.[legacyKey] ?? raw[flatLegacyKey];
|
|
1633
|
+
if (legacy === undefined) return;
|
|
1634
|
+
const existingOrder = providerPrefsObj?.[orderKey] ?? raw[`providers.${orderKey}`];
|
|
1635
|
+
const orderAlreadySet = Array.isArray(existingOrder) && existingOrder.length > 0;
|
|
1636
|
+
if (!orderAlreadySet && typeof legacy === "string") {
|
|
1637
|
+
const expanded = expand(legacy);
|
|
1638
|
+
if (expanded) {
|
|
1639
|
+
const root = providerPrefsObj ?? {};
|
|
1640
|
+
root[orderKey] = expanded;
|
|
1641
|
+
raw.providers = root;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
if (providerPrefsObj) delete providerPrefsObj[legacyKey];
|
|
1645
|
+
delete raw[flatLegacyKey];
|
|
1646
|
+
};
|
|
1647
|
+
migrateProviderPreference("webSearch", "webSearchOrder", value =>
|
|
1648
|
+
value !== "auto" && isSearchProviderId(value)
|
|
1649
|
+
? [value, ...SEARCH_PROVIDER_ORDER.filter(id => id !== value)]
|
|
1650
|
+
: undefined,
|
|
1651
|
+
);
|
|
1652
|
+
migrateProviderPreference("image", "imageOrder", value =>
|
|
1653
|
+
value !== "auto" && isImageProviderId(value)
|
|
1654
|
+
? [value, ...AUTO_IMAGE_PROVIDER_ORDER.filter(id => id !== value)]
|
|
1655
|
+
: undefined,
|
|
1656
|
+
);
|
|
1657
|
+
|
|
1598
1658
|
return raw;
|
|
1599
1659
|
}
|
|
1600
1660
|
|
|
@@ -1646,7 +1706,7 @@ export class Settings {
|
|
|
1646
1706
|
}
|
|
1647
1707
|
|
|
1648
1708
|
async #saveNow(): Promise<void> {
|
|
1649
|
-
if (!this.#persist || !this.#configPath) return;
|
|
1709
|
+
if (this.#savesCancelled || !this.#persist || !this.#configPath) return;
|
|
1650
1710
|
if (this.#modified.size === 0 && this.#modifiedGlobalModelRoles.size === 0) return;
|
|
1651
1711
|
|
|
1652
1712
|
const configPath = this.#configPath;
|
|
@@ -1750,7 +1810,7 @@ export class Settings {
|
|
|
1750
1810
|
}
|
|
1751
1811
|
|
|
1752
1812
|
async #saveProjectNow(): Promise<void> {
|
|
1753
|
-
if (!this.#persist || this.#modifiedProjectModelRoles.size === 0) return;
|
|
1813
|
+
if (this.#savesCancelled || !this.#persist || this.#modifiedProjectModelRoles.size === 0) return;
|
|
1754
1814
|
|
|
1755
1815
|
const projectConfigPath = path.join(this.#cwd, ".omp", "config.yml");
|
|
1756
1816
|
const modifiedModelRoles = [...this.#modifiedProjectModelRoles];
|
|
@@ -1919,6 +1979,9 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
|
1919
1979
|
"providers.maxInFlightRequests": value => {
|
|
1920
1980
|
configureProviderMaxInFlightRequests(validateProviderMaxInFlightRequests(value));
|
|
1921
1981
|
},
|
|
1982
|
+
"secrets.enabled": value => {
|
|
1983
|
+
configureCredentialRedaction(value === true);
|
|
1984
|
+
},
|
|
1922
1985
|
"hindsight.bankId": () => hindsightScopeSignal.fire(),
|
|
1923
1986
|
"hindsight.bankIdPrefix": () => hindsightScopeSignal.fire(),
|
|
1924
1987
|
"hindsight.scoping": () => hindsightScopeSignal.fire(),
|
|
@@ -1978,6 +2041,13 @@ export const onHindsightScopeChanged = (cb: () => void) => hindsightScopeSignal.
|
|
|
1978
2041
|
// Global Singleton
|
|
1979
2042
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1980
2043
|
|
|
2044
|
+
/**
|
|
2045
|
+
* Weak registry of every constructed instance so `resetSettingsForTest` can
|
|
2046
|
+
* disarm stray background saves on isolated instances too. WeakRefs never
|
|
2047
|
+
* retain instances; the set is cleared on every test reset.
|
|
2048
|
+
*/
|
|
2049
|
+
const liveSettingsInstances = new Set<WeakRef<Settings>>();
|
|
2050
|
+
|
|
1981
2051
|
let globalInstance: Settings | null = null;
|
|
1982
2052
|
let globalInstancePromise: Promise<Settings> | null = null;
|
|
1983
2053
|
let boundSettingsInstance: Settings | null = null;
|
|
@@ -1997,10 +2067,19 @@ export function isSettingsInitialized(): boolean {
|
|
|
1997
2067
|
* @internal
|
|
1998
2068
|
*/
|
|
1999
2069
|
export function resetSettingsForTest(): void {
|
|
2070
|
+
// Disarm every constructed instance's debounced saves — including isolated
|
|
2071
|
+
// (non-singleton) instances: an armed timer or chained in-flight save on a
|
|
2072
|
+
// dropped instance fires mid-way through the NEXT test and races its file
|
|
2073
|
+
// locks/spies (cross-file pollution).
|
|
2074
|
+
for (const ref of liveSettingsInstances) {
|
|
2075
|
+
ref.deref()?.cancelPendingSaves();
|
|
2076
|
+
}
|
|
2077
|
+
liveSettingsInstances.clear();
|
|
2000
2078
|
globalInstance = null;
|
|
2001
2079
|
globalInstancePromise = null;
|
|
2002
2080
|
clearBoundSettingsMethods();
|
|
2003
2081
|
configureProviderMaxInFlightRequests(undefined);
|
|
2082
|
+
configureCredentialRedaction(false);
|
|
2004
2083
|
}
|
|
2005
2084
|
|
|
2006
2085
|
/**
|
package/src/dap/client.ts
CHANGED
|
@@ -260,7 +260,7 @@ export class DapClient {
|
|
|
260
260
|
// socket connect fails, we must not leak the detached adapter process.
|
|
261
261
|
try {
|
|
262
262
|
await waitForCondition(() => isUnixSocketReady(socketPath), timeoutMs, proc);
|
|
263
|
-
const { readable, writeSink, socket } = await connectSocket({ unix: socketPath });
|
|
263
|
+
const { readable, writeSink, socket } = await connectSocket({ unix: socketPath }, timeoutMs);
|
|
264
264
|
const client = new DapClient(adapter, cwd, proc, { readable, writeSink, socket });
|
|
265
265
|
proc.exited.then(() => client.#handleProcessExit());
|
|
266
266
|
void client.#startMessageReader();
|
|
@@ -924,10 +924,20 @@ function socketToSink(socket: Bun.Socket<undefined>): DapWriteSink {
|
|
|
924
924
|
};
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
-
/**
|
|
928
|
-
|
|
929
|
-
|
|
927
|
+
/**
|
|
928
|
+
* Connect to a unix domain socket and return DAP transport streams.
|
|
929
|
+
*
|
|
930
|
+
* Rejects (rather than hanging) when the connect fails — a stat-ready but dead
|
|
931
|
+
* socket returns ECONNREFUSED, a socket removed between the readiness stat and
|
|
932
|
+
* the connect returns ENOENT, a permission mismatch returns EACCES — and when
|
|
933
|
+
* neither `open` nor an error arrives within `timeoutMs` (e.g. a TOCTOU stall).
|
|
934
|
+
* `#spawnSocketUnix`'s catch then kills the detached adapter instead of leaking
|
|
935
|
+
* it. Exported so tests can drive the reject path deterministically.
|
|
936
|
+
*/
|
|
937
|
+
export async function connectSocket(options: { unix: string }, timeoutMs: number): Promise<SocketTransport> {
|
|
938
|
+
const { promise, resolve, reject } = Promise.withResolvers<SocketTransport>();
|
|
930
939
|
let streamController: ReadableStreamDefaultController<Uint8Array>;
|
|
940
|
+
let opened = false;
|
|
931
941
|
|
|
932
942
|
const readable = new ReadableStream<Uint8Array>({
|
|
933
943
|
start(controller) {
|
|
@@ -935,10 +945,21 @@ async function connectSocket(options: { unix: string }): Promise<SocketTransport
|
|
|
935
945
|
},
|
|
936
946
|
});
|
|
937
947
|
|
|
948
|
+
const timer = setTimeout(() => {
|
|
949
|
+
reject(new Error(`Timed out connecting to unix socket ${options.unix} after ${timeoutMs}ms`));
|
|
950
|
+
}, timeoutMs);
|
|
951
|
+
// A late socket callback after settle is a no-op; clearing the timer just
|
|
952
|
+
// stops it from keeping the event loop alive past the connect.
|
|
953
|
+
void promise.then(
|
|
954
|
+
() => clearTimeout(timer),
|
|
955
|
+
() => clearTimeout(timer),
|
|
956
|
+
);
|
|
957
|
+
|
|
938
958
|
Bun.connect({
|
|
939
959
|
unix: options.unix,
|
|
940
960
|
socket: {
|
|
941
961
|
open(socket) {
|
|
962
|
+
opened = true;
|
|
942
963
|
resolve({
|
|
943
964
|
readable,
|
|
944
965
|
writeSink: socketToSink(socket),
|
|
@@ -949,6 +970,9 @@ async function connectSocket(options: { unix: string }): Promise<SocketTransport
|
|
|
949
970
|
streamController.enqueue(new Uint8Array(data));
|
|
950
971
|
},
|
|
951
972
|
close() {
|
|
973
|
+
if (!opened) {
|
|
974
|
+
reject(new Error(`Unix socket ${options.unix} closed before opening`));
|
|
975
|
+
}
|
|
952
976
|
try {
|
|
953
977
|
streamController.close();
|
|
954
978
|
} catch {
|
|
@@ -956,6 +980,9 @@ async function connectSocket(options: { unix: string }): Promise<SocketTransport
|
|
|
956
980
|
}
|
|
957
981
|
},
|
|
958
982
|
error(_socket, err) {
|
|
983
|
+
if (!opened) {
|
|
984
|
+
reject(err);
|
|
985
|
+
}
|
|
959
986
|
try {
|
|
960
987
|
streamController.error(err);
|
|
961
988
|
} catch {
|
|
@@ -963,6 +990,12 @@ async function connectSocket(options: { unix: string }): Promise<SocketTransport
|
|
|
963
990
|
}
|
|
964
991
|
},
|
|
965
992
|
},
|
|
993
|
+
}).catch(err => {
|
|
994
|
+
// Bun.connect rejects the returned promise on synchronous connect
|
|
995
|
+
// failures (e.g. ENOENT) without always firing the `error` handler.
|
|
996
|
+
if (!opened) {
|
|
997
|
+
reject(err);
|
|
998
|
+
}
|
|
966
999
|
});
|
|
967
1000
|
|
|
968
1001
|
return promise;
|
package/src/edit/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import hashlineGrammar from "@oh-my-pi/hashline/grammar.lark" with { type: "text
|
|
|
3
3
|
import hashlineDescription from "@oh-my-pi/hashline/prompt.md" with { type: "text" };
|
|
4
4
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
6
|
-
import { prompt } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { isEnoent, isEnotdir, prompt } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { createLspWritethrough, flushLspWritethroughBatch, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
8
8
|
import { DeferredDiagnostics } from "../lsp/deferred-diagnostics";
|
|
9
9
|
import { getDiagnosticsLedger } from "../lsp/diagnostics-ledger";
|
|
@@ -12,7 +12,8 @@ import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
|
|
|
12
12
|
import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
|
|
13
13
|
import type { ToolSession } from "../tools";
|
|
14
14
|
import { truncateForPrompt } from "../tools/approval";
|
|
15
|
-
import { isInternalUrlPath } from "../tools/path-utils";
|
|
15
|
+
import { findUniqueWorkspaceSuffix, isInternalUrlPath } from "../tools/path-utils";
|
|
16
|
+
import { resolvePlanPath } from "../tools/plan-mode-guard";
|
|
16
17
|
import { type EditMode, normalizeEditMode, resolveEditMode } from "../utils/edit-mode";
|
|
17
18
|
import { executeHashlineSingle, hashlineEditParamsSchema } from "./hashline";
|
|
18
19
|
import { type ApplyPatchParams, applyPatchSchema, expandApplyPatchToEntries } from "./modes/apply-patch";
|
|
@@ -73,6 +74,24 @@ function resolveConfiguredEditMode(rawEditMode: string): EditMode | undefined {
|
|
|
73
74
|
return editMode;
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
async function resolveEditPath(
|
|
78
|
+
session: ToolSession,
|
|
79
|
+
authoredPath: string,
|
|
80
|
+
options: { mustExist: boolean; signal?: AbortSignal },
|
|
81
|
+
): Promise<string> {
|
|
82
|
+
if (!options.mustExist || isInternalUrlPath(authoredPath)) return authoredPath;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await Bun.file(resolvePlanPath(session, authoredPath)).stat();
|
|
86
|
+
return authoredPath;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (!isEnoent(error) && !isEnotdir(error)) throw error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const match = await findUniqueWorkspaceSuffix(authoredPath, session.cwd, options.signal);
|
|
92
|
+
return match?.displayPath ?? authoredPath;
|
|
93
|
+
}
|
|
94
|
+
|
|
76
95
|
function resolveAllowFuzzy(session: ToolSession, rawValue: string): boolean {
|
|
77
96
|
switch (rawValue) {
|
|
78
97
|
case "true":
|
|
@@ -517,7 +536,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
517
536
|
note: "All entries in one call apply to the top-level `path`; use separate calls for different files.",
|
|
518
537
|
},
|
|
519
538
|
] satisfies readonly ToolExample<PatchParams>[],
|
|
520
|
-
execute: (
|
|
539
|
+
execute: async (
|
|
521
540
|
tool: EditTool,
|
|
522
541
|
params: EditParams,
|
|
523
542
|
signal: AbortSignal | undefined,
|
|
@@ -525,11 +544,15 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
525
544
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
526
545
|
) => {
|
|
527
546
|
const { edits, path } = params as PatchParams;
|
|
547
|
+
const targetPath = await resolveEditPath(tool.session, path, {
|
|
548
|
+
mustExist: (edits[0]?.op ?? "update") !== "create",
|
|
549
|
+
signal,
|
|
550
|
+
});
|
|
528
551
|
const runs = (edits as PatchEditEntry[]).map(
|
|
529
552
|
entry => (br: LspBatchRequest | undefined) =>
|
|
530
553
|
executePatchSingle({
|
|
531
554
|
session: tool.session,
|
|
532
|
-
path,
|
|
555
|
+
path: targetPath,
|
|
533
556
|
params: entry,
|
|
534
557
|
signal,
|
|
535
558
|
batchRequest: br,
|
|
@@ -542,7 +565,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
542
565
|
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
543
566
|
}),
|
|
544
567
|
);
|
|
545
|
-
return executeSinglePathEntries(
|
|
568
|
+
return executeSinglePathEntries(targetPath, runs, batchRequest, onUpdate, tool.session.cwd, signal);
|
|
546
569
|
},
|
|
547
570
|
},
|
|
548
571
|
apply_patch: {
|
|
@@ -564,14 +587,26 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
564
587
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
565
588
|
) => {
|
|
566
589
|
const entries = expandApplyPatchToEntries(params as ApplyPatchParams);
|
|
590
|
+
// Resolve each authored path once per patch so paired hunks (e.g. delete
|
|
591
|
+
// then re-add of the same file) share the same workspace target.
|
|
592
|
+
const resolvedTargets = new Map<string, Promise<string>>();
|
|
593
|
+
const resolveOnce = (path: string, mustExist: boolean): Promise<string> => {
|
|
594
|
+
let pending = resolvedTargets.get(path);
|
|
595
|
+
if (!pending) {
|
|
596
|
+
pending = resolveEditPath(tool.session, path, { mustExist, signal });
|
|
597
|
+
resolvedTargets.set(path, pending);
|
|
598
|
+
}
|
|
599
|
+
return pending;
|
|
600
|
+
};
|
|
567
601
|
const perFile = entries.map(entry => {
|
|
568
602
|
const { path, ...patchParams } = entry;
|
|
569
603
|
return {
|
|
570
604
|
path,
|
|
571
|
-
run: (br: LspBatchRequest | undefined) =>
|
|
572
|
-
|
|
605
|
+
run: async (br: LspBatchRequest | undefined) => {
|
|
606
|
+
const targetPath = await resolveOnce(path, patchParams.op !== "create");
|
|
607
|
+
return executePatchSingle({
|
|
573
608
|
session: tool.session,
|
|
574
|
-
path,
|
|
609
|
+
path: targetPath,
|
|
575
610
|
params: patchParams,
|
|
576
611
|
signal,
|
|
577
612
|
batchRequest: br,
|
|
@@ -579,7 +614,8 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
579
614
|
fuzzyThreshold: tool.#fuzzyThreshold,
|
|
580
615
|
writethrough: tool.#writethrough,
|
|
581
616
|
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
582
|
-
})
|
|
617
|
+
});
|
|
618
|
+
},
|
|
583
619
|
};
|
|
584
620
|
});
|
|
585
621
|
return executeApplyPatchPerFile(perFile, batchRequest, tool.session.cwd, signal, onUpdate);
|
|
@@ -609,7 +645,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
609
645
|
replace: {
|
|
610
646
|
description: () => prompt.render(replaceDescription),
|
|
611
647
|
parameters: replaceEditSchema,
|
|
612
|
-
execute: (
|
|
648
|
+
execute: async (
|
|
613
649
|
tool: EditTool,
|
|
614
650
|
params: EditParams,
|
|
615
651
|
signal: AbortSignal | undefined,
|
|
@@ -617,11 +653,12 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
617
653
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
618
654
|
) => {
|
|
619
655
|
const { edits, path } = params as ReplaceParams;
|
|
656
|
+
const targetPath = await resolveEditPath(tool.session, path, { mustExist: true, signal });
|
|
620
657
|
const runs = (edits as ReplaceEditEntry[]).map(
|
|
621
658
|
entry => (br: LspBatchRequest | undefined) =>
|
|
622
659
|
executeReplaceSingle({
|
|
623
660
|
session: tool.session,
|
|
624
|
-
path,
|
|
661
|
+
path: targetPath,
|
|
625
662
|
params: entry,
|
|
626
663
|
signal,
|
|
627
664
|
batchRequest: br,
|
|
@@ -631,7 +668,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
631
668
|
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
632
669
|
}),
|
|
633
670
|
);
|
|
634
|
-
return executeSinglePathEntries(
|
|
671
|
+
return executeSinglePathEntries(targetPath, runs, batchRequest, onUpdate, tool.session.cwd, signal);
|
|
635
672
|
},
|
|
636
673
|
},
|
|
637
674
|
}[this.mode];
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type KernelDisplayOutput,
|
|
22
22
|
type KernelExecuteOptions,
|
|
23
23
|
type KernelExecuteResult,
|
|
24
|
+
type KernelShutdownResult,
|
|
24
25
|
PythonKernel,
|
|
25
26
|
} from "./kernel";
|
|
26
27
|
import { resolveExplicitPythonRuntime } from "./runtime";
|
|
@@ -136,11 +137,19 @@ export interface PythonResult {
|
|
|
136
137
|
// the same tuple; the kernel stays alive until the last owner detaches.
|
|
137
138
|
// ---------------------------------------------------------------------------
|
|
138
139
|
|
|
140
|
+
interface SessionKernelReplacement {
|
|
141
|
+
generation: number;
|
|
142
|
+
deadlineMs?: number;
|
|
143
|
+
promise: Promise<PythonKernel>;
|
|
144
|
+
}
|
|
145
|
+
|
|
139
146
|
interface PythonSession {
|
|
140
147
|
sessionKey: string;
|
|
141
148
|
sessionId: string;
|
|
142
149
|
cwd: string;
|
|
143
150
|
kernel: PythonKernel;
|
|
151
|
+
generation: number;
|
|
152
|
+
replacement?: SessionKernelReplacement;
|
|
144
153
|
ownerIds: Set<string>;
|
|
145
154
|
hasFallbackOwner: boolean;
|
|
146
155
|
}
|
|
@@ -254,6 +263,7 @@ async function acquireSession(
|
|
|
254
263
|
sessionId,
|
|
255
264
|
cwd,
|
|
256
265
|
kernel,
|
|
266
|
+
generation: 0,
|
|
257
267
|
ownerIds: new Set(),
|
|
258
268
|
hasFallbackOwner: false,
|
|
259
269
|
};
|
|
@@ -272,31 +282,99 @@ async function acquireSession(
|
|
|
272
282
|
|
|
273
283
|
async function replaceSessionKernel(
|
|
274
284
|
session: PythonSession,
|
|
285
|
+
kernel: PythonKernel,
|
|
286
|
+
generation: number,
|
|
275
287
|
cwd: string,
|
|
276
288
|
options: PythonExecutorOptions,
|
|
277
|
-
): Promise<
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
289
|
+
): Promise<PythonKernel> {
|
|
290
|
+
const inFlight = session.replacement;
|
|
291
|
+
if (inFlight?.generation === generation) {
|
|
292
|
+
if (
|
|
293
|
+
inFlight.deadlineMs !== undefined &&
|
|
294
|
+
(options.deadlineMs === undefined || options.deadlineMs > inFlight.deadlineMs)
|
|
295
|
+
) {
|
|
296
|
+
inFlight.deadlineMs = options.deadlineMs;
|
|
297
|
+
}
|
|
298
|
+
return await waitForPromiseWithCancellation(inFlight.promise, options, PythonExecutionCancelledError);
|
|
285
299
|
}
|
|
286
|
-
|
|
287
|
-
const next = await startKernel(cwd, options);
|
|
288
|
-
if (sessions.get(session.sessionKey) !== session) {
|
|
289
|
-
await next.shutdown().catch(() => undefined);
|
|
300
|
+
if (sessions.get(session.sessionKey) !== session || session.generation !== generation || session.kernel !== kernel) {
|
|
290
301
|
throw new PythonExecutionCancelledError(false);
|
|
291
302
|
}
|
|
292
|
-
|
|
303
|
+
|
|
304
|
+
const deferred = Promise.withResolvers<PythonKernel>();
|
|
305
|
+
const replacement: SessionKernelReplacement = {
|
|
306
|
+
generation,
|
|
307
|
+
deadlineMs: options.deadlineMs,
|
|
308
|
+
promise: deferred.promise,
|
|
309
|
+
};
|
|
310
|
+
session.replacement = replacement;
|
|
311
|
+
void (async () => {
|
|
312
|
+
try {
|
|
313
|
+
const remaining = getRemainingTimeoutMs(options.deadlineMs);
|
|
314
|
+
await kernel
|
|
315
|
+
.shutdown(remaining !== undefined ? { timeoutMs: Math.max(0, remaining) } : undefined)
|
|
316
|
+
.catch(() => undefined);
|
|
317
|
+
if (replacement.deadlineMs !== undefined && replacement.deadlineMs <= Date.now()) {
|
|
318
|
+
throw new PythonExecutionCancelledError(true);
|
|
319
|
+
}
|
|
320
|
+
if (
|
|
321
|
+
sessions.get(session.sessionKey) !== session ||
|
|
322
|
+
session.generation !== generation ||
|
|
323
|
+
session.kernel !== kernel
|
|
324
|
+
) {
|
|
325
|
+
throw new PythonExecutionCancelledError(false);
|
|
326
|
+
}
|
|
327
|
+
const next = await startKernel(cwd, {
|
|
328
|
+
...options,
|
|
329
|
+
signal: undefined,
|
|
330
|
+
deadlineMs: undefined,
|
|
331
|
+
});
|
|
332
|
+
if (
|
|
333
|
+
sessions.get(session.sessionKey) !== session ||
|
|
334
|
+
session.generation !== generation ||
|
|
335
|
+
session.kernel !== kernel
|
|
336
|
+
) {
|
|
337
|
+
await next.shutdown().catch(() => undefined);
|
|
338
|
+
throw new PythonExecutionCancelledError(false);
|
|
339
|
+
}
|
|
340
|
+
session.kernel = next;
|
|
341
|
+
session.generation += 1;
|
|
342
|
+
deferred.resolve(next);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
deferred.reject(err);
|
|
345
|
+
} finally {
|
|
346
|
+
if (session.replacement === replacement) session.replacement = undefined;
|
|
347
|
+
}
|
|
348
|
+
})();
|
|
349
|
+
return await waitForPromiseWithCancellation(deferred.promise, options, PythonExecutionCancelledError);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function shutdownInvalidatedSession(session: PythonSession): Promise<KernelShutdownResult> {
|
|
353
|
+
const replacement = session.replacement;
|
|
354
|
+
if (replacement) await replacement.promise.catch(() => undefined);
|
|
355
|
+
return await session.kernel.shutdown();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function acquireLiveSessionKernel(
|
|
359
|
+
session: PythonSession,
|
|
360
|
+
cwd: string,
|
|
361
|
+
options: PythonExecutorOptions,
|
|
362
|
+
): Promise<PythonKernel> {
|
|
363
|
+
while (sessions.get(session.sessionKey) === session) {
|
|
364
|
+
const kernel = session.kernel;
|
|
365
|
+
const generation = session.generation;
|
|
366
|
+
if (kernel.isAlive()) return kernel;
|
|
367
|
+
await replaceSessionKernel(session, kernel, generation, cwd, options);
|
|
368
|
+
}
|
|
369
|
+
throw new PythonExecutionCancelledError(false);
|
|
293
370
|
}
|
|
294
371
|
|
|
295
372
|
async function resetSession(sessionKey: string): Promise<void> {
|
|
296
373
|
const existing = sessions.get(sessionKey) ?? (await startingSessions.get(sessionKey)?.catch(() => undefined));
|
|
297
374
|
if (!existing) return;
|
|
375
|
+
existing.generation += 1;
|
|
298
376
|
sessions.delete(sessionKey);
|
|
299
|
-
await existing
|
|
377
|
+
await shutdownInvalidatedSession(existing).catch(() => undefined);
|
|
300
378
|
}
|
|
301
379
|
|
|
302
380
|
// ---------------------------------------------------------------------------
|
|
@@ -315,9 +393,10 @@ export async function disposeAllKernelSessions(): Promise<void> {
|
|
|
315
393
|
}
|
|
316
394
|
}
|
|
317
395
|
for (const [id, session] of all) {
|
|
396
|
+
session.generation += 1;
|
|
318
397
|
if (sessions.get(id) === session) sessions.delete(id);
|
|
319
398
|
}
|
|
320
|
-
const results = await Promise.allSettled(all.map(([, session]) => session
|
|
399
|
+
const results = await Promise.allSettled(all.map(([, session]) => shutdownInvalidatedSession(session)));
|
|
321
400
|
for (let i = 0; i < all.length; i += 1) {
|
|
322
401
|
const [id, session] = all[i];
|
|
323
402
|
const result = results[i];
|
|
@@ -344,9 +423,10 @@ export async function disposeKernelSessionsByOwner(ownerId: string): Promise<voi
|
|
|
344
423
|
session.ownerIds.delete(ownerId);
|
|
345
424
|
}
|
|
346
425
|
for (const session of toShutdown) {
|
|
426
|
+
session.generation += 1;
|
|
347
427
|
if (sessions.get(session.sessionKey) === session) sessions.delete(session.sessionKey);
|
|
348
428
|
}
|
|
349
|
-
const results = await Promise.allSettled(toShutdown.map(session => session
|
|
429
|
+
const results = await Promise.allSettled(toShutdown.map(session => shutdownInvalidatedSession(session)));
|
|
350
430
|
for (let i = 0; i < toShutdown.length; i += 1) {
|
|
351
431
|
const session = toShutdown[i];
|
|
352
432
|
const result = results[i];
|
|
@@ -458,31 +538,21 @@ async function executeOnSession(code: string, cwd: string, options: PythonExecut
|
|
|
458
538
|
isTimedOutCancellation(options.signal.reason, PythonExecutionCancelledError, options.signal),
|
|
459
539
|
);
|
|
460
540
|
}
|
|
461
|
-
|
|
541
|
+
const kernel = await acquireLiveSessionKernel(session, cwd, options);
|
|
542
|
+
if (sessions.get(session.sessionKey) !== session || session.kernel !== kernel) {
|
|
462
543
|
throw new PythonExecutionCancelledError(false);
|
|
463
544
|
}
|
|
464
|
-
if (!session.kernel.isAlive()) {
|
|
465
|
-
await replaceSessionKernel(session, cwd, options);
|
|
466
|
-
if (sessions.get(session.sessionKey) !== session) {
|
|
467
|
-
throw new PythonExecutionCancelledError(false);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
545
|
const runOptions = { ...options, cwd };
|
|
471
546
|
try {
|
|
472
|
-
return await executeWithKernel(
|
|
547
|
+
return await executeWithKernel(kernel, code, runOptions);
|
|
473
548
|
} catch (err) {
|
|
474
549
|
if (isCancellationError(err, PythonExecutionCancelledError) || options.signal?.aborted) throw err;
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
// Shared kernels are keyed by cwd, so a dead kernel can be recreated in place
|
|
480
|
-
// without risking cross-directory state bleed.
|
|
481
|
-
await replaceSessionKernel(session, cwd, options);
|
|
482
|
-
if (sessions.get(session.sessionKey) !== session) {
|
|
550
|
+
if (kernel.isAlive()) throw err;
|
|
551
|
+
const retryKernel = await acquireLiveSessionKernel(session, cwd, options);
|
|
552
|
+
if (sessions.get(session.sessionKey) !== session || session.kernel !== retryKernel) {
|
|
483
553
|
throw new PythonExecutionCancelledError(false);
|
|
484
554
|
}
|
|
485
|
-
return await executeWithKernel(
|
|
555
|
+
return await executeWithKernel(retryKernel, code, runOptions);
|
|
486
556
|
}
|
|
487
557
|
}
|
|
488
558
|
|