@oh-my-pi/pi-coding-agent 17.0.0 → 17.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/dist/cli.js +4006 -3966
- package/dist/types/advisor/advise-tool.d.ts +5 -2
- package/dist/types/advisor/config.d.ts +14 -6
- package/dist/types/advisor/runtime.d.ts +20 -11
- package/dist/types/autolearn/controller.d.ts +1 -0
- package/dist/types/cli-commands.d.ts +14 -1
- package/dist/types/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +32 -7
- package/dist/types/config/settings.d.ts +50 -0
- package/dist/types/cursor.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +5 -2
- package/dist/types/discovery/substitute-plugin-root.d.ts +20 -7
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +18 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/internal-urls/agent-protocol.d.ts +1 -0
- package/dist/types/lib/xai-http.d.ts +11 -0
- package/dist/types/mcp/transports/stdio.d.ts +13 -1
- package/dist/types/modes/acp/acp-agent.d.ts +3 -0
- package/dist/types/modes/acp/acp-mode.d.ts +6 -2
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/assistant-message.d.ts +2 -0
- package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +23 -1
- package/dist/types/modes/components/hook-editor.d.ts +16 -2
- package/dist/types/modes/components/login-dialog.test.d.ts +1 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +4 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -1
- package/dist/types/modes/components/tool-execution.test.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/noninteractive-dispose.test.d.ts +1 -0
- package/dist/types/modes/print-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +10 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -2
- package/dist/types/modes/warp-events.d.ts +24 -0
- package/dist/types/modes/warp-events.test.d.ts +1 -0
- package/dist/types/plan-mode/model-transition.d.ts +47 -0
- package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
- package/dist/types/registry/agent-lifecycle.d.ts +26 -1
- package/dist/types/sdk.d.ts +13 -2
- package/dist/types/session/agent-session.d.ts +43 -11
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +14 -0
- package/dist/types/session/streaming-output.d.ts +2 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/slash-commands/types.d.ts +4 -4
- package/dist/types/system-prompt.d.ts +1 -1
- package/dist/types/task/label.d.ts +1 -1
- package/dist/types/telemetry-export.d.ts +34 -9
- package/dist/types/tools/approval.d.ts +8 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/registry.d.ts +7 -3
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/essential-tools.d.ts +29 -0
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/manage-skill.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- package/dist/types/utils/title-generator.d.ts +2 -1
- package/dist/types/web/search/index.d.ts +2 -0
- package/dist/types/web/search/providers/base.d.ts +3 -0
- package/dist/types/web/search/providers/kimi.d.ts +4 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +21 -16
- package/src/advisor/__tests__/advisor.test.ts +1358 -86
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/advise-tool.ts +7 -3
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli/bench-cli.ts +4 -1
- package/src/cli/grep-cli.ts +2 -1
- package/src/cli-commands.ts +57 -23
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +34 -6
- package/src/config/model-resolver.ts +57 -12
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +43 -6
- package/src/config/settings.ts +405 -25
- package/src/cursor.ts +20 -3
- package/src/debug/report-bundle.ts +40 -4
- package/src/discovery/codex.ts +14 -4
- package/src/discovery/helpers.ts +28 -5
- package/src/discovery/substitute-plugin-root.ts +23 -7
- package/src/exec/bash-executor.ts +14 -5
- package/src/export/html/template.js +2 -0
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/types.ts +18 -0
- package/src/extensibility/extensions/wrapper.ts +2 -1
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/extensibility/shared-events.ts +6 -0
- package/src/extensibility/utils.ts +91 -25
- package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +73 -0
- package/src/internal-urls/agent-protocol.ts +73 -39
- package/src/irc/bus.ts +22 -3
- package/src/launch/broker.ts +3 -2
- package/src/launch/client.ts +2 -2
- package/src/launch/presence.ts +2 -2
- package/src/lib/xai-http.ts +28 -2
- package/src/lsp/client.ts +1 -1
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/transports/stdio.ts +103 -23
- package/src/modes/acp/acp-agent.ts +28 -9
- package/src/modes/acp/acp-event-mapper.ts +8 -0
- package/src/modes/acp/acp-mode.ts +18 -4
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/assistant-message.ts +35 -14
- package/src/modes/components/chat-transcript-builder.ts +2 -0
- package/src/modes/components/custom-editor-plugin-ctor.test.ts +36 -0
- package/src/modes/components/custom-editor.ts +62 -1
- package/src/modes/components/hook-editor.ts +35 -5
- package/src/modes/components/login-dialog.test.ts +56 -0
- package/src/modes/components/login-dialog.ts +7 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/plan-review-overlay.ts +51 -10
- package/src/modes/components/session-selector.ts +10 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/component.ts +2 -4
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/components/tool-execution.test.ts +101 -0
- package/src/modes/components/tool-execution.ts +117 -9
- package/src/modes/components/transcript-container.ts +30 -2
- package/src/modes/controllers/command-controller-shared.ts +1 -1
- package/src/modes/controllers/command-controller.ts +168 -48
- package/src/modes/controllers/event-controller.ts +6 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -22
- package/src/modes/controllers/input-controller.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +208 -46
- package/src/modes/interactive-mode.ts +205 -65
- package/src/modes/noninteractive-dispose.test.ts +60 -0
- package/src/modes/print-mode.ts +10 -5
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +31 -7
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +10 -0
- package/src/modes/utils/interactive-context-helpers.ts +3 -1
- package/src/modes/utils/transcript-render-helpers.ts +3 -2
- package/src/modes/warp-events.test.ts +794 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/plan-mode/model-transition.test.ts +60 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/agent-lifecycle.ts +133 -18
- package/src/sdk.ts +227 -44
- package/src/session/agent-session.ts +1364 -382
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-listing.ts +6 -5
- package/src/session/session-loader.ts +9 -0
- package/src/session/session-manager.ts +48 -0
- package/src/session/session-persistence.ts +11 -0
- package/src/session/streaming-output.ts +3 -1
- package/src/slash-commands/builtin-registry.ts +9 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/slash-commands/types.ts +4 -4
- package/src/system-prompt.ts +2 -2
- package/src/task/executor.ts +1 -1
- package/src/task/label.ts +2 -0
- package/src/telemetry-export.ts +453 -97
- package/src/tools/approval.ts +11 -0
- package/src/tools/bash.ts +71 -38
- package/src/tools/browser/registry.ts +9 -2
- package/src/tools/browser/tab-supervisor.ts +65 -10
- package/src/tools/browser.ts +4 -3
- package/src/tools/essential-tools.ts +45 -0
- package/src/tools/eval.ts +3 -0
- package/src/tools/gh.ts +169 -2
- package/src/tools/image-gen.ts +624 -513
- package/src/tools/index.ts +10 -6
- package/src/tools/manage-skill.ts +5 -3
- package/src/tools/output-meta.ts +7 -0
- package/src/tools/path-utils.ts +8 -5
- package/src/tools/read.ts +48 -3
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/utils/title-generator.ts +15 -4
- package/src/web/search/index.ts +21 -6
- package/src/web/search/providers/base.ts +3 -0
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/providers/xai.ts +40 -9
- package/src/web/search/types.ts +6 -1
|
@@ -12,7 +12,7 @@ import { loadCapability } from "../../discovery";
|
|
|
12
12
|
import * as PiCodingAgent from "../../index";
|
|
13
13
|
import type { CustomMessagePayload } from "../../session/messages";
|
|
14
14
|
import * as typebox from "../typebox";
|
|
15
|
-
import { resolvePath,
|
|
15
|
+
import { resolvePath, withHostGuard } from "../utils";
|
|
16
16
|
import { execCommand } from "./runner";
|
|
17
17
|
import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
|
|
18
18
|
|
|
@@ -149,7 +149,7 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
|
|
149
149
|
|
|
150
150
|
try {
|
|
151
151
|
// Import the module using native Bun import
|
|
152
|
-
const module = await
|
|
152
|
+
const module = await withHostGuard(() => import(resolvedPath));
|
|
153
153
|
const factory = module.default as HookFactory;
|
|
154
154
|
|
|
155
155
|
if (typeof factory !== "function") {
|
|
@@ -164,7 +164,7 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
|
|
164
164
|
);
|
|
165
165
|
|
|
166
166
|
// Call factory to register handlers
|
|
167
|
-
await
|
|
167
|
+
await withHostGuard(async () => factory(api));
|
|
168
168
|
|
|
169
169
|
return {
|
|
170
170
|
hook: {
|
|
@@ -44,9 +44,10 @@ import { ReadTool } from "../tools/read";
|
|
|
44
44
|
import { formatBytes } from "../tools/render-utils";
|
|
45
45
|
import { WriteTool } from "../tools/write";
|
|
46
46
|
import { EventBus } from "../utils/event-bus";
|
|
47
|
-
import { loadExtensionFromFactory, loadExtensions } from "./extensions";
|
|
47
|
+
import { discoverExtensionPaths, loadExtensionFromFactory, loadExtensions } from "./extensions";
|
|
48
48
|
import { ExtensionRuntime } from "./extensions/loader";
|
|
49
49
|
import type { ExtensionFactory, ToolDefinition } from "./extensions/types";
|
|
50
|
+
import { getEnabledPlugins, resolvePluginExtensionPaths, type ScopedInstalledPlugin } from "./plugins/loader";
|
|
50
51
|
import type { Skill } from "./skills";
|
|
51
52
|
import { loadSkillsFromDir } from "./skills";
|
|
52
53
|
import { Type } from "./typebox";
|
|
@@ -655,6 +656,100 @@ export const SettingsManager = {
|
|
|
655
656
|
},
|
|
656
657
|
} as const;
|
|
657
658
|
|
|
659
|
+
/** Scope used by the legacy package manager for discovered resources. */
|
|
660
|
+
export type SourceScope = "user" | "project" | "temporary";
|
|
661
|
+
|
|
662
|
+
/** Discovery metadata exposed alongside a legacy package resource path. */
|
|
663
|
+
export interface PathMetadata {
|
|
664
|
+
source: string;
|
|
665
|
+
scope: SourceScope;
|
|
666
|
+
origin: "package" | "top-level";
|
|
667
|
+
baseDir?: string;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** One extension, skill, prompt, or theme resolved by the legacy package manager. */
|
|
671
|
+
export interface ResolvedResource {
|
|
672
|
+
path: string;
|
|
673
|
+
enabled: boolean;
|
|
674
|
+
metadata: PathMetadata;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Resource groups returned by {@link DefaultPackageManager.resolve}. */
|
|
678
|
+
export interface ResolvedPaths {
|
|
679
|
+
extensions: ResolvedResource[];
|
|
680
|
+
skills: ResolvedResource[];
|
|
681
|
+
prompts: ResolvedResource[];
|
|
682
|
+
themes: ResolvedResource[];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Action a legacy caller requests when a configured package is unavailable. */
|
|
686
|
+
export type MissingSourceAction = "install" | "skip" | "error";
|
|
687
|
+
|
|
688
|
+
/** Construction inputs accepted by the legacy package manager. */
|
|
689
|
+
export interface DefaultPackageManagerOptions {
|
|
690
|
+
cwd: string;
|
|
691
|
+
agentDir: string;
|
|
692
|
+
settingsManager: Settings | Promise<Settings>;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Enumerates the extensions OMP would load through the historical package
|
|
697
|
+
* manager surface used by legacy extensions.
|
|
698
|
+
*/
|
|
699
|
+
export class DefaultPackageManager {
|
|
700
|
+
#cwd: string;
|
|
701
|
+
#agentDir: string;
|
|
702
|
+
#settingsManager: Settings | Promise<Settings>;
|
|
703
|
+
|
|
704
|
+
constructor(options: DefaultPackageManagerOptions) {
|
|
705
|
+
this.#cwd = options.cwd;
|
|
706
|
+
this.#agentDir = options.agentDir;
|
|
707
|
+
this.#settingsManager = options.settingsManager;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Resolve enabled extension paths with their OMP plugin provenance. */
|
|
711
|
+
async resolve(_onMissing?: (source: string) => Promise<MissingSourceAction>): Promise<ResolvedPaths> {
|
|
712
|
+
const settings = await this.#settingsManager;
|
|
713
|
+
const configuredPaths = settings.get("extensions") ?? [];
|
|
714
|
+
const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
|
|
715
|
+
const [extensionPaths, plugins] = await Promise.all([
|
|
716
|
+
discoverExtensionPaths(configuredPaths, this.#cwd, disabledExtensionIds),
|
|
717
|
+
getEnabledPlugins(this.#cwd),
|
|
718
|
+
]);
|
|
719
|
+
const pluginByExtensionPath = new Map<string, ScopedInstalledPlugin>();
|
|
720
|
+
for (const plugin of plugins) {
|
|
721
|
+
for (const extensionPath of resolvePluginExtensionPaths(plugin)) {
|
|
722
|
+
pluginByExtensionPath.set(path.resolve(extensionPath), plugin);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const extensions = extensionPaths.map(extensionPath => {
|
|
727
|
+
const resolvedPath = path.resolve(extensionPath);
|
|
728
|
+
const plugin = pluginByExtensionPath.get(resolvedPath);
|
|
729
|
+
const agentDirRelative = path.relative(path.resolve(this.#agentDir), resolvedPath);
|
|
730
|
+
const metadata: PathMetadata = plugin
|
|
731
|
+
? {
|
|
732
|
+
source: `npm:${plugin.name}`,
|
|
733
|
+
scope: plugin.scope,
|
|
734
|
+
origin: "package",
|
|
735
|
+
baseDir: plugin.path,
|
|
736
|
+
}
|
|
737
|
+
: {
|
|
738
|
+
source: "auto",
|
|
739
|
+
scope:
|
|
740
|
+
agentDirRelative === "" ||
|
|
741
|
+
(!agentDirRelative.startsWith("..") && !path.isAbsolute(agentDirRelative))
|
|
742
|
+
? "user"
|
|
743
|
+
: "project",
|
|
744
|
+
origin: "top-level",
|
|
745
|
+
};
|
|
746
|
+
return { path: resolvedPath, enabled: true, metadata };
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
return { extensions, skills: [], prompts: [], themes: [] };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
658
753
|
/**
|
|
659
754
|
* Resource-loader compatibility layer for legacy pi extensions.
|
|
660
755
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference path="./legacy-pi-virtual-modules.d.ts" />
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import { isBuiltin } from "node:module";
|
|
3
|
+
import { createRequire, isBuiltin } from "node:module";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import * as url from "node:url";
|
|
6
6
|
import { isCompiledBinary, stripWindowsExtendedLengthPathPrefix } from "@oh-my-pi/pi-utils";
|
|
@@ -1111,6 +1111,74 @@ const EXTENSION_GRAPH_SPECIFIER_REGEX = /((?:from\s+|import\s+|import\s*\(\s*)["
|
|
|
1111
1111
|
// reloads install supplemental hooks only for modules added to the graph since
|
|
1112
1112
|
// the previous load.
|
|
1113
1113
|
const extensionGraphHookModules = new Map<string, Set<string>>();
|
|
1114
|
+
const commonJsModuleSources = new Map<string, string>();
|
|
1115
|
+
const commonJsFallbackModulePaths = new Map<string, string>();
|
|
1116
|
+
const COMMONJS_REQUIRE_GLOBAL = "__ompLegacyPiRequireGraphModule";
|
|
1117
|
+
const commonJsModuleDefinitions = new Map<string, { source: string; filename: string; dirname: string }>();
|
|
1118
|
+
const commonJsModuleCache = new Map<
|
|
1119
|
+
string,
|
|
1120
|
+
{
|
|
1121
|
+
exports: unknown;
|
|
1122
|
+
filename: string;
|
|
1123
|
+
id: string;
|
|
1124
|
+
path: string;
|
|
1125
|
+
require: NodeJS.Require;
|
|
1126
|
+
loaded: boolean;
|
|
1127
|
+
}
|
|
1128
|
+
>();
|
|
1129
|
+
const commonJsTypeScriptTranspiler = new Bun.Transpiler({ loader: "ts" });
|
|
1130
|
+
|
|
1131
|
+
function evaluateGraphCommonJs(modulePath: string): unknown {
|
|
1132
|
+
const cached = commonJsModuleCache.get(modulePath);
|
|
1133
|
+
if (cached) {
|
|
1134
|
+
return cached.exports;
|
|
1135
|
+
}
|
|
1136
|
+
const definition = commonJsModuleDefinitions.get(modulePath);
|
|
1137
|
+
if (!definition) {
|
|
1138
|
+
throw new Error(`Missing graph-owned CommonJS definition: ${modulePath}`);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const nativeRequire = createRequire(definition.filename);
|
|
1142
|
+
const module = {
|
|
1143
|
+
exports: {},
|
|
1144
|
+
filename: definition.filename,
|
|
1145
|
+
id: definition.filename,
|
|
1146
|
+
path: definition.dirname,
|
|
1147
|
+
require: nativeRequire,
|
|
1148
|
+
loaded: false,
|
|
1149
|
+
};
|
|
1150
|
+
commonJsModuleCache.set(modulePath, module);
|
|
1151
|
+
const graphRequire: NodeJS.Require = Object.assign(
|
|
1152
|
+
(specifier: string) => {
|
|
1153
|
+
const resolved = nativeRequire.resolve(specifier);
|
|
1154
|
+
let graphPath = resolved;
|
|
1155
|
+
try {
|
|
1156
|
+
graphPath = fs.realpathSync(resolved);
|
|
1157
|
+
} catch {
|
|
1158
|
+
// Builtins and virtual modules have no filesystem realpath.
|
|
1159
|
+
}
|
|
1160
|
+
return commonJsModuleDefinitions.has(graphPath) ? evaluateGraphCommonJs(graphPath) : nativeRequire(specifier);
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
resolve: nativeRequire.resolve,
|
|
1164
|
+
cache: nativeRequire.cache,
|
|
1165
|
+
extensions: nativeRequire.extensions,
|
|
1166
|
+
main: nativeRequire.main,
|
|
1167
|
+
},
|
|
1168
|
+
);
|
|
1169
|
+
module.require = graphRequire;
|
|
1170
|
+
const execute = new Function("exports", "require", "module", "__filename", "__dirname", definition.source);
|
|
1171
|
+
try {
|
|
1172
|
+
execute.call(module.exports, module.exports, graphRequire, module, definition.filename, definition.dirname);
|
|
1173
|
+
module.loaded = true;
|
|
1174
|
+
return module.exports;
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
commonJsModuleCache.delete(modulePath);
|
|
1177
|
+
throw error;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
Reflect.set(globalThis, COMMONJS_REQUIRE_GLOBAL, evaluateGraphCommonJs);
|
|
1114
1182
|
|
|
1115
1183
|
let legacyPiLoadTag = 0;
|
|
1116
1184
|
|
|
@@ -1144,9 +1212,9 @@ async function realpathOrSelfUncached(p: string): Promise<string> {
|
|
|
1144
1212
|
* Extension-local bare dependency entries are also included so their relative
|
|
1145
1213
|
* children receive the reload mtime tag; bare imports inside those dependencies
|
|
1146
1214
|
* remain native Bun resolutions to avoid taking over full third-party graphs.
|
|
1147
|
-
* CommonJS modules reached through `require()` stay on Bun's native loader
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1215
|
+
* CommonJS modules reached through `require()` stay on Bun's native loader
|
|
1216
|
+
* unless they resolve native addons. CommonJS reached through ESM imports stays
|
|
1217
|
+
* graph-owned so the load hook can expose its exports through an ESM default.
|
|
1150
1218
|
*/
|
|
1151
1219
|
async function collectExtensionModules(entryRealPath: string): Promise<Map<string, string>> {
|
|
1152
1220
|
const modules = new Map<string, string>();
|
|
@@ -1253,21 +1321,106 @@ async function collectExtensionModules(entryRealPath: string): Promise<Map<strin
|
|
|
1253
1321
|
return modules;
|
|
1254
1322
|
}
|
|
1255
1323
|
|
|
1324
|
+
/**
|
|
1325
|
+
* Discovers CommonJS export names Bun normally exposes to ESM importers. The
|
|
1326
|
+
* bridge must declare them statically because its default export is synthetic.
|
|
1327
|
+
*/
|
|
1328
|
+
function collectCommonJsNamedExports(source: string): string[] {
|
|
1329
|
+
const names = new Set<string>();
|
|
1330
|
+
const assignmentPattern = /(?:^|[;\n])\s*(?:exports|module\.exports)\.([A-Za-z_$][\w$]*)\s*=/gm;
|
|
1331
|
+
for (const match of source.matchAll(assignmentPattern)) {
|
|
1332
|
+
const name = match[1];
|
|
1333
|
+
if (name && name !== "default") {
|
|
1334
|
+
names.add(name);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
const objectPattern = /module\.exports\s*=\s*\{([\s\S]*?)\}/g;
|
|
1338
|
+
for (const objectMatch of source.matchAll(objectPattern)) {
|
|
1339
|
+
const propertyPattern = /(?:^|,)\s*(?:([A-Za-z_$][\w$]*)\s*(?=[:,]|$)|["']([A-Za-z_$][\w$]*)["']\s*:)/g;
|
|
1340
|
+
for (const propertyMatch of objectMatch[1]?.matchAll(propertyPattern) ?? []) {
|
|
1341
|
+
const name = propertyMatch[1] ?? propertyMatch[2];
|
|
1342
|
+
if (name && name !== "default") {
|
|
1343
|
+
names.add(name);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return [...names];
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* The shared evaluator gives ESM imports and sibling `require()` calls the
|
|
1352
|
+
* same `module.exports` value and cycle-aware cache.
|
|
1353
|
+
*/
|
|
1354
|
+
function synthesizeCommonJsDefaultModule(modulePath: string, source: string, targetPath = modulePath): string {
|
|
1355
|
+
let commonJsSource = source;
|
|
1356
|
+
if (commonJsSource.startsWith("#!")) {
|
|
1357
|
+
const firstLineEnd = commonJsSource.indexOf("\n");
|
|
1358
|
+
commonJsSource = firstLineEnd === -1 ? "" : commonJsSource.slice(firstLineEnd + 1);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
const executableSource = targetPath.endsWith(".cts")
|
|
1362
|
+
? commonJsTypeScriptTranspiler.transformSync(commonJsSource)
|
|
1363
|
+
: commonJsSource;
|
|
1364
|
+
commonJsModuleDefinitions.set(modulePath, {
|
|
1365
|
+
source: executableSource,
|
|
1366
|
+
filename: targetPath,
|
|
1367
|
+
dirname: path.dirname(targetPath),
|
|
1368
|
+
});
|
|
1369
|
+
commonJsModuleCache.delete(modulePath);
|
|
1370
|
+
const exportsBinding = "__ompLegacyPiCommonJsExports";
|
|
1371
|
+
const namedExports = collectCommonJsNamedExports(executableSource)
|
|
1372
|
+
.map(
|
|
1373
|
+
(name, index) =>
|
|
1374
|
+
`const __ompLegacyPiCommonJsExport${index} = ${exportsBinding}[${JSON.stringify(name)}]; export { __ompLegacyPiCommonJsExport${index} as ${name} };`,
|
|
1375
|
+
)
|
|
1376
|
+
.join("\n");
|
|
1377
|
+
return `const ${exportsBinding} = globalThis[${JSON.stringify(COMMONJS_REQUIRE_GLOBAL)}](${JSON.stringify(modulePath)});\nexport default ${exportsBinding};\n${namedExports}\n`;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Linkedom's canvas bridge uses its bundled fallback because OMP does not ship
|
|
1382
|
+
* native canvas.
|
|
1383
|
+
*/
|
|
1384
|
+
async function prepareCommonJsDefaultModule(modulePath: string, source: string): Promise<string> {
|
|
1385
|
+
const packageRoot = await findPackageRoot(modulePath);
|
|
1386
|
+
if (!packageRoot) {
|
|
1387
|
+
return synthesizeCommonJsDefaultModule(modulePath, source);
|
|
1388
|
+
}
|
|
1389
|
+
const manifest = await readPackageManifest(packageRoot);
|
|
1390
|
+
const packageRelativePath = path.relative(packageRoot, modulePath).split(path.sep).join("/");
|
|
1391
|
+
if (manifest?.name !== "linkedom" || packageRelativePath !== "commonjs/canvas.cjs") {
|
|
1392
|
+
return synthesizeCommonJsDefaultModule(modulePath, source);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
const targetPath = path.join(packageRoot, "commonjs", "canvas-shim.cjs");
|
|
1396
|
+
commonJsFallbackModulePaths.set(modulePath, targetPath);
|
|
1397
|
+
return synthesizeCommonJsDefaultModule(modulePath, await Bun.file(targetPath).text(), targetPath);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1256
1400
|
/**
|
|
1257
1401
|
* Install exact-path load hooks for the current extension graph. ESM/TS source
|
|
1258
|
-
* retains the async rewrite path.
|
|
1259
|
-
*
|
|
1260
|
-
* `
|
|
1402
|
+
* retains the async rewrite path. CommonJS wrappers and native-addon loaders
|
|
1403
|
+
* stay synchronous because Bun rejects `require()` targets backed by async
|
|
1404
|
+
* `onLoad` callbacks.
|
|
1261
1405
|
*/
|
|
1262
|
-
function installExtensionGraphHook(
|
|
1406
|
+
async function installExtensionGraphHook(
|
|
1263
1407
|
entryRealPath: string,
|
|
1264
1408
|
modules: Map<string, string>,
|
|
1265
|
-
|
|
1409
|
+
commonJsPaths: Set<string>,
|
|
1410
|
+
): Promise<{ asyncModules: Map<string, string>; syncSourceModules: Map<string, string> }> {
|
|
1266
1411
|
const asyncModules = new Map<string, string>();
|
|
1267
|
-
const
|
|
1412
|
+
const syncSourceModules = new Map<string, string>();
|
|
1268
1413
|
for (const [modulePath, source] of modules) {
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1414
|
+
const extension = path.extname(modulePath);
|
|
1415
|
+
if (extension === ".cjs" || extension === ".cts") {
|
|
1416
|
+
if (!commonJsPaths.has(modulePath)) {
|
|
1417
|
+
throw new Error(`Missing CommonJS compatibility source: ${modulePath}`);
|
|
1418
|
+
}
|
|
1419
|
+
} else if (nativeAddonLoaderModulePaths.has(modulePath)) {
|
|
1420
|
+
syncSourceModules.set(modulePath, source);
|
|
1421
|
+
} else {
|
|
1422
|
+
asyncModules.set(modulePath, source);
|
|
1423
|
+
}
|
|
1271
1424
|
}
|
|
1272
1425
|
|
|
1273
1426
|
if (asyncModules.size > 0) {
|
|
@@ -1299,17 +1452,42 @@ function installExtensionGraphHook(
|
|
|
1299
1452
|
});
|
|
1300
1453
|
}
|
|
1301
1454
|
|
|
1302
|
-
if (
|
|
1303
|
-
const alternation = [...
|
|
1455
|
+
if (commonJsPaths.size > 0) {
|
|
1456
|
+
const alternation = [...commonJsPaths].map(escapeRegExp).join("|");
|
|
1457
|
+
const filter = new RegExp(`^(?:${alternation})(?:\\?mtime=\\d+)?$`);
|
|
1458
|
+
const hookId = Bun.hash(`${entryRealPath}\0commonjs\0${[...commonJsPaths].join("\0")}`).toString(36);
|
|
1459
|
+
Bun.plugin({
|
|
1460
|
+
name: `omp:legacy-pi-ext:${hookId}`,
|
|
1461
|
+
setup(build) {
|
|
1462
|
+
build.onLoad({ filter, namespace: "file" }, args => {
|
|
1463
|
+
const queryIndex = args.path.indexOf("?mtime=");
|
|
1464
|
+
const sourcePath = queryIndex >= 0 ? args.path.slice(0, queryIndex) : args.path;
|
|
1465
|
+
const source =
|
|
1466
|
+
commonJsModuleSources.get(sourcePath) ??
|
|
1467
|
+
synthesizeCommonJsDefaultModule(
|
|
1468
|
+
sourcePath,
|
|
1469
|
+
fs.readFileSync(commonJsFallbackModulePaths.get(sourcePath) ?? sourcePath, "utf8"),
|
|
1470
|
+
commonJsFallbackModulePaths.get(sourcePath) ?? sourcePath,
|
|
1471
|
+
);
|
|
1472
|
+
return { contents: source, loader: getLoader(sourcePath) };
|
|
1473
|
+
});
|
|
1474
|
+
},
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
if (syncSourceModules.size > 0) {
|
|
1479
|
+
const alternation = [...syncSourceModules.keys()].map(escapeRegExp).join("|");
|
|
1304
1480
|
const filter = new RegExp(`^(?:${alternation})(?:\\?mtime=\\d+)?$`);
|
|
1305
|
-
const hookId = Bun.hash(`${entryRealPath}\0sync-
|
|
1481
|
+
const hookId = Bun.hash(`${entryRealPath}\0sync-source\0${[...syncSourceModules.keys()].join("\0")}`).toString(
|
|
1482
|
+
36,
|
|
1483
|
+
);
|
|
1306
1484
|
Bun.plugin({
|
|
1307
1485
|
name: `omp:legacy-pi-ext:${hookId}`,
|
|
1308
1486
|
setup(build) {
|
|
1309
1487
|
build.onLoad({ filter, namespace: "file" }, args => {
|
|
1310
1488
|
const queryIndex = args.path.indexOf("?mtime=");
|
|
1311
1489
|
const sourcePath = queryIndex >= 0 ? args.path.slice(0, queryIndex) : args.path;
|
|
1312
|
-
const source =
|
|
1490
|
+
const source = syncSourceModules.get(sourcePath);
|
|
1313
1491
|
if (source === undefined) {
|
|
1314
1492
|
throw new Error(`Missing pre-rewritten CommonJS extension source: ${sourcePath}`);
|
|
1315
1493
|
}
|
|
@@ -1318,7 +1496,7 @@ function installExtensionGraphHook(
|
|
|
1318
1496
|
},
|
|
1319
1497
|
});
|
|
1320
1498
|
}
|
|
1321
|
-
return { asyncModules,
|
|
1499
|
+
return { asyncModules, syncSourceModules };
|
|
1322
1500
|
}
|
|
1323
1501
|
|
|
1324
1502
|
/**
|
|
@@ -1331,6 +1509,14 @@ function installExtensionGraphHook(
|
|
|
1331
1509
|
*/
|
|
1332
1510
|
async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(): void } | undefined> {
|
|
1333
1511
|
const currentModules = await collectExtensionModules(entryRealPath);
|
|
1512
|
+
const commonJsPaths = new Set<string>();
|
|
1513
|
+
for (const [modulePath, source] of currentModules) {
|
|
1514
|
+
const extension = path.extname(modulePath);
|
|
1515
|
+
if (extension === ".cjs" || extension === ".cts") {
|
|
1516
|
+
commonJsModuleSources.set(modulePath, await prepareCommonJsDefaultModule(modulePath, source));
|
|
1517
|
+
commonJsPaths.add(modulePath);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1334
1520
|
let hookedModules = extensionGraphHookModules.get(entryRealPath);
|
|
1335
1521
|
if (!hookedModules) {
|
|
1336
1522
|
hookedModules = new Set<string>();
|
|
@@ -1338,23 +1524,40 @@ async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(
|
|
|
1338
1524
|
}
|
|
1339
1525
|
|
|
1340
1526
|
const pendingModules = new Map<string, string>();
|
|
1527
|
+
const pendingCommonJsPaths = new Set<string>();
|
|
1341
1528
|
for (const [modulePath, source] of currentModules) {
|
|
1342
1529
|
if (!hookedModules.has(modulePath)) {
|
|
1343
1530
|
pendingModules.set(modulePath, source);
|
|
1531
|
+
if (commonJsPaths.has(modulePath)) {
|
|
1532
|
+
pendingCommonJsPaths.add(modulePath);
|
|
1533
|
+
}
|
|
1344
1534
|
}
|
|
1345
1535
|
}
|
|
1346
|
-
if (pendingModules.size === 0) {
|
|
1536
|
+
if (pendingModules.size === 0 && commonJsPaths.size === 0) {
|
|
1347
1537
|
return undefined;
|
|
1348
1538
|
}
|
|
1349
1539
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1540
|
+
let asyncModules = new Map<string, string>();
|
|
1541
|
+
let syncSourceModules = new Map<string, string>();
|
|
1542
|
+
if (pendingModules.size > 0) {
|
|
1543
|
+
({ asyncModules, syncSourceModules } = await installExtensionGraphHook(
|
|
1544
|
+
entryRealPath,
|
|
1545
|
+
pendingModules,
|
|
1546
|
+
pendingCommonJsPaths,
|
|
1547
|
+
));
|
|
1548
|
+
for (const modulePath of pendingModules.keys()) {
|
|
1549
|
+
hookedModules.add(modulePath);
|
|
1550
|
+
}
|
|
1353
1551
|
}
|
|
1354
1552
|
return {
|
|
1355
1553
|
clear() {
|
|
1356
1554
|
asyncModules.clear();
|
|
1357
|
-
|
|
1555
|
+
syncSourceModules.clear();
|
|
1556
|
+
for (const modulePath of commonJsPaths) {
|
|
1557
|
+
commonJsModuleSources.delete(modulePath);
|
|
1558
|
+
commonJsModuleDefinitions.delete(modulePath);
|
|
1559
|
+
commonJsModuleCache.delete(modulePath);
|
|
1560
|
+
}
|
|
1358
1561
|
},
|
|
1359
1562
|
};
|
|
1360
1563
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isEnoent,
|
|
12
12
|
logger,
|
|
13
13
|
} from "@oh-my-pi/pi-utils";
|
|
14
|
-
import {
|
|
14
|
+
import { withHostGuard } from "../utils";
|
|
15
15
|
import { refreshBunGitCache } from "./bun-git-cache";
|
|
16
16
|
import { type GitSource, parseGitUrl } from "./git-url";
|
|
17
17
|
import { installLegacyPiSpecifierShim, loadLegacyPiModule } from "./legacy-pi-compat";
|
|
@@ -375,7 +375,7 @@ export class PluginManager {
|
|
|
375
375
|
installLegacyPiSpecifierShim();
|
|
376
376
|
for (const extensionPath of loadable) {
|
|
377
377
|
try {
|
|
378
|
-
const module = await
|
|
378
|
+
const module = await withHostGuard(() => loadLegacyPiModule(extensionPath));
|
|
379
379
|
if (!hasExtensionFactoryExport(module)) {
|
|
380
380
|
errors.push(`${extensionPath}: extension does not export a valid factory function`);
|
|
381
381
|
}
|
|
@@ -191,6 +191,12 @@ export interface AgentStartEvent {
|
|
|
191
191
|
export interface AgentEndEvent {
|
|
192
192
|
type: "agent_end";
|
|
193
193
|
messages: AgentMessage[];
|
|
194
|
+
/**
|
|
195
|
+
* When true, the session has already scheduled an automatic continuation
|
|
196
|
+
* (auto-retry, empty/unexpected-stop retry, etc.). Subscribers must not
|
|
197
|
+
* treat this as a user-visible terminal settle.
|
|
198
|
+
*/
|
|
199
|
+
willContinue?: boolean;
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
/** Fired at the start of each turn */
|
|
@@ -44,7 +44,7 @@ export function createNoOpUIContext(): HookUIContext {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Raised by {@link
|
|
47
|
+
* Raised by {@link withHostGuard} when a guarded callback synchronously
|
|
48
48
|
* attempts to terminate the host process. Callers catch this like any other
|
|
49
49
|
* load-time failure so the extension/hook is skipped with a logged error
|
|
50
50
|
* instead of taking the CLI down with it.
|
|
@@ -66,22 +66,46 @@ export class ExtensionExitError extends Error {
|
|
|
66
66
|
|
|
67
67
|
type ExitAliasName = "process.exit" | "process.reallyExit";
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
/**
|
|
70
|
+
* stdin events a loaded module must not be allowed to leave hijacked. A
|
|
71
|
+
* top-level `new StdioServerTransport()` (or a bare `process.stdin.resume()`)
|
|
72
|
+
* inside a `~/.claude/tools` MCP server attaches a `data` consumer and puts the
|
|
73
|
+
* shared stdin into flowing mode; Bun delivers one `data` event to that
|
|
74
|
+
* consumer and the TUI's own listener (attached later in `terminal.start()`)
|
|
75
|
+
* then never re-arms — every keypress after the first is swallowed (#5618).
|
|
76
|
+
*/
|
|
77
|
+
const HOST_GUARD_STDIN_EVENTS = ["data", "readable", "end", "close", "error"] as const;
|
|
78
|
+
type StdinGuardEvent = (typeof HOST_GUARD_STDIN_EVENTS)[number];
|
|
79
|
+
type StdinGuardListener = (...args: unknown[]) => void;
|
|
80
|
+
|
|
81
|
+
let hostGuardDepth = 0;
|
|
82
|
+
let hostGuardOriginalProcessExit: typeof process.exit | null = null;
|
|
83
|
+
let hostGuardOriginalReallyExit: typeof process.reallyExit | null = null;
|
|
84
|
+
let hostGuardStdinListeners: Record<StdinGuardEvent, StdinGuardListener[]> | null = null;
|
|
85
|
+
let hostGuardStdinWasPaused = false;
|
|
86
|
+
let hostGuardStdinWasRaw = false;
|
|
72
87
|
|
|
73
88
|
/**
|
|
74
|
-
* Run `fn` with
|
|
75
|
-
*
|
|
89
|
+
* Run `fn` with host-owned process state fenced off from third-party module
|
|
90
|
+
* evaluation, restored in `finally`. Guards the dynamic-import and
|
|
91
|
+
* factory-invocation sites that load extension / hook / tool / plugin modules
|
|
92
|
+
* from user directories (including Claude Code's `~/.claude/tools`, which OMP
|
|
93
|
+
* slurps wholesale). Two hazards are neutralized:
|
|
76
94
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
95
|
+
* - **Hard exit.** `process.exit(0)` / `process.reallyExit(0)` in a stranger's
|
|
96
|
+
* script (e.g. a CLI-shaped module with `main()` at the bottom) would kill
|
|
97
|
+
* OMP during startup with no error surface, since `try/catch` cannot
|
|
98
|
+
* intercept a synchronous exit. Both are patched to throw
|
|
99
|
+
* {@link ExtensionExitError} instead.
|
|
100
|
+
* - **stdin hijack.** A module that attaches a stdin consumer at evaluation
|
|
101
|
+
* time (an MCP `StdioServerTransport`, or a bare `resume()`) steals Bun's
|
|
102
|
+
* single stdin reader, so the TUI goes permanently deaf after one keypress
|
|
103
|
+
* (#5618). Any `data`/`readable`/`end`/`close`/`error` listener the module
|
|
104
|
+
* adds is removed, and the stream's paused and raw-mode state is restored to
|
|
105
|
+
* the pre-load snapshot.
|
|
82
106
|
*
|
|
83
107
|
* Nested and concurrent guard windows are safe: only the outermost guard
|
|
84
|
-
*
|
|
108
|
+
* snapshots and restores host state.
|
|
85
109
|
*/
|
|
86
110
|
function guardedExit(alias: ExitAliasName): (code?: number | string) => never {
|
|
87
111
|
return (code?: number | string): never => {
|
|
@@ -89,29 +113,71 @@ function guardedExit(alias: ExitAliasName): (code?: number | string) => never {
|
|
|
89
113
|
};
|
|
90
114
|
}
|
|
91
115
|
|
|
92
|
-
export async function
|
|
93
|
-
if (
|
|
94
|
-
|
|
116
|
+
export async function withHostGuard<T>(fn: () => Promise<T>): Promise<T> {
|
|
117
|
+
if (hostGuardDepth === 0) {
|
|
118
|
+
hostGuardOriginalProcessExit = process.exit;
|
|
95
119
|
process.exit = guardedExit("process.exit") as typeof process.exit;
|
|
96
120
|
|
|
97
121
|
if (typeof process.reallyExit === "function") {
|
|
98
|
-
|
|
122
|
+
hostGuardOriginalReallyExit = process.reallyExit;
|
|
99
123
|
process.reallyExit = guardedExit("process.reallyExit") as typeof process.reallyExit;
|
|
100
124
|
}
|
|
125
|
+
|
|
126
|
+
const stdin = process.stdin;
|
|
127
|
+
hostGuardStdinWasPaused = stdin.isPaused();
|
|
128
|
+
hostGuardStdinWasRaw = stdin.isRaw ?? false;
|
|
129
|
+
const snapshot = {} as Record<StdinGuardEvent, StdinGuardListener[]>;
|
|
130
|
+
for (const event of HOST_GUARD_STDIN_EVENTS) {
|
|
131
|
+
snapshot[event] = stdin.rawListeners(event) as StdinGuardListener[];
|
|
132
|
+
}
|
|
133
|
+
hostGuardStdinListeners = snapshot;
|
|
101
134
|
}
|
|
102
|
-
|
|
135
|
+
hostGuardDepth++;
|
|
103
136
|
try {
|
|
104
137
|
return await fn();
|
|
105
138
|
} finally {
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
if (
|
|
109
|
-
process.exit =
|
|
110
|
-
|
|
139
|
+
hostGuardDepth--;
|
|
140
|
+
if (hostGuardDepth === 0) {
|
|
141
|
+
if (hostGuardOriginalProcessExit) {
|
|
142
|
+
process.exit = hostGuardOriginalProcessExit;
|
|
143
|
+
hostGuardOriginalProcessExit = null;
|
|
144
|
+
}
|
|
145
|
+
if (hostGuardOriginalReallyExit) {
|
|
146
|
+
process.reallyExit = hostGuardOriginalReallyExit;
|
|
147
|
+
hostGuardOriginalReallyExit = null;
|
|
111
148
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
149
|
+
if (hostGuardStdinListeners) {
|
|
150
|
+
const stdin = process.stdin;
|
|
151
|
+
for (const event of HOST_GUARD_STDIN_EVENTS) {
|
|
152
|
+
const before = hostGuardStdinListeners[event];
|
|
153
|
+
// Reconcile the stream back to the pre-load snapshot: drop any
|
|
154
|
+
// listener the module added, and reinstate any snapshot listener
|
|
155
|
+
// it removed (e.g. a factory calling `removeAllListeners("data")`
|
|
156
|
+
// would otherwise permanently strip ProcessTerminal's input
|
|
157
|
+
// handler, leaving the parent TUI deaf). removeAllListeners then
|
|
158
|
+
// re-adding in snapshot order restores both membership and order.
|
|
159
|
+
const current = stdin.rawListeners(event) as StdinGuardListener[];
|
|
160
|
+
const differs =
|
|
161
|
+
current.length !== before.length || current.some((listener, index) => listener !== before[index]);
|
|
162
|
+
if (!differs) continue;
|
|
163
|
+
stdin.removeAllListeners(event);
|
|
164
|
+
for (const listener of before) {
|
|
165
|
+
stdin.on(event, listener);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (
|
|
169
|
+
stdin.isTTY &&
|
|
170
|
+
typeof stdin.setRawMode === "function" &&
|
|
171
|
+
(stdin.isRaw ?? false) !== hostGuardStdinWasRaw
|
|
172
|
+
) {
|
|
173
|
+
stdin.setRawMode(hostGuardStdinWasRaw);
|
|
174
|
+
}
|
|
175
|
+
if (hostGuardStdinWasPaused && !stdin.isPaused()) {
|
|
176
|
+
stdin.pause();
|
|
177
|
+
} else if (!hostGuardStdinWasPaused && stdin.isPaused()) {
|
|
178
|
+
stdin.resume();
|
|
179
|
+
}
|
|
180
|
+
hostGuardStdinListeners = null;
|
|
115
181
|
}
|
|
116
182
|
}
|
|
117
183
|
}
|