pi-btw 0.6.1 → 0.7.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/README.md +49 -0
- package/extensions/btw-extension-tools.ts +111 -0
- package/extensions/btw.ts +137 -16
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -25,6 +25,10 @@ A small [pi](https://github.com/earendil-works/pi-mono) extension that adds a `/
|
|
|
25
25
|
|
|
26
26
|
pi-btw requires Pi 0.85.1 or newer.
|
|
27
27
|
|
|
28
|
+
Development dependencies target Pi 0.99.2. CI runs the tests and typecheck
|
|
29
|
+
against the locked dependencies, Pi 0.85.1, and the latest published Pi release
|
|
30
|
+
on each PR and weekly. Pi 0.99.2 requires Node.js 22.19.0 or newer.
|
|
31
|
+
|
|
28
32
|
### From npm (after publish)
|
|
29
33
|
|
|
30
34
|
```bash
|
|
@@ -174,8 +178,53 @@ BTW is implemented as an actual pi sub-session with its own in-memory session st
|
|
|
174
178
|
- BTW can inherit the main thread model/thinking settings or use BTW-only overrides via `/btw:model` and `/btw:thinking`
|
|
175
179
|
- `/btw:summarize` uses the current effective BTW model but keeps thinking off
|
|
176
180
|
- the overlay transcript/status line is driven from sub-session events, so tool activity, streaming deltas, failures, and recovery are all visible without scraping rendered output
|
|
181
|
+
- child prompts preserve the main session's instructions and append an authoritative list of their own tools; inherited tool/skill instructions and historical tool calls do not grant additional capabilities
|
|
177
182
|
- handoff commands (`/btw:inject` and `/btw:summarize`) read from the BTW sub-session thread rather than maintaining a separate manual transcript model
|
|
178
183
|
|
|
184
|
+
### Opt-in extension tools
|
|
185
|
+
|
|
186
|
+
BTW loads no extensions by default. To enable tools such as `web_search` and
|
|
187
|
+
`fetch_content` in `/btw`, `/side`, and `/btw:tangent`, create
|
|
188
|
+
`~/.pi/agent/btw.json` (or `btw.json` in your `PI_CODING_AGENT_DIR`):
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"extensions": ["npm:pi-web-access"]
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
A trusted project's `.pi/btw.json` can override this list. Lists replace rather
|
|
197
|
+
than merge; `{"extensions": []}` disables global BTW extensions for that
|
|
198
|
+
project. An omitted `extensions` key inherits the global list. Untrusted
|
|
199
|
+
projects do not contribute configuration. Config is read when a child session
|
|
200
|
+
is created; use `/btw:clear` to apply changes to an existing thread.
|
|
201
|
+
|
|
202
|
+
Sources can be Pi `npm:` or `git:` packages, or local extension files/package
|
|
203
|
+
directories. Local paths are relative to the config file's directory. Remote
|
|
204
|
+
packages are resolved into a separate BTW cache under the agent directory's
|
|
205
|
+
`btw/` folder and can be installed on first use. Pin a source version when you
|
|
206
|
+
need reproducible behavior.
|
|
207
|
+
|
|
208
|
+
Only listed sources are loaded. BTW runs their lifecycle handlers headlessly
|
|
209
|
+
(`ctx.hasUI === false`) and exposes their tools alongside `read`, `bash`,
|
|
210
|
+
`edit`, and `write`, including tools registered at startup. The capability
|
|
211
|
+
note follows the child's active tools. Load/startup errors stop child creation
|
|
212
|
+
and are reported with the failing source; clear, mode/model changes, and parent
|
|
213
|
+
shutdown run extension cleanup before disposal.
|
|
214
|
+
|
|
215
|
+
Choose extensions that support headless sessions. Tools requiring interactive
|
|
216
|
+
dialogs (such as `ask_user`) need additional UI integration. BTW does not import
|
|
217
|
+
extension skills, prompt templates, themes, widgets, or shortcuts into its UI.
|
|
218
|
+
Extensions are trusted code, not sandboxed: separate package installs avoid
|
|
219
|
+
sharing the parent's cached extension factory, but extensions may still use
|
|
220
|
+
shared files or external services. A local source already registered by the
|
|
221
|
+
parent is rejected; use an `npm:` or `git:` source for a separate install.
|
|
222
|
+
|
|
223
|
+
`/btw:ask` never reads this configuration or loads extension tools, so it retains
|
|
224
|
+
its built-in read-only tool set. `/btw:summarize` remains tool-free. Extension
|
|
225
|
+
configuration is machine/project configuration, not persisted in hidden
|
|
226
|
+
conversation-history entries.
|
|
227
|
+
|
|
179
228
|
### In-modal slash behavior
|
|
180
229
|
|
|
181
230
|
Inside the BTW modal composer, slash handling is split at the BTW/session boundary:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
DefaultPackageManager,
|
|
6
|
+
DefaultResourceLoader,
|
|
7
|
+
SettingsManager,
|
|
8
|
+
type ResourceLoader,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
/** Resolve local paths relative to the config that owns them, not process.cwd(). */
|
|
12
|
+
function resolveSource(source: string, configPath: string): string {
|
|
13
|
+
if (source.startsWith("npm:") || source.startsWith("git:")) return source;
|
|
14
|
+
if (source.startsWith("~/")) return join(homedir(), source.slice(2));
|
|
15
|
+
return resolve(dirname(configPath), source);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function readSources(path: string): Promise<string[] | undefined> {
|
|
19
|
+
let text: string;
|
|
20
|
+
try {
|
|
21
|
+
text = await readFile(path, "utf8");
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
24
|
+
throw new Error(`Cannot read BTW config ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let config: unknown;
|
|
28
|
+
try {
|
|
29
|
+
config = JSON.parse(text);
|
|
30
|
+
} catch {
|
|
31
|
+
throw new Error(`Invalid BTW config ${path}: expected JSON.`);
|
|
32
|
+
}
|
|
33
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
34
|
+
throw new Error(`Invalid BTW config ${path}: expected an object.`);
|
|
35
|
+
}
|
|
36
|
+
const fields = Object.keys(config);
|
|
37
|
+
if (fields.some((field) => field !== "extensions")) {
|
|
38
|
+
throw new Error(`Invalid BTW config ${path}: only the "extensions" key is supported.`);
|
|
39
|
+
}
|
|
40
|
+
if (!("extensions" in config)) return undefined;
|
|
41
|
+
if (!Array.isArray(config.extensions) || config.extensions.some((source) => typeof source !== "string" || !source.trim())) {
|
|
42
|
+
throw new Error(`Invalid BTW config ${path}: "extensions" must be an array of nonempty source strings.`);
|
|
43
|
+
}
|
|
44
|
+
return [...new Set(config.extensions.map((source: string) => resolveSource(source.trim(), path)))];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Project lists replace global lists, including an explicit empty list. */
|
|
48
|
+
export async function readBtwExtensionSources(options: {
|
|
49
|
+
cwd: string;
|
|
50
|
+
agentDir: string;
|
|
51
|
+
projectTrusted: boolean;
|
|
52
|
+
}): Promise<string[]> {
|
|
53
|
+
const globalSources = await readSources(join(options.agentDir, "btw.json"));
|
|
54
|
+
const projectSources = options.projectTrusted
|
|
55
|
+
? await readSources(join(options.cwd, ".pi", "btw.json"))
|
|
56
|
+
: undefined;
|
|
57
|
+
return projectSources ?? globalSources ?? [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function canonicalPath(path: string): Promise<string> {
|
|
61
|
+
try {
|
|
62
|
+
return await realpath(path);
|
|
63
|
+
} catch {
|
|
64
|
+
return resolve(path);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the allowlist before running any extension factories. Remote packages
|
|
70
|
+
* have a BTW-owned install root so Pi's factory cache cannot reuse the parent's
|
|
71
|
+
* module globals. Local sources already loaded by the parent are rejected.
|
|
72
|
+
*/
|
|
73
|
+
export async function loadBtwExtensionResources(options: {
|
|
74
|
+
cwd: string;
|
|
75
|
+
agentDir: string;
|
|
76
|
+
sources: string[];
|
|
77
|
+
parentExtensionPaths: string[];
|
|
78
|
+
}): Promise<ResourceLoader> {
|
|
79
|
+
const agentDir = join(options.agentDir, "btw");
|
|
80
|
+
const settingsManager = SettingsManager.inMemory();
|
|
81
|
+
const packages = new DefaultPackageManager({ cwd: options.cwd, agentDir, settingsManager });
|
|
82
|
+
const selectedPaths = new Set<string>();
|
|
83
|
+
for (const source of options.sources) {
|
|
84
|
+
const resolved = await packages.resolveExtensionSources([source], { temporary: true });
|
|
85
|
+
const paths = resolved.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);
|
|
86
|
+
if (paths.length === 0) throw new Error(`BTW source ${source} did not resolve to any extensions.`);
|
|
87
|
+
for (const path of paths) selectedPaths.add(path);
|
|
88
|
+
}
|
|
89
|
+
const paths = [...selectedPaths];
|
|
90
|
+
const parentPaths = new Set(await Promise.all(options.parentExtensionPaths.map(canonicalPath)));
|
|
91
|
+
for (const path of paths) {
|
|
92
|
+
if (parentPaths.has(await canonicalPath(path))) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`BTW cannot reload the parent's extension ${path}: Pi can share its module state. Use an npm: or git: source for a separate BTW install.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const loader = new DefaultResourceLoader({
|
|
99
|
+
cwd: options.cwd,
|
|
100
|
+
agentDir,
|
|
101
|
+
settingsManager,
|
|
102
|
+
noExtensions: true,
|
|
103
|
+
noSkills: true,
|
|
104
|
+
noPromptTemplates: true,
|
|
105
|
+
noThemes: true,
|
|
106
|
+
noContextFiles: true,
|
|
107
|
+
additionalExtensionPaths: paths,
|
|
108
|
+
});
|
|
109
|
+
await loader.reload();
|
|
110
|
+
return loader;
|
|
111
|
+
}
|
package/extensions/btw.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
buildSessionContext,
|
|
3
3
|
createAgentSession,
|
|
4
4
|
createExtensionRuntime,
|
|
5
|
+
getAgentDir,
|
|
5
6
|
getMarkdownTheme,
|
|
6
7
|
ModelRuntime,
|
|
7
8
|
SessionManager,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
type OverlayOptions,
|
|
39
40
|
type TUI,
|
|
40
41
|
} from "@earendil-works/pi-tui";
|
|
42
|
+
import { loadBtwExtensionResources, readBtwExtensionSources } from "./btw-extension-tools";
|
|
41
43
|
|
|
42
44
|
const BTW_MESSAGE_TYPE = "btw-note";
|
|
43
45
|
const BTW_ENTRY_TYPE = "btw-thread-entry";
|
|
@@ -270,6 +272,7 @@ type BtwSessionRuntime = {
|
|
|
270
272
|
sideThreadStartIndex: number;
|
|
271
273
|
abortPromise?: Promise<void>;
|
|
272
274
|
promptQueue: Promise<void>;
|
|
275
|
+
hasExtensions: boolean;
|
|
273
276
|
};
|
|
274
277
|
|
|
275
278
|
type OverlayRuntime = {
|
|
@@ -298,10 +301,28 @@ function stripDynamicSystemPromptFooter(systemPrompt: string): string {
|
|
|
298
301
|
|
|
299
302
|
function createBtwResourceLoader(
|
|
300
303
|
ctx: ExtensionCommandContext,
|
|
304
|
+
tools: readonly string[] | (() => readonly string[]),
|
|
301
305
|
appendSystemPrompt: string[] = [BTW_SYSTEM_PROMPT],
|
|
306
|
+
extensionResources?: ResourceLoader,
|
|
302
307
|
): ResourceLoader {
|
|
303
|
-
const extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
308
|
+
const extensionsResult = extensionResources?.getExtensions() ?? { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
304
309
|
const systemPrompt = stripDynamicSystemPromptFooter(ctx.getSystemPrompt());
|
|
310
|
+
// Preserve the parent's project/custom instructions, but override capability
|
|
311
|
+
// claims in its rendered prompt and in the history seeded into this child.
|
|
312
|
+
const capabilities = () => {
|
|
313
|
+
const availableTools = typeof tools === "function" ? tools() : tools;
|
|
314
|
+
return [
|
|
315
|
+
"<btw_capabilities>",
|
|
316
|
+
availableTools.length > 0
|
|
317
|
+
? `Available tools in this BTW session: ${availableTools.join(", ")}.`
|
|
318
|
+
: "No tools are available in this BTW session.",
|
|
319
|
+
"This capability list is authoritative for this child session.",
|
|
320
|
+
"Tool and skill instructions inherited from the main session may describe tools that are unavailable here.",
|
|
321
|
+
"Previous tool calls in inherited conversation are historical context, not available capabilities.",
|
|
322
|
+
"Only call tools listed above; do not infer additional tools from the main session.",
|
|
323
|
+
"</btw_capabilities>",
|
|
324
|
+
].join("\n");
|
|
325
|
+
};
|
|
305
326
|
|
|
306
327
|
const resourceLoader: ResourceLoader = {
|
|
307
328
|
getExtensions: () => extensionsResult,
|
|
@@ -311,7 +332,7 @@ function createBtwResourceLoader(
|
|
|
311
332
|
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
312
333
|
getSystemPrompt: () => systemPrompt,
|
|
313
334
|
getSystemPromptSource: () => undefined,
|
|
314
|
-
getAppendSystemPrompt: () => appendSystemPrompt,
|
|
335
|
+
getAppendSystemPrompt: () => [...appendSystemPrompt, capabilities()],
|
|
315
336
|
getAppendSystemPromptSources: () => [],
|
|
316
337
|
extendResources: () => {},
|
|
317
338
|
reload: async (_options) => {},
|
|
@@ -1628,6 +1649,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1628
1649
|
let activeBtwSession: BtwSessionRuntime | null = null;
|
|
1629
1650
|
let btwLifecycleGeneration = 0;
|
|
1630
1651
|
let btwSubmissionQueue = Promise.resolve();
|
|
1652
|
+
let btwSessionCreationQueue = Promise.resolve();
|
|
1631
1653
|
|
|
1632
1654
|
function invalidateBtwLifecycle(): void {
|
|
1633
1655
|
btwLifecycleGeneration += 1;
|
|
@@ -1803,7 +1825,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
1803
1825
|
|
|
1804
1826
|
clearBtwSessionSubscriptions(current);
|
|
1805
1827
|
await requestBtwSessionAbort(current);
|
|
1806
|
-
current.session.
|
|
1828
|
+
await disposeChildSession(current.session, current.hasExtensions);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
async function disposeChildSession(session: AgentSession, hasExtensions: boolean): Promise<void> {
|
|
1832
|
+
try {
|
|
1833
|
+
if (hasExtensions) {
|
|
1834
|
+
await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
|
|
1835
|
+
}
|
|
1836
|
+
} finally {
|
|
1837
|
+
session.dispose();
|
|
1838
|
+
}
|
|
1807
1839
|
}
|
|
1808
1840
|
|
|
1809
1841
|
async function dismissOverlaySession(): Promise<void> {
|
|
@@ -1974,8 +2006,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
1974
2006
|
throw new Error(settings.fallbackReason || "No active model selected.");
|
|
1975
2007
|
}
|
|
1976
2008
|
|
|
2009
|
+
const agentDir = getAgentDir();
|
|
2010
|
+
const sources = mode === "readonly" ? [] : await readBtwExtensionSources({
|
|
2011
|
+
cwd: ctx.cwd,
|
|
2012
|
+
agentDir,
|
|
2013
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
2014
|
+
});
|
|
1977
2015
|
const modelRuntimeOptions = await createBtwModelRuntimeOptions(ctx, settings.model);
|
|
1978
|
-
const
|
|
2016
|
+
const extensionResources = sources.length > 0 ? await loadBtwExtensionResources({
|
|
2017
|
+
cwd: ctx.cwd,
|
|
2018
|
+
agentDir,
|
|
2019
|
+
sources,
|
|
2020
|
+
parentExtensionPaths: [
|
|
2021
|
+
...pi.getAllTools().map((tool) => tool.sourceInfo.path),
|
|
2022
|
+
...pi.getCommands().flatMap((command) => command.sourceInfo ? [command.sourceInfo.path] : []),
|
|
2023
|
+
],
|
|
2024
|
+
}) : undefined;
|
|
2025
|
+
const sessionManager = SessionManager.inMemory(ctx.cwd);
|
|
1979
2026
|
const { messages: seedMessages, sideThreadStartIndex } = buildBtwSeedState(ctx, pendingThread, mode, settings.model);
|
|
1980
2027
|
|
|
1981
2028
|
// The session manager is the source of provider context. Seed it before
|
|
@@ -1984,23 +2031,85 @@ export default function (pi: ExtensionAPI) {
|
|
|
1984
2031
|
sessionManager.appendMessage(message);
|
|
1985
2032
|
}
|
|
1986
2033
|
|
|
2034
|
+
const builtinTools = BTW_TOOLS_BY_MODE[mode];
|
|
2035
|
+
let childSession: AgentSession | undefined;
|
|
1987
2036
|
const sessionOptions: CreateAgentSessionOptions = {
|
|
2037
|
+
cwd: ctx.cwd,
|
|
1988
2038
|
sessionManager,
|
|
1989
2039
|
model: settings.model,
|
|
1990
2040
|
...modelRuntimeOptions,
|
|
1991
2041
|
thinkingLevel: settings.thinkingLevel,
|
|
1992
|
-
//
|
|
1993
|
-
|
|
1994
|
-
|
|
2042
|
+
// Extension sessions need a live registry for tools registered at startup
|
|
2043
|
+
// or later. Read-only sessions keep the structural built-in allowlist.
|
|
2044
|
+
...(extensionResources ? { noTools: "builtin" as const } : { tools: [...builtinTools] }),
|
|
2045
|
+
resourceLoader: createBtwResourceLoader(
|
|
2046
|
+
ctx, () => childSession?.getActiveToolNames() ?? builtinTools, [BTW_SYSTEM_PROMPT], extensionResources,
|
|
2047
|
+
),
|
|
1995
2048
|
};
|
|
1996
|
-
const { session } = await createAgentSession(sessionOptions);
|
|
2049
|
+
const { session, extensionsResult } = await createAgentSession(sessionOptions);
|
|
2050
|
+
childSession = session;
|
|
2051
|
+
const hasExtensions = !!extensionResources;
|
|
2052
|
+
if (hasExtensions) {
|
|
2053
|
+
try {
|
|
2054
|
+
if (extensionsResult.errors.length > 0) {
|
|
2055
|
+
throw new Error(extensionsResult.errors.map((error) => `${error.path}: ${error.error}`).join("\n"));
|
|
2056
|
+
}
|
|
2057
|
+
const collectToolNames = () => {
|
|
2058
|
+
const builtinNames = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "powershell"]);
|
|
2059
|
+
const extensionNames = new Set<string>();
|
|
2060
|
+
for (const extension of extensionsResult.extensions) {
|
|
2061
|
+
for (const name of extension.tools.keys()) {
|
|
2062
|
+
if (builtinNames.has(name) || extensionNames.has(name)) {
|
|
2063
|
+
throw new Error(`BTW extension tool name collision: ${name}.`);
|
|
2064
|
+
}
|
|
2065
|
+
extensionNames.add(name);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
return [...extensionNames];
|
|
2069
|
+
};
|
|
2070
|
+
session.setActiveToolsByName([...builtinTools, ...collectToolNames()]);
|
|
2071
|
+
const startupErrors: string[] = [];
|
|
2072
|
+
let starting = true;
|
|
2073
|
+
await session.bindExtensions({
|
|
2074
|
+
onError: (error) => {
|
|
2075
|
+
const message = `${error.extensionPath}: ${error.error}`;
|
|
2076
|
+
if (starting) startupErrors.push(message);
|
|
2077
|
+
else notify(ctx, `BTW extension error: ${message}`, "error");
|
|
2078
|
+
},
|
|
2079
|
+
});
|
|
2080
|
+
starting = false;
|
|
2081
|
+
if (startupErrors.length > 0) throw new Error(startupErrors.join("\n"));
|
|
2082
|
+
collectToolNames();
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
try {
|
|
2085
|
+
await session.abort();
|
|
2086
|
+
} finally {
|
|
2087
|
+
await disposeChildSession(session, true);
|
|
2088
|
+
}
|
|
2089
|
+
throw error;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
1997
2092
|
|
|
1998
|
-
return { session, mode, subscriptions: new Set(), sideThreadStartIndex, promptQueue: Promise.resolve() };
|
|
2093
|
+
return { session, mode, subscriptions: new Set(), sideThreadStartIndex, promptQueue: Promise.resolve(), hasExtensions };
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
function ensureBtwSession(ctx: ExtensionCommandContext, mode: BtwThreadMode): Promise<BtwSessionRuntime | null> {
|
|
2097
|
+
const generation = btwLifecycleGeneration;
|
|
2098
|
+
// Composer-only commands can overlap while npm resolution is in flight.
|
|
2099
|
+
// Serialize creation so they reuse one child and never race package installs.
|
|
2100
|
+
const creation = btwSessionCreationQueue.then(() => createOrReuseBtwSession(ctx, mode, generation));
|
|
2101
|
+
btwSessionCreationQueue = creation.then(() => {}, () => {});
|
|
2102
|
+
return creation;
|
|
1999
2103
|
}
|
|
2000
2104
|
|
|
2001
|
-
async function
|
|
2105
|
+
async function createOrReuseBtwSession(
|
|
2106
|
+
ctx: ExtensionCommandContext,
|
|
2107
|
+
mode: BtwThreadMode,
|
|
2108
|
+
generation: number,
|
|
2109
|
+
): Promise<BtwSessionRuntime | null> {
|
|
2110
|
+
if (generation !== btwLifecycleGeneration) return null;
|
|
2002
2111
|
const settings = await resolveBtwSettings(ctx, true);
|
|
2003
|
-
if (!settings.model) {
|
|
2112
|
+
if (generation !== btwLifecycleGeneration || !settings.model) {
|
|
2004
2113
|
return null;
|
|
2005
2114
|
}
|
|
2006
2115
|
|
|
@@ -2009,7 +2118,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
2009
2118
|
}
|
|
2010
2119
|
|
|
2011
2120
|
await disposeBtwSession();
|
|
2012
|
-
|
|
2121
|
+
if (generation !== btwLifecycleGeneration) return null;
|
|
2122
|
+
try {
|
|
2123
|
+
const created = await createBtwSubSession(ctx, mode, settings);
|
|
2124
|
+
if (generation !== btwLifecycleGeneration) {
|
|
2125
|
+
await requestBtwSessionAbort(created);
|
|
2126
|
+
await disposeChildSession(created.session, created.hasExtensions);
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
activeBtwSession = created;
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
if (generation !== btwLifecycleGeneration) return null;
|
|
2132
|
+
const message = `Could not start BTW session: ${error instanceof Error ? error.message : String(error)}`;
|
|
2133
|
+
setOverlayStatus(message, ctx);
|
|
2134
|
+
notify(ctx, message, "error");
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2013
2137
|
return activeBtwSession;
|
|
2014
2138
|
}
|
|
2015
2139
|
|
|
@@ -2501,8 +2625,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
2501
2625
|
return;
|
|
2502
2626
|
}
|
|
2503
2627
|
if (!sessionRuntime) {
|
|
2504
|
-
setOverlayStatus("No active model selected.", ctx);
|
|
2505
|
-
notify(ctx, "No active model selected.", "error");
|
|
2506
2628
|
return;
|
|
2507
2629
|
}
|
|
2508
2630
|
|
|
@@ -2670,7 +2792,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2670
2792
|
...modelRuntimeOptions,
|
|
2671
2793
|
thinkingLevel: "off",
|
|
2672
2794
|
tools: [],
|
|
2673
|
-
resourceLoader: createBtwResourceLoader(ctx, [BTW_SUMMARIZE_SYSTEM_PROMPT]),
|
|
2795
|
+
resourceLoader: createBtwResourceLoader(ctx, [], [BTW_SUMMARIZE_SYSTEM_PROMPT]),
|
|
2674
2796
|
};
|
|
2675
2797
|
const { session } = await createAgentSession(sessionOptions);
|
|
2676
2798
|
|
|
@@ -2861,4 +2983,3 @@ export default function (pi: ExtensionAPI) {
|
|
|
2861
2983
|
},
|
|
2862
2984
|
});
|
|
2863
2985
|
}
|
|
2864
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-btw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A pi extension for parallel side conversations with /btw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"@earendil-works/pi-tui": ">=0.85.1 <1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@earendil-works/pi-ai": "0.
|
|
53
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
54
|
-
"@earendil-works/pi-tui": "0.
|
|
52
|
+
"@earendil-works/pi-ai": "0.99.2",
|
|
53
|
+
"@earendil-works/pi-coding-agent": "0.99.2",
|
|
54
|
+
"@earendil-works/pi-tui": "0.99.2",
|
|
55
55
|
"typescript": "^6.0.2",
|
|
56
|
-
"vitest": "^4.1.
|
|
56
|
+
"vitest": "^4.1.11"
|
|
57
57
|
}
|
|
58
58
|
}
|