@pencil-agent/nano-pencil 1.13.0 → 1.13.4
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/dist/builtin-extensions.js +20 -0
- package/dist/cli.js +24 -1
- package/dist/core/config/settings-manager.d.ts +17 -0
- package/dist/core/config/settings-manager.js +176 -0
- package/dist/core/extensions/loader.js +10 -3
- package/dist/core/mcp/mcp-client.js +18 -7
- package/dist/core/messages.js +1 -1
- package/dist/core/package-manager.d.ts +2 -1
- package/dist/core/package-manager.js +17 -22
- package/dist/core/runtime/agent-session.js +8 -3
- package/dist/extensions/defaults/CLAUDE.md +10 -5
- package/dist/extensions/defaults/btw/index.d.ts +8 -0
- package/dist/extensions/defaults/btw/index.js +110 -0
- package/dist/extensions/defaults/debug/collectors.d.ts +85 -0
- package/dist/extensions/defaults/debug/collectors.js +248 -0
- package/dist/extensions/defaults/debug/index.d.ts +8 -0
- package/dist/extensions/defaults/debug/index.js +189 -0
- package/dist/extensions/defaults/grub/README.md +85 -14
- package/dist/extensions/defaults/grub/grub-controller.d.ts +25 -4
- package/dist/extensions/defaults/grub/grub-controller.js +120 -17
- package/dist/extensions/defaults/grub/grub-feature-list.d.ts +35 -0
- package/dist/extensions/defaults/grub/grub-feature-list.js +208 -0
- package/dist/extensions/defaults/grub/grub-parser.d.ts +1 -1
- package/dist/extensions/defaults/grub/grub-parser.js +87 -8
- package/dist/extensions/defaults/grub/grub-persistence.d.ts +17 -0
- package/dist/extensions/defaults/grub/grub-persistence.js +166 -0
- package/dist/extensions/defaults/grub/grub-types.d.ts +50 -3
- package/dist/extensions/defaults/grub/grub-types.js +2 -1
- package/dist/extensions/defaults/grub/index.d.ts +3 -3
- package/dist/extensions/defaults/grub/index.js +257 -21
- package/dist/extensions/defaults/loop/cron/cron-scheduler.js +7 -3
- package/dist/extensions/defaults/plan/exit-plan-mode-tool.js +11 -20
- package/dist/extensions/defaults/plan/index.js +21 -5
- package/dist/extensions/defaults/plan/plan-file-manager.d.ts +3 -3
- package/dist/extensions/defaults/plan/plan-file-manager.js +12 -7
- package/dist/extensions/defaults/plan/teammate-approval.d.ts +4 -1
- package/dist/extensions/defaults/plan/teammate-approval.js +4 -1
- package/dist/extensions/defaults/presence/index.d.ts +1 -1
- package/dist/extensions/defaults/presence/index.js +18 -6
- package/dist/extensions/defaults/sal/index.js +66 -24
- package/dist/extensions/defaults/sal/terrain.d.ts +13 -3
- package/dist/extensions/defaults/sal/terrain.js +65 -37
- package/dist/extensions/defaults/security-audit/engine/detector.d.ts +0 -7
- package/dist/extensions/defaults/security-audit/engine/detector.js +0 -7
- package/dist/extensions/defaults/security-audit/engine/interceptor.d.ts +0 -7
- package/dist/extensions/defaults/security-audit/engine/interceptor.js +0 -7
- package/dist/extensions/defaults/security-audit/engine/logger.d.ts +0 -7
- package/dist/extensions/defaults/security-audit/engine/logger.js +0 -7
- package/dist/extensions/defaults/security-audit/interface.d.ts +0 -8
- package/dist/extensions/defaults/security-audit/interface.js +0 -8
- package/dist/extensions/optional/export-html/index.js +8 -1
- package/dist/main.js +9 -2
- package/dist/modes/acp/acp-mode.js +4 -1
- package/dist/modes/interactive/components/memory-stats.d.ts +0 -5
- package/dist/modes/interactive/components/memory-stats.js +0 -5
- package/dist/modes/interactive/components/pencil-loader.d.ts +27 -3
- package/dist/modes/interactive/components/pencil-loader.js +73 -6
- package/dist/modes/interactive/components/soul-stats.d.ts +0 -5
- package/dist/modes/interactive/components/soul-stats.js +0 -5
- package/dist/modes/interactive/interactive-mode.d.ts +9 -1
- package/dist/modes/interactive/interactive-mode.js +87 -16
- package/dist/modes/interactive/services/tips.d.ts +20 -0
- package/dist/modes/interactive/services/tips.js +96 -0
- package/dist/nanopencil-defaults.js +4 -6
- package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +162 -71
- package/dist/node_modules/@pencil-agent/ai/models.generated.js +220 -134
- package/dist/node_modules/@pencil-agent/tui/autocomplete.js +10 -3
- package/dist/node_modules/@pencil-agent/tui/components/editor.d.ts +2 -0
- package/dist/node_modules/@pencil-agent/tui/components/editor.js +6 -2
- package/dist/node_modules/@pencil-agent/tui/fuzzy.d.ts +11 -0
- package/dist/node_modules/@pencil-agent/tui/fuzzy.js +40 -0
- package/dist/node_modules/@pencil-agent/tui/index.d.ts +1 -1
- package/dist/node_modules/@pencil-agent/tui/index.js +1 -1
- package/dist/packages/mem-core/consolidation.js +2 -1
- package/dist/packages/mem-core/engine-scoring-v2.js +33 -9
- package/dist/packages/mem-core/extraction.js +2 -1
- package/dist/packages/soul-core/config.d.ts +0 -5
- package/dist/packages/soul-core/config.js +0 -5
- package/dist/packages/soul-core/evolution.d.ts +0 -5
- package/dist/packages/soul-core/evolution.js +0 -5
- package/dist/packages/soul-core/index.d.ts +0 -5
- package/dist/packages/soul-core/index.js +0 -5
- package/dist/packages/soul-core/injection.d.ts +0 -5
- package/dist/packages/soul-core/injection.js +0 -5
- package/dist/packages/soul-core/manager.d.ts +0 -5
- package/dist/packages/soul-core/manager.js +0 -5
- package/dist/packages/soul-core/src/config.d.ts +0 -5
- package/dist/packages/soul-core/src/config.js +0 -5
- package/dist/packages/soul-core/src/evolution.d.ts +0 -5
- package/dist/packages/soul-core/src/evolution.js +0 -5
- package/dist/packages/soul-core/src/index.d.ts +0 -5
- package/dist/packages/soul-core/src/index.js +0 -5
- package/dist/packages/soul-core/src/injection.d.ts +0 -5
- package/dist/packages/soul-core/src/injection.js +0 -5
- package/dist/packages/soul-core/src/manager.d.ts +0 -5
- package/dist/packages/soul-core/src/manager.js +0 -5
- package/dist/packages/soul-core/src/store.d.ts +0 -5
- package/dist/packages/soul-core/src/store.js +0 -5
- package/dist/packages/soul-core/src/types.d.ts +0 -5
- package/dist/packages/soul-core/src/types.js +0 -5
- package/dist/packages/soul-core/store.d.ts +0 -5
- package/dist/packages/soul-core/store.js +0 -5
- package/dist/packages/soul-core/types.d.ts +0 -5
- package/dist/packages/soul-core/types.js +0 -5
- package/dist/utils/startup-profiler.d.ts +31 -0
- package/dist/utils/startup-profiler.js +48 -0
- package/docs/SAL/345/256/236/351/252/214/350/257/204/344/274/260/346/226/271/345/274/217/357/274/210/344/273/243/347/240/201/345/257/271/346/257/224/344/270/216/345/244/232worktree/357/274/211.md +158 -0
- package/docs/SAL/346/200/273/344/275/223/350/267/257/347/272/277/344/270/216/345/256/236/351/252/214/345/244/247/347/272/262.md +213 -0
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +251 -0
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +123 -0
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -0
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -0
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -0
- package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +321 -0
- package/docs/loop-usage-examples.md +215 -0
- package/docs/planmode.md +1987 -0
- package/package.json +5 -3
- package/docs/SAL/345/256/236/351/252/214/350/276/271/347/225/214/344/270/216/345/220/216/347/273/255/346/234/272/345/210/266/350/267/257/347/272/277.md +0 -305
- package/docs/SAL/345/257/271/346/257/224/350/257/225/351/252/214/350/256/276/350/256/241.md +0 -667
- package/docs/SAL/347/273/223/346/236/204/351/224/232/347/202/271/345/256/232/344/275/215/346/226/271/346/241/210.md +0 -851
|
@@ -24,6 +24,8 @@ const BUNDLED_SAL_EXTENSION = join(__dirname, "extensions", "defaults", "sal", "
|
|
|
24
24
|
const BUNDLED_GRUB_EXTENSION = join(__dirname, "extensions", "defaults", "grub", "index.js");
|
|
25
25
|
const BUNDLED_SUBAGENT_EXTENSION = join(__dirname, "extensions", "defaults", "subagent", "index.js");
|
|
26
26
|
const BUNDLED_TEAM_EXTENSION = join(__dirname, "extensions", "defaults", "team", "index.js");
|
|
27
|
+
const BUNDLED_BTW_EXTENSION = join(__dirname, "extensions", "defaults", "btw", "index.js");
|
|
28
|
+
const BUNDLED_DEBUG_EXTENSION = join(__dirname, "extensions", "defaults", "debug", "index.js");
|
|
27
29
|
const BUNDLED_MCP_EXTENSION = join(__dirname, "extensions", "defaults", "mcp", "index.js");
|
|
28
30
|
const BUNDLED_EXPORT_HTML_EXTENSION = join(__dirname, "extensions", "optional", "export-html", "index.js");
|
|
29
31
|
/** Find package root from current module location (containing package.json with nano-pencil related name) */
|
|
@@ -209,6 +211,24 @@ export function getBuiltinExtensionPaths() {
|
|
|
209
211
|
if (existsSync(teamTs))
|
|
210
212
|
paths.push(teamTs);
|
|
211
213
|
}
|
|
214
|
+
// === BTW extension (quick side question without interrupting) ===
|
|
215
|
+
if (existsSync(BUNDLED_BTW_EXTENSION)) {
|
|
216
|
+
paths.push(BUNDLED_BTW_EXTENSION);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const btwTs = join(__dirname, "extensions", "defaults", "btw", "index.ts");
|
|
220
|
+
if (existsSync(btwTs))
|
|
221
|
+
paths.push(btwTs);
|
|
222
|
+
}
|
|
223
|
+
// === Debug extension (system diagnostics with three-layer analysis) ===
|
|
224
|
+
if (existsSync(BUNDLED_DEBUG_EXTENSION)) {
|
|
225
|
+
paths.push(BUNDLED_DEBUG_EXTENSION);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const debugTs = join(__dirname, "extensions", "defaults", "debug", "index.ts");
|
|
229
|
+
if (existsSync(debugTs))
|
|
230
|
+
paths.push(debugTs);
|
|
231
|
+
}
|
|
212
232
|
// Built-in MCP extension
|
|
213
233
|
if (existsSync(BUNDLED_MCP_EXTENSION)) {
|
|
214
234
|
paths.push(BUNDLED_MCP_EXTENSION);
|
package/dist/cli.js
CHANGED
|
@@ -6,5 +6,28 @@
|
|
|
6
6
|
* [HERE]: Entry point; orchestrates argument parsing and mode selection
|
|
7
7
|
*/
|
|
8
8
|
process.title = "nanopencil";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
// Fast path: --version, --help don't need full module loading
|
|
14
|
+
if (args.includes("--version")) {
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
// In dev, package.json is in project root; in bundle, it's two levels up from dist/cli.js
|
|
18
|
+
const pkgPath = __dirname.endsWith("dist") ? join(__dirname, "..", "package.json") : join(__dirname, "package.json");
|
|
19
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
20
|
+
console.log(pkg.version);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
24
|
+
console.log(`nanoPencil AI coding agent`);
|
|
25
|
+
console.log(`Usage: nanopencil [options]`);
|
|
26
|
+
console.log(` nanopencil [command] [options]`);
|
|
27
|
+
console.log(`Options:`);
|
|
28
|
+
console.log(` --version Show version`);
|
|
29
|
+
console.log(` --help, -h Show this help`);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
9
32
|
import { main } from "./main.js";
|
|
10
|
-
main(
|
|
33
|
+
main(args);
|
|
@@ -113,6 +113,8 @@ export interface Settings {
|
|
|
113
113
|
export type SettingsScope = "global" | "project";
|
|
114
114
|
export interface SettingsStorage {
|
|
115
115
|
withLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void;
|
|
116
|
+
withLockAsync(scope: SettingsScope, fn: (current: string | undefined) => Promise<string | undefined>): Promise<void>;
|
|
117
|
+
readStorageAsync(scope: SettingsScope): Promise<string | undefined>;
|
|
116
118
|
}
|
|
117
119
|
export interface SettingsError {
|
|
118
120
|
scope: SettingsScope;
|
|
@@ -123,11 +125,15 @@ export declare class FileSettingsStorage implements SettingsStorage {
|
|
|
123
125
|
private projectSettingsPath;
|
|
124
126
|
constructor(cwd?: string, agentDir?: string);
|
|
125
127
|
withLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void;
|
|
128
|
+
withLockAsync(scope: SettingsScope, fn: (current: string | undefined) => Promise<string | undefined>): Promise<void>;
|
|
129
|
+
readStorageAsync(scope: SettingsScope): Promise<string | undefined>;
|
|
126
130
|
}
|
|
127
131
|
export declare class InMemorySettingsStorage implements SettingsStorage {
|
|
128
132
|
private global;
|
|
129
133
|
private project;
|
|
130
134
|
withLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void;
|
|
135
|
+
withLockAsync(scope: SettingsScope, fn: (current: string | undefined) => Promise<string | undefined>): Promise<void>;
|
|
136
|
+
readStorageAsync(scope: SettingsScope): Promise<string | undefined>;
|
|
131
137
|
}
|
|
132
138
|
export declare class SettingsManager {
|
|
133
139
|
private storage;
|
|
@@ -145,12 +151,18 @@ export declare class SettingsManager {
|
|
|
145
151
|
private constructor();
|
|
146
152
|
/** Create a SettingsManager that loads from files */
|
|
147
153
|
static create(cwd?: string, agentDir?: string): SettingsManager;
|
|
154
|
+
/** Create a SettingsManager that loads from files async */
|
|
155
|
+
static createAsync(cwd?: string, agentDir?: string): Promise<SettingsManager>;
|
|
148
156
|
/** Create a SettingsManager from an arbitrary storage backend */
|
|
149
157
|
static fromStorage(storage: SettingsStorage): SettingsManager;
|
|
158
|
+
/** Create a SettingsManager from an arbitrary storage backend async */
|
|
159
|
+
static fromStorageAsync(storage: SettingsStorage): Promise<SettingsManager>;
|
|
150
160
|
/** Create an in-memory SettingsManager (no file I/O) */
|
|
151
161
|
static inMemory(settings?: Partial<Settings>): SettingsManager;
|
|
152
162
|
private static loadFromStorage;
|
|
163
|
+
private static loadFromStorageAsync;
|
|
153
164
|
private static tryLoadFromStorage;
|
|
165
|
+
private static tryLoadFromStorageAsync;
|
|
154
166
|
/** Migrate old settings format to new format */
|
|
155
167
|
private static migrateSettings;
|
|
156
168
|
getGlobalSettings(): Settings;
|
|
@@ -158,6 +170,7 @@ export declare class SettingsManager {
|
|
|
158
170
|
/** Get merged effective settings (project overrides global). */
|
|
159
171
|
getSettings(): Settings;
|
|
160
172
|
reload(): void;
|
|
173
|
+
reloadAsync(): Promise<void>;
|
|
161
174
|
/** Apply additional overrides on top of current settings */
|
|
162
175
|
applyOverrides(overrides: Partial<Settings>): void;
|
|
163
176
|
/** Mark a global field as modified during this session */
|
|
@@ -167,10 +180,14 @@ export declare class SettingsManager {
|
|
|
167
180
|
private recordError;
|
|
168
181
|
private clearModifiedScope;
|
|
169
182
|
private enqueueWrite;
|
|
183
|
+
private enqueueWriteAsync;
|
|
170
184
|
private cloneModifiedNestedFields;
|
|
171
185
|
private persistScopedSettings;
|
|
186
|
+
private persistScopedSettingsAsync;
|
|
172
187
|
private save;
|
|
188
|
+
private saveAsync;
|
|
173
189
|
private saveProjectSettings;
|
|
190
|
+
private saveProjectSettingsAsync;
|
|
174
191
|
flush(): Promise<void>;
|
|
175
192
|
drainErrors(): SettingsError[];
|
|
176
193
|
getLastChangelogVersion(): string | undefined;
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
2
2
|
import { dirname, join } from "path";
|
|
3
3
|
import lockfile from "proper-lockfile";
|
|
4
4
|
import { APP_NAME, CONFIG_DIR_NAME, getAgentDir } from "../../config.js";
|
|
5
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
5
6
|
/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */
|
|
6
7
|
function deepMergeSettings(base, overrides) {
|
|
7
8
|
const result = { ...base };
|
|
@@ -63,6 +64,42 @@ export class FileSettingsStorage {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
async withLockAsync(scope, fn) {
|
|
68
|
+
const filePath = scope === "global" ? this.globalSettingsPath : this.projectSettingsPath;
|
|
69
|
+
const dir = dirname(filePath);
|
|
70
|
+
let release;
|
|
71
|
+
try {
|
|
72
|
+
const fileExists = existsSync(filePath);
|
|
73
|
+
if (fileExists) {
|
|
74
|
+
release = lockfile.lockSync(filePath, { realpath: false });
|
|
75
|
+
}
|
|
76
|
+
// Read inside the lock so we capture a consistent snapshot before writing.
|
|
77
|
+
const current = fileExists ? await readFile(filePath, "utf-8") : undefined;
|
|
78
|
+
const next = await fn(current);
|
|
79
|
+
if (next !== undefined) {
|
|
80
|
+
if (!existsSync(dir)) {
|
|
81
|
+
await mkdir(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
// Ensure we hold the lock for the write even if we only read above.
|
|
84
|
+
if (!release) {
|
|
85
|
+
release = lockfile.lockSync(filePath, { realpath: false });
|
|
86
|
+
}
|
|
87
|
+
await writeFile(filePath, next, "utf-8");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
if (release) {
|
|
92
|
+
release();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async readStorageAsync(scope) {
|
|
97
|
+
const filePath = scope === "global" ? this.globalSettingsPath : this.projectSettingsPath;
|
|
98
|
+
if (!existsSync(filePath)) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
return readFile(filePath, "utf-8");
|
|
102
|
+
}
|
|
66
103
|
}
|
|
67
104
|
export class InMemorySettingsStorage {
|
|
68
105
|
global;
|
|
@@ -79,6 +116,21 @@ export class InMemorySettingsStorage {
|
|
|
79
116
|
}
|
|
80
117
|
}
|
|
81
118
|
}
|
|
119
|
+
async withLockAsync(scope, fn) {
|
|
120
|
+
const current = scope === "global" ? this.global : this.project;
|
|
121
|
+
const next = await fn(current);
|
|
122
|
+
if (next !== undefined) {
|
|
123
|
+
if (scope === "global") {
|
|
124
|
+
this.global = next;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
this.project = next;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async readStorageAsync(scope) {
|
|
132
|
+
return scope === "global" ? this.global : this.project;
|
|
133
|
+
}
|
|
82
134
|
}
|
|
83
135
|
export class SettingsManager {
|
|
84
136
|
storage;
|
|
@@ -107,6 +159,11 @@ export class SettingsManager {
|
|
|
107
159
|
const storage = new FileSettingsStorage(cwd, agentDir);
|
|
108
160
|
return SettingsManager.fromStorage(storage);
|
|
109
161
|
}
|
|
162
|
+
/** Create a SettingsManager that loads from files async */
|
|
163
|
+
static async createAsync(cwd = process.cwd(), agentDir = getAgentDir()) {
|
|
164
|
+
const storage = new FileSettingsStorage(cwd, agentDir);
|
|
165
|
+
return SettingsManager.fromStorageAsync(storage);
|
|
166
|
+
}
|
|
110
167
|
/** Create a SettingsManager from an arbitrary storage backend */
|
|
111
168
|
static fromStorage(storage) {
|
|
112
169
|
const globalLoad = SettingsManager.tryLoadFromStorage(storage, "global");
|
|
@@ -120,6 +177,21 @@ export class SettingsManager {
|
|
|
120
177
|
}
|
|
121
178
|
return new SettingsManager(storage, globalLoad.settings, projectLoad.settings, globalLoad.error, projectLoad.error, initialErrors);
|
|
122
179
|
}
|
|
180
|
+
/** Create a SettingsManager from an arbitrary storage backend async */
|
|
181
|
+
static async fromStorageAsync(storage) {
|
|
182
|
+
const [globalLoadResult, projectLoadResult] = await Promise.all([
|
|
183
|
+
SettingsManager.tryLoadFromStorageAsync(storage, "global"),
|
|
184
|
+
SettingsManager.tryLoadFromStorageAsync(storage, "project"),
|
|
185
|
+
]);
|
|
186
|
+
const initialErrors = [];
|
|
187
|
+
if (globalLoadResult.error) {
|
|
188
|
+
initialErrors.push({ scope: "global", error: globalLoadResult.error });
|
|
189
|
+
}
|
|
190
|
+
if (projectLoadResult.error) {
|
|
191
|
+
initialErrors.push({ scope: "project", error: projectLoadResult.error });
|
|
192
|
+
}
|
|
193
|
+
return new SettingsManager(storage, globalLoadResult.settings, projectLoadResult.settings, globalLoadResult.error, projectLoadResult.error, initialErrors);
|
|
194
|
+
}
|
|
123
195
|
/** Create an in-memory SettingsManager (no file I/O) */
|
|
124
196
|
static inMemory(settings = {}) {
|
|
125
197
|
const storage = new InMemorySettingsStorage();
|
|
@@ -137,6 +209,14 @@ export class SettingsManager {
|
|
|
137
209
|
const settings = JSON.parse(content);
|
|
138
210
|
return SettingsManager.migrateSettings(settings);
|
|
139
211
|
}
|
|
212
|
+
static async loadFromStorageAsync(storage, scope) {
|
|
213
|
+
const content = await storage.readStorageAsync(scope);
|
|
214
|
+
if (!content) {
|
|
215
|
+
return {};
|
|
216
|
+
}
|
|
217
|
+
const settings = JSON.parse(content);
|
|
218
|
+
return SettingsManager.migrateSettings(settings);
|
|
219
|
+
}
|
|
140
220
|
static tryLoadFromStorage(storage, scope) {
|
|
141
221
|
try {
|
|
142
222
|
return { settings: SettingsManager.loadFromStorage(storage, scope), error: null };
|
|
@@ -145,6 +225,14 @@ export class SettingsManager {
|
|
|
145
225
|
return { settings: {}, error: error };
|
|
146
226
|
}
|
|
147
227
|
}
|
|
228
|
+
static async tryLoadFromStorageAsync(storage, scope) {
|
|
229
|
+
try {
|
|
230
|
+
return { settings: await SettingsManager.loadFromStorageAsync(storage, scope), error: null };
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
return { settings: {}, error: error };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
148
236
|
/** Migrate old settings format to new format */
|
|
149
237
|
static migrateSettings(settings) {
|
|
150
238
|
// Migrate queueMode -> steeringMode
|
|
@@ -210,6 +298,33 @@ export class SettingsManager {
|
|
|
210
298
|
}
|
|
211
299
|
this.settings = deepMergeSettings(this.globalSettings, this.projectSettings);
|
|
212
300
|
}
|
|
301
|
+
async reloadAsync() {
|
|
302
|
+
const [globalLoadResult, projectLoadResult] = await Promise.all([
|
|
303
|
+
SettingsManager.tryLoadFromStorageAsync(this.storage, "global"),
|
|
304
|
+
SettingsManager.tryLoadFromStorageAsync(this.storage, "project"),
|
|
305
|
+
]);
|
|
306
|
+
if (!globalLoadResult.error) {
|
|
307
|
+
this.globalSettings = globalLoadResult.settings;
|
|
308
|
+
this.globalSettingsLoadError = null;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
this.globalSettingsLoadError = globalLoadResult.error;
|
|
312
|
+
this.recordError("global", globalLoadResult.error);
|
|
313
|
+
}
|
|
314
|
+
this.modifiedFields.clear();
|
|
315
|
+
this.modifiedNestedFields.clear();
|
|
316
|
+
this.modifiedProjectFields.clear();
|
|
317
|
+
this.modifiedProjectNestedFields.clear();
|
|
318
|
+
if (!projectLoadResult.error) {
|
|
319
|
+
this.projectSettings = projectLoadResult.settings;
|
|
320
|
+
this.projectSettingsLoadError = null;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
this.projectSettingsLoadError = projectLoadResult.error;
|
|
324
|
+
this.recordError("project", projectLoadResult.error);
|
|
325
|
+
}
|
|
326
|
+
this.settings = deepMergeSettings(this.globalSettings, this.projectSettings);
|
|
327
|
+
}
|
|
213
328
|
/** Apply additional overrides on top of current settings */
|
|
214
329
|
applyOverrides(overrides) {
|
|
215
330
|
this.settings = deepMergeSettings(this.settings, overrides);
|
|
@@ -257,6 +372,17 @@ export class SettingsManager {
|
|
|
257
372
|
this.recordError(scope, error);
|
|
258
373
|
});
|
|
259
374
|
}
|
|
375
|
+
enqueueWriteAsync(scope, task) {
|
|
376
|
+
this.writeQueue = this.writeQueue
|
|
377
|
+
.then(async () => {
|
|
378
|
+
await task();
|
|
379
|
+
this.clearModifiedScope(scope);
|
|
380
|
+
})
|
|
381
|
+
.catch((error) => {
|
|
382
|
+
this.recordError(scope, error);
|
|
383
|
+
});
|
|
384
|
+
return this.writeQueue;
|
|
385
|
+
}
|
|
260
386
|
cloneModifiedNestedFields(source) {
|
|
261
387
|
const snapshot = new Map();
|
|
262
388
|
for (const [key, value] of source.entries()) {
|
|
@@ -289,6 +415,31 @@ export class SettingsManager {
|
|
|
289
415
|
return JSON.stringify(mergedSettings, null, 2);
|
|
290
416
|
});
|
|
291
417
|
}
|
|
418
|
+
async persistScopedSettingsAsync(scope, snapshotSettings, modifiedFields, modifiedNestedFields) {
|
|
419
|
+
await this.storage.withLockAsync(scope, async (current) => {
|
|
420
|
+
const currentFileSettings = current
|
|
421
|
+
? SettingsManager.migrateSettings(JSON.parse(current))
|
|
422
|
+
: {};
|
|
423
|
+
const mergedSettings = { ...currentFileSettings };
|
|
424
|
+
for (const field of modifiedFields) {
|
|
425
|
+
const value = snapshotSettings[field];
|
|
426
|
+
if (modifiedNestedFields.has(field) && typeof value === "object" && value !== null) {
|
|
427
|
+
const nestedModified = modifiedNestedFields.get(field);
|
|
428
|
+
const baseNested = currentFileSettings[field] ?? {};
|
|
429
|
+
const inMemoryNested = value;
|
|
430
|
+
const mergedNested = { ...baseNested };
|
|
431
|
+
for (const nestedKey of nestedModified) {
|
|
432
|
+
mergedNested[nestedKey] = inMemoryNested[nestedKey];
|
|
433
|
+
}
|
|
434
|
+
mergedSettings[field] = mergedNested;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
mergedSettings[field] = value;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return JSON.stringify(mergedSettings, null, 2);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
292
443
|
save() {
|
|
293
444
|
this.settings = deepMergeSettings(this.globalSettings, this.projectSettings);
|
|
294
445
|
if (this.globalSettingsLoadError) {
|
|
@@ -301,6 +452,18 @@ export class SettingsManager {
|
|
|
301
452
|
this.persistScopedSettings("global", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);
|
|
302
453
|
});
|
|
303
454
|
}
|
|
455
|
+
async saveAsync() {
|
|
456
|
+
this.settings = deepMergeSettings(this.globalSettings, this.projectSettings);
|
|
457
|
+
if (this.globalSettingsLoadError) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const snapshotGlobalSettings = structuredClone(this.globalSettings);
|
|
461
|
+
const modifiedFields = new Set(this.modifiedFields);
|
|
462
|
+
const modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedNestedFields);
|
|
463
|
+
await this.enqueueWriteAsync("global", async () => {
|
|
464
|
+
await this.persistScopedSettingsAsync("global", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
304
467
|
saveProjectSettings(settings) {
|
|
305
468
|
this.projectSettings = structuredClone(settings);
|
|
306
469
|
this.settings = deepMergeSettings(this.globalSettings, this.projectSettings);
|
|
@@ -314,6 +477,19 @@ export class SettingsManager {
|
|
|
314
477
|
this.persistScopedSettings("project", snapshotProjectSettings, modifiedFields, modifiedNestedFields);
|
|
315
478
|
});
|
|
316
479
|
}
|
|
480
|
+
async saveProjectSettingsAsync(settings) {
|
|
481
|
+
this.projectSettings = structuredClone(settings);
|
|
482
|
+
this.settings = deepMergeSettings(this.globalSettings, this.projectSettings);
|
|
483
|
+
if (this.projectSettingsLoadError) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const snapshotProjectSettings = structuredClone(this.projectSettings);
|
|
487
|
+
const modifiedFields = new Set(this.modifiedProjectFields);
|
|
488
|
+
const modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedProjectNestedFields);
|
|
489
|
+
await this.enqueueWriteAsync("project", async () => {
|
|
490
|
+
await this.persistScopedSettingsAsync("project", snapshotProjectSettings, modifiedFields, modifiedNestedFields);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
317
493
|
async flush() {
|
|
318
494
|
await this.writeQueue;
|
|
319
495
|
}
|
|
@@ -260,10 +260,17 @@ export async function loadExtensions(paths, cwd, eventBus) {
|
|
|
260
260
|
const errors = [];
|
|
261
261
|
const resolvedEventBus = eventBus ?? createEventBus();
|
|
262
262
|
const runtime = createExtensionRuntime();
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
// Parallel loading for faster startup.
|
|
264
|
+
// Note: unlike the old sequential loop, this collects all errors before surfacing
|
|
265
|
+
// them — a crashing extension no longer aborts early. This is intentional: we want
|
|
266
|
+
// all extensions to attempt loading so error reports are comprehensive, not truncated.
|
|
267
|
+
const results = await Promise.all(paths.map((extPath) => loadExtension(extPath, cwd, resolvedEventBus, runtime).then((result) => ({
|
|
268
|
+
...result,
|
|
269
|
+
path: extPath,
|
|
270
|
+
}))));
|
|
271
|
+
for (const { extension, error, path } of results) {
|
|
265
272
|
if (error) {
|
|
266
|
-
errors.push({ path
|
|
273
|
+
errors.push({ path, error });
|
|
267
274
|
continue;
|
|
268
275
|
}
|
|
269
276
|
if (extension) {
|
|
@@ -174,13 +174,24 @@ export class MCPClient {
|
|
|
174
174
|
}
|
|
175
175
|
async spawnProcess(spec, env, cwd) {
|
|
176
176
|
return await new Promise((resolve, reject) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
// On Windows, use shell mode but pass command as a single string to avoid DEP0190 warning
|
|
178
|
+
// On Unix, use non-shell mode for better security
|
|
179
|
+
const useShell = process.platform === "win32";
|
|
180
|
+
const child = useShell
|
|
181
|
+
? spawn([spec.command, ...spec.args].map(arg => arg.includes(" ") ? `"${arg}"` : arg).join(" "), {
|
|
182
|
+
env,
|
|
183
|
+
cwd,
|
|
184
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
185
|
+
windowsHide: true,
|
|
186
|
+
shell: true,
|
|
187
|
+
})
|
|
188
|
+
: spawn(spec.command, spec.args, {
|
|
189
|
+
env,
|
|
190
|
+
cwd,
|
|
191
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
192
|
+
windowsHide: true,
|
|
193
|
+
shell: false,
|
|
194
|
+
});
|
|
184
195
|
const onError = (err) => {
|
|
185
196
|
child.removeListener("spawn", onSpawn);
|
|
186
197
|
reject(err);
|
package/dist/core/messages.js
CHANGED
|
@@ -9,7 +9,7 @@ export const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch tha
|
|
|
9
9
|
<summary>
|
|
10
10
|
`;
|
|
11
11
|
export const BRANCH_SUMMARY_SUFFIX = `</summary>`;
|
|
12
|
-
export const CUSTOM_MESSAGE_TYPES_EXCLUDED_FROM_CONTEXT = new Set(["presence"]);
|
|
12
|
+
export const CUSTOM_MESSAGE_TYPES_EXCLUDED_FROM_CONTEXT = new Set(["presence", "btw"]);
|
|
13
13
|
/**
|
|
14
14
|
* Convert a BashExecutionMessage to user message text for LLM context.
|
|
15
15
|
*/
|
|
@@ -93,8 +93,9 @@ export declare class DefaultPackageManager implements PackageManager {
|
|
|
93
93
|
private parseSource;
|
|
94
94
|
/**
|
|
95
95
|
* Check if an npm package needs to be updated.
|
|
96
|
-
* - For unpinned packages: check if registry has a newer version
|
|
97
96
|
* - For pinned packages: check if installed version matches the pinned version
|
|
97
|
+
* - For unpinned packages: skip registry check at startup (returns false); registry
|
|
98
|
+
* check is deferred to explicit /update or auto-update runs to avoid startup latency
|
|
98
99
|
*/
|
|
99
100
|
private npmNeedsUpdate;
|
|
100
101
|
private getInstalledNpmVersion;
|
|
@@ -129,8 +129,8 @@ function collectFiles(dir, filePattern, skipNodeModules = true, ignoreMatcher, r
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
catch {
|
|
133
|
-
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error("[package-manager] collectFiles failed:", err);
|
|
134
134
|
}
|
|
135
135
|
return files;
|
|
136
136
|
}
|
|
@@ -177,8 +177,8 @@ function collectSkillEntries(dir, includeRootFiles = true, ignoreMatcher, rootDi
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
catch {
|
|
181
|
-
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error("[package-manager] collectSkillEntries failed:", err);
|
|
182
182
|
}
|
|
183
183
|
return entries;
|
|
184
184
|
}
|
|
@@ -247,8 +247,8 @@ function collectAutoPromptEntries(dir) {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
|
-
catch {
|
|
251
|
-
|
|
250
|
+
catch (err) {
|
|
251
|
+
console.error("[package-manager] collectAutoPromptEntries failed:", err);
|
|
252
252
|
}
|
|
253
253
|
return entries;
|
|
254
254
|
}
|
|
@@ -283,8 +283,8 @@ function collectAutoThemeEntries(dir) {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
-
catch {
|
|
287
|
-
|
|
286
|
+
catch (err) {
|
|
287
|
+
console.error("[package-manager] collectAutoThemeEntries failed:", err);
|
|
288
288
|
}
|
|
289
289
|
return entries;
|
|
290
290
|
}
|
|
@@ -372,8 +372,8 @@ function collectAutoExtensionEntries(dir) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
-
catch {
|
|
376
|
-
|
|
375
|
+
catch (err) {
|
|
376
|
+
console.error("[package-manager] collectAutoExtensionEntries failed:", err);
|
|
377
377
|
}
|
|
378
378
|
return entries;
|
|
379
379
|
}
|
|
@@ -862,8 +862,9 @@ export class DefaultPackageManager {
|
|
|
862
862
|
}
|
|
863
863
|
/**
|
|
864
864
|
* Check if an npm package needs to be updated.
|
|
865
|
-
* - For unpinned packages: check if registry has a newer version
|
|
866
865
|
* - For pinned packages: check if installed version matches the pinned version
|
|
866
|
+
* - For unpinned packages: skip registry check at startup (returns false); registry
|
|
867
|
+
* check is deferred to explicit /update or auto-update runs to avoid startup latency
|
|
867
868
|
*/
|
|
868
869
|
async npmNeedsUpdate(source, installedPath) {
|
|
869
870
|
if (isOfflineModeEnabled()) {
|
|
@@ -877,15 +878,9 @@ export class DefaultPackageManager {
|
|
|
877
878
|
// Pinned: check if installed matches pinned (exact match for now)
|
|
878
879
|
return installedVersion !== pinnedVersion;
|
|
879
880
|
}
|
|
880
|
-
//
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
return latestVersion !== installedVersion;
|
|
884
|
-
}
|
|
885
|
-
catch {
|
|
886
|
-
// If we can't check registry, assume it's fine
|
|
887
|
-
return false;
|
|
888
|
-
}
|
|
881
|
+
// For unpinned packages on startup, skip registry check - assume installed version is fine
|
|
882
|
+
// Registry check will happen only when user explicitly runs /update or auto-update triggers
|
|
883
|
+
return false;
|
|
889
884
|
}
|
|
890
885
|
getInstalledNpmVersion(installedPath) {
|
|
891
886
|
const packageJsonPath = join(installedPath, "package.json");
|
|
@@ -1372,8 +1367,8 @@ export class DefaultPackageManager {
|
|
|
1372
1367
|
files.push(...collectResourceFiles(p, resourceType));
|
|
1373
1368
|
}
|
|
1374
1369
|
}
|
|
1375
|
-
catch {
|
|
1376
|
-
|
|
1370
|
+
catch (err) {
|
|
1371
|
+
console.error("[package-manager] collectFilesFromPaths failed:", err);
|
|
1377
1372
|
}
|
|
1378
1373
|
}
|
|
1379
1374
|
return files;
|
|
@@ -339,7 +339,9 @@ export class AgentSession {
|
|
|
339
339
|
if (event.type === "message_update") {
|
|
340
340
|
// Streaming updates: emit to UI immediately, don't await extensions
|
|
341
341
|
this._emit(event);
|
|
342
|
-
this._emitExtensionEvent(event).catch(() => {
|
|
342
|
+
this._emitExtensionEvent(event).catch((err) => {
|
|
343
|
+
this._logger.error("[extension] message_update event error", { error: err });
|
|
344
|
+
});
|
|
343
345
|
}
|
|
344
346
|
else {
|
|
345
347
|
// All other events: extensions run concurrently with UI notification
|
|
@@ -394,8 +396,9 @@ export class AgentSession {
|
|
|
394
396
|
await this._soulManager.recordInteraction(context, outcome, "turn");
|
|
395
397
|
await this._soulManager.updateExpertise(expertiseDomain, tags, outcome === "success");
|
|
396
398
|
}
|
|
397
|
-
catch {
|
|
399
|
+
catch (err) {
|
|
398
400
|
// Keep Soul failures non-blocking for the main session lifecycle.
|
|
401
|
+
this._logger.warn("[soul] recordInteraction/updateExpertise failed", { error: err });
|
|
399
402
|
}
|
|
400
403
|
})();
|
|
401
404
|
}
|
|
@@ -408,7 +411,9 @@ export class AgentSession {
|
|
|
408
411
|
type: "agent_end",
|
|
409
412
|
messages: event.messages,
|
|
410
413
|
})
|
|
411
|
-
.catch(() => {
|
|
414
|
+
.catch((err) => {
|
|
415
|
+
this._logger.error("[extension] agent_end event error", { error: err });
|
|
416
|
+
});
|
|
412
417
|
}
|
|
413
418
|
};
|
|
414
419
|
/** Extract text content from a message */
|
|
@@ -17,16 +17,21 @@ security-audit/engine/interceptor.ts: Request/response interception, Interceptor
|
|
|
17
17
|
security-audit/engine/logger.ts: Security event logging, JSON file audit trail
|
|
18
18
|
security-audit/engine/detector.ts: Vulnerability detection, pattern matching for dangerous commands
|
|
19
19
|
soul/index.ts: AI personality evolution extension, persistent personality across sessions
|
|
20
|
-
grub/index.ts: Grub extension entry - autonomous
|
|
21
|
-
grub/grub-controller.ts: GrubController -
|
|
22
|
-
grub/grub-parser.ts: Grub command parsing
|
|
23
|
-
grub/grub-types.ts: Grub types
|
|
24
|
-
grub/
|
|
20
|
+
grub/index.ts: Grub extension entry - long-running autonomous harness, dual-phase system prompts (initializer/coding), /grub command (start/status/resume/stop) + grub renderer, session_start auto-adopt, git harness commit, pruneStale cleanup
|
|
21
|
+
grub/grub-controller.ts: GrubController - state machine for /grub iterations, durable persistState on every transition, adoptResumedTask for cross-session resume, validateCompletion downgrades premature complete when feature-list still has pending entries
|
|
22
|
+
grub/grub-parser.ts: Grub command parsing - parseGrubCommand/buildGrubHelp with resume subcommand, status --json, --max-iter/--max-fail flags
|
|
23
|
+
grub/grub-types.ts: Grub types - GrubStatus/GrubDecisionStatus/GrubDecision/GrubPhase/GrubTaskState/GrubTaskSnapshot/ParsedGrubCommand + FeatureItem/FeatureList (version 1 schema) + PersistedGrubState envelope
|
|
24
|
+
grub/grub-feature-list.ts: feature-list.json IO - readFeatureList/writeFeatureList atomic write, validateFeatureListDiff enforces passes/evidence-only mutations, createInitialFeatureList placeholder, migrateChecklistToFeatureList legacy converter, countPassing/allPassing/firstPending helpers
|
|
25
|
+
grub/grub-persistence.ts: Cross-session persistence - persistState atomic JSON write to .grub/<id>/state.json, loadState shape-validated read, discoverActiveTasks scans .grub/ for running records, pruneStale removes terminal harnesses older than 30 days by default
|
|
26
|
+
grub/README.md: Grub extension documentation - long-running harness contract, feature-list.json schema, completion guard, cross-session resume, legacy migration
|
|
25
27
|
loop/index.ts: Loop extension entry - session-scoped recurring prompt/command scheduler with pause/resume/run-now/max-runs/quiet, /loop command + LOOP_MESSAGE_TYPE renderer
|
|
26
28
|
loop/scheduler-controller.ts: SchedulerController - in-memory recurring task store with pause/resume/run-now/max-runs, MAX_SCHEDULED_TASKS=50
|
|
27
29
|
loop/scheduler-parser.ts: Loop command parsing with flags/subcommands, parseSchedulerCommand/parseDurationSpec/buildSchedulerHelp, --name/--max/--quiet
|
|
28
30
|
loop/scheduler-types.ts: Scheduled loop types, LoopPayloadKind/ScheduledLoopTask/LoopStartSpec/ParsedSchedulerCommand
|
|
29
31
|
loop/README.md: Loop extension documentation - recurring scheduler usage and flags
|
|
32
|
+
btw/index.ts: BTW extension entry - /btw command for quick side questions without interrupting main task, uses completeSimple() for lightweight response, BTW_MESSAGE_TYPE renderer
|
|
33
|
+
debug/index.ts: Debug extension entry - /debug command for system diagnostics with three-layer analysis (Phenomenon/Essence/Philosophy), supports /debug env|session|model subcommands, uses completeSimple() for LLM analysis, DEBUG_MESSAGE_TYPE renderer
|
|
34
|
+
debug/collectors.ts: Diagnostic data collectors for /debug command, collectSystemInfo/collectModelInfo/collectSessionInfo/collectConfigInfo/collectGitInfo/collectAgentState, sanitizeForLLM, formatDiagnosticData
|
|
30
35
|
plan/index.ts: Plan Mode extension entry - registers /plan command, EnterPlanMode/ExitPlanMode tools, permission gating, workflow prompt injection
|
|
31
36
|
plan/types.ts: PlanModeState, PlanModeAttachment types, PlanModeConfig, PlanApprovalRequest/Response
|
|
32
37
|
plan/plan-file-manager.ts: PlanFileManager - plan file path management and I/O, slug generation, plans directory
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: btwExtension - registers /btw command and BTW message renderer
|
|
3
|
+
* [FROM]: Depends on core/extensions/types
|
|
4
|
+
* [TO]: Auto-loaded by builtin-extensions.ts as a default extension
|
|
5
|
+
* [HERE]: extensions/defaults/btw/index.ts - quick side question without interrupting main task
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtensionAPI } from "../../../core/extensions/types.js";
|
|
8
|
+
export default function btwExtension(api: ExtensionAPI): Promise<void>;
|