@oh-my-pi/pi-coding-agent 17.0.1 → 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 +92 -20
- package/dist/cli.js +3485 -3448
- 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/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- 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/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/mcp/transports/stdio.d.ts +13 -1
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/hook-editor.d.ts +7 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +2 -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/print-mode.d.ts +1 -1
- 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 +34 -10
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +14 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- 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/essential-tools.d.ts +29 -0
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +1 -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/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 +1304 -42
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +25 -6
- package/src/config/model-resolver.ts +14 -7
- package/src/config/settings-schema.ts +42 -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/helpers.ts +28 -5
- package/src/exec/bash-executor.ts +14 -5
- 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/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/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/components/advisor-config.ts +65 -3
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/hook-editor.ts +18 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/session-selector.ts +4 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/controllers/command-controller.ts +167 -47
- package/src/modes/controllers/event-controller.ts +5 -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 +191 -31
- package/src/modes/interactive-mode.ts +139 -54
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +19 -4
- 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/registry/agent-lifecycle.ts +133 -18
- package/src/sdk.ts +221 -42
- package/src/session/agent-session.ts +1285 -348
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-manager.ts +48 -0
- package/src/slash-commands/builtin-registry.ts +7 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- 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/essential-tools.ts +45 -0
- package/src/tools/gh.ts +169 -2
- package/src/tools/image-gen.ts +69 -7
- package/src/tools/index.ts +7 -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/providers/kimi.ts +18 -12
- package/src/web/search/types.ts +6 -1
|
@@ -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
|
}
|
package/src/irc/bus.ts
CHANGED
|
@@ -127,12 +127,31 @@ export class IrcBus {
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// A `parked` recipient always needs the lifecycle to revive it — this is
|
|
131
|
+
// read from *this* bus's registry, so it holds for any registry. The
|
|
132
|
+
// mid-park / adopted checks below query the lifecycle's own state, which
|
|
133
|
+
// only describes the registry it manages: consult them only when the
|
|
134
|
+
// lifecycle owns this bus's registry, otherwise a custom-registry bus
|
|
135
|
+
// (fallen back to the global manager) would gate a live recipient on
|
|
136
|
+
// unrelated global park state. Main/non-adopted live peers skip the gate,
|
|
137
|
+
// and pending waiters still win without a session.
|
|
138
|
+
const lifecycle = this.#lifecycle();
|
|
139
|
+
const lifecycleOwnsRegistry = lifecycle.manages(this.#registry);
|
|
140
|
+
const needsLifecycleGate =
|
|
141
|
+
ref.status === "parked" ||
|
|
142
|
+
(lifecycleOwnsRegistry && (lifecycle.isParking(message.to) || lifecycle.has(message.to)));
|
|
143
|
+
|
|
144
|
+
const priorSession = ref.session;
|
|
130
145
|
let revived = false;
|
|
131
|
-
if (
|
|
146
|
+
if (needsLifecycleGate) {
|
|
132
147
|
try {
|
|
133
|
-
await
|
|
134
|
-
|
|
148
|
+
const liveSession = await lifecycle.ensureLive(message.to);
|
|
149
|
+
// Revival = we did not keep the same live instance (parked start, or
|
|
150
|
+
// park completed and a fresh session was rebuilt).
|
|
151
|
+
revived = !priorSession || liveSession !== priorSession;
|
|
135
152
|
} catch (error) {
|
|
153
|
+
// Not revivable / released / revive failed. Do not buffer: a permanent
|
|
154
|
+
// failure must not inflate unread counts or pretend delivery is pending.
|
|
136
155
|
return {
|
|
137
156
|
to: message.to,
|
|
138
157
|
outcome: "failed",
|
package/src/launch/broker.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as net from "node:net";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { Process, type PtyRunResult, PtySession } from "@oh-my-pi/pi-natives";
|
|
6
|
-
import { isEexist, isEnoent, logger, postmortem, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { isEexist, isEnoent, logger, postmortem, procmgr, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { truncateHead, truncateHeadBytes, truncateTail, truncateTailBytes } from "../session/streaming-output";
|
|
8
8
|
import { workerEnvFromParent } from "../subprocess/worker-client";
|
|
9
9
|
import { daemonBrokerEndpoint } from "./paths";
|
|
@@ -555,7 +555,8 @@ class DaemonBroker {
|
|
|
555
555
|
`printf '%s' "$$" > ${quoteShellArg(pidPath)}`,
|
|
556
556
|
`exec ${argv.map(quoteShellArg).join(" ")}`,
|
|
557
557
|
].join("; ");
|
|
558
|
-
|
|
558
|
+
const shell = procmgr.getShellConfig().shell;
|
|
559
|
+
run = session.start({ command, shell, ...options }, onChunk);
|
|
559
560
|
}
|
|
560
561
|
void run
|
|
561
562
|
.then(result => this.#onPtyExit(record, generation, result))
|
package/src/launch/client.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as net from "node:net";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { isEexist, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import { isEexist, isEisdir, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { resolveWorkerSpawnCmd, workerEnvFromParent } from "../subprocess/worker-client";
|
|
7
7
|
import { daemonBrokerEndpoint, daemonRuntimeDir } from "./paths";
|
|
8
8
|
import {
|
|
@@ -49,7 +49,7 @@ async function canonicalProjectDir(projectDir: string): Promise<string> {
|
|
|
49
49
|
try {
|
|
50
50
|
return await fs.realpath(resolved);
|
|
51
51
|
} catch (error) {
|
|
52
|
-
if (isEnoent(error)) return resolved;
|
|
52
|
+
if (isEnoent(error) || isEisdir(error)) return resolved;
|
|
53
53
|
throw error;
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/launch/presence.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { isEisdir, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { daemonRuntimeDir } from "./paths";
|
|
5
5
|
|
|
6
6
|
const CLIENTS_DIR = "clients";
|
|
@@ -15,7 +15,7 @@ async function canonicalProjectDir(projectDir: string): Promise<string> {
|
|
|
15
15
|
try {
|
|
16
16
|
return await fs.realpath(resolved);
|
|
17
17
|
} catch (error) {
|
|
18
|
-
if (isEnoent(error)) return resolved;
|
|
18
|
+
if (isEnoent(error) || isEisdir(error)) return resolved;
|
|
19
19
|
throw error;
|
|
20
20
|
}
|
|
21
21
|
}
|
package/src/lsp/client.ts
CHANGED
|
@@ -428,7 +428,7 @@ async function handleConfigurationRequest(client: LspClient, message: LspJsonRpc
|
|
|
428
428
|
const items = params?.items ?? [];
|
|
429
429
|
const result = items.map(item => {
|
|
430
430
|
const section = item.section ?? "";
|
|
431
|
-
return client.config.settings?.[section] ??
|
|
431
|
+
return client.config.settings?.[section] ?? null;
|
|
432
432
|
});
|
|
433
433
|
await sendResponse(client, message.id, result, "workspace/configuration");
|
|
434
434
|
}
|
package/src/main.ts
CHANGED
|
@@ -59,6 +59,7 @@ import type { PrintModeOptions } from "./modes/print-mode";
|
|
|
59
59
|
import { CURRENT_SETUP_VERSION } from "./modes/setup-version";
|
|
60
60
|
import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
|
|
61
61
|
import type { SubmittedUserInput } from "./modes/types";
|
|
62
|
+
import { createWarpEventBridgeExtension } from "./modes/warp-events";
|
|
62
63
|
import { AgentLifecycleManager } from "./registry/agent-lifecycle";
|
|
63
64
|
import {
|
|
64
65
|
type CreateAgentSessionOptions,
|
|
@@ -76,7 +77,7 @@ import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
|
76
77
|
import { shouldShowStartupSplash } from "./startup-splash";
|
|
77
78
|
import { discoverTitleSystemPromptFile, resolvePromptInput } from "./system-prompt";
|
|
78
79
|
import { createPersistedSubagentReviverFactory } from "./task/persisted-revive";
|
|
79
|
-
import { initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
|
|
80
|
+
import { createTelemetryExportConfig, initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
|
|
80
81
|
import { concreteThinkingLevel, parseConfiguredThinkingLevel } from "./thinking";
|
|
81
82
|
import type { LspStartupServerInfo } from "./tools";
|
|
82
83
|
import {
|
|
@@ -1350,15 +1351,13 @@ export async function runRootCommand(
|
|
|
1350
1351
|
sessionOptions.hasUI = isInteractive || mode === "rpc-ui";
|
|
1351
1352
|
sessionOptions.settings = settingsInstance;
|
|
1352
1353
|
|
|
1353
|
-
// OTEL: register
|
|
1354
|
-
//
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
1357
|
-
// is enough to enable telemetry — content capture is governed by the
|
|
1358
|
-
// standard OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT env var.
|
|
1354
|
+
// OTEL: register global OTLP exporters when an endpoint is configured via
|
|
1355
|
+
// env, then switch on the agent loop's telemetry hooks so traces, run-level
|
|
1356
|
+
// metrics, and structured logs have source events to export. Content capture
|
|
1357
|
+
// remains governed by OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT.
|
|
1359
1358
|
await logger.time("initTelemetryExport", initTelemetryExport);
|
|
1360
1359
|
if (isTelemetryExportEnabled()) {
|
|
1361
|
-
sessionOptions.telemetry =
|
|
1360
|
+
sessionOptions.telemetry = createTelemetryExportConfig(sessionOptions.telemetry);
|
|
1362
1361
|
}
|
|
1363
1362
|
|
|
1364
1363
|
// Handle CLI --api-key as runtime override (not persisted)
|
|
@@ -1407,6 +1406,10 @@ export async function runRootCommand(
|
|
|
1407
1406
|
// string-flag value such as `--target @notes.md` is the flag's value, not a
|
|
1408
1407
|
// file — and the same result is handed to createAgentSession via
|
|
1409
1408
|
// `preloadedExtensions` so the discovery work is not repeated.
|
|
1409
|
+
if (isInteractive) {
|
|
1410
|
+
sessionOptions.extensions = [...(sessionOptions.extensions ?? []), createWarpEventBridgeExtension()];
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1410
1413
|
const eventBus = new EventBus();
|
|
1411
1414
|
const extensionsResult = await loadSessionExtensions(sessionOptions, cwd, settingsInstance, eventBus);
|
|
1412
1415
|
const extensionFlagSink: ExtensionFlagSink = {
|