@diffexai/diffex 0.2.2 → 0.2.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/AGENTS.md +28 -0
  3. package/dist/cli/session-selection.d.ts +7 -0
  4. package/dist/cli/session-selection.js +181 -0
  5. package/dist/core/extensions/factory.d.ts +21 -0
  6. package/dist/core/extensions/factory.js +254 -0
  7. package/dist/core/extensions/loader.d.ts +2 -10
  8. package/dist/core/extensions/loader.js +2 -251
  9. package/dist/core/resource-loader.js +7 -2
  10. package/dist/core/tools/subagents.js +1 -0
  11. package/dist/main.js +3 -176
  12. package/dist/modes/interactive/interactive-mode.js +2 -19
  13. package/dist/node_modules/@diffexai/diffex-agent-core/distribution-components.json +4 -4
  14. package/dist/node_modules/@diffexai/diffex-agent-core/distribution-files.json +1 -1
  15. package/dist/node_modules/@diffexai/diffex-agent-core/package.json +1 -1
  16. package/dist/node_modules/@diffexai/diffex-ai/dist/image-models.generated.d.ts +180 -0
  17. package/dist/node_modules/@diffexai/diffex-ai/dist/image-models.generated.js +182 -2
  18. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/.manifest.json +1 -1
  19. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/kimi-coding.json +1 -1
  20. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/nvidia.json +1 -1
  21. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/openrouter.json +1 -1
  22. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  23. package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  24. package/dist/node_modules/@diffexai/diffex-ai/distribution-components.json +3 -3
  25. package/dist/node_modules/@diffexai/diffex-ai/distribution-files.json +9 -9
  26. package/dist/node_modules/@diffexai/diffex-ai/package.json +1 -1
  27. package/dist/node_modules/@diffexai/diffex-client/distribution-components.json +3 -3
  28. package/dist/node_modules/@diffexai/diffex-client/distribution-files.json +1 -1
  29. package/dist/node_modules/@diffexai/diffex-client/package.json +1 -1
  30. package/dist/node_modules/@diffexai/diffex-harness-state/distribution-components.json +2 -2
  31. package/dist/node_modules/@diffexai/diffex-harness-state/distribution-files.json +1 -1
  32. package/dist/node_modules/@diffexai/diffex-harness-state/package.json +1 -1
  33. package/dist/node_modules/@diffexai/diffex-protocol/distribution-components.json +2 -2
  34. package/dist/node_modules/@diffexai/diffex-protocol/distribution-files.json +1 -1
  35. package/dist/node_modules/@diffexai/diffex-protocol/package.json +1 -1
  36. package/dist/node_modules/@diffexai/diffex-telemetry/distribution-components.json +2 -2
  37. package/dist/node_modules/@diffexai/diffex-telemetry/distribution-files.json +1 -1
  38. package/dist/node_modules/@diffexai/diffex-telemetry/package.json +1 -1
  39. package/dist/node_modules/@diffexai/diffex-tui/distribution-components.json +2 -2
  40. package/dist/node_modules/@diffexai/diffex-tui/distribution-files.json +1 -1
  41. package/dist/node_modules/@diffexai/diffex-tui/package.json +1 -1
  42. package/distribution-components.json +11 -11
  43. package/distribution-files.json +56 -36
  44. package/docs/usage.md +10 -4
  45. package/npm-shrinkwrap.json +2 -2
  46. package/package.json +1 -1
  47. package/release/distribution-manifest.json +4 -4
  48. package/release/install-package-lock.json +5 -5
  49. package/release/install-package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.4] - 2026-09-15
4
+
5
+ ## [0.2.3] - 2026-09-14
6
+
7
+ ### New Features
8
+
9
+ - Added a streamlined interactive transcript and compact dialogs for clearer conversation and activity states ([#166](https://github.com/diffexai/diffex/pull/166) by [@jefft72](https://github.com/jefft72)).
10
+ - Added an animated Diffex welcome sequence at interactive startup ([#167](https://github.com/diffexai/diffex/pull/167) by [@jefft72](https://github.com/jefft72)).
11
+ - Added a responsive interactive layout with adaptive editor and footer sizing, compact queued-message summaries, and a recoverable small-terminal view. See [Interactive Mode](docs/usage.md#interactive-mode) ([#168](https://github.com/diffexai/diffex/pull/168) by [@jefft72](https://github.com/jefft72)).
12
+
13
+ ### Fixed
14
+
15
+ - Fixed startup changelog previews to show only entries not previously seen.
16
+
3
17
  ## [0.2.2] - 2026-09-14
4
18
 
5
19
  ### Breaking Changes
package/dist/AGENTS.md ADDED
@@ -0,0 +1,28 @@
1
+ # Development Rules
2
+
3
+ ## Conversational Style
4
+
5
+ - Keep responses concise and direct, adapting detail and terminology to the user.
6
+ - No emojis in commits, issues, PR comments, or code.
7
+ - No fluff or cheerful filler text (e.g., "Thanks @user" not "Thanks so much @user!").
8
+ - When the user asks a question, answer it first before making edits or running implementation commands.
9
+ - Address the user's feedback directly before describing changes.
10
+
11
+ ## Code Quality
12
+
13
+ - Follow repository instructions, conventions, and existing tooling.
14
+ - Before editing or drawing conclusions, read enough surrounding code to understand the relevant behavior and dependencies.
15
+ - Read affected files in full for broad changes; do not rely on search snippets alone for investigations or audits.
16
+ - Ask before removing intentional functionality or code when the removal falls outside the requested scope or its purpose is unclear.
17
+ - Preserve unrelated local changes and keep modifications focused on the requested task.
18
+
19
+ ## Commands
20
+
21
+ - After changes, run relevant tests and checks, including any tests you create or modify.
22
+ - Resolve failures caused by your changes and report any remaining failures or checks you could not run.
23
+ - Write substantial or multiline ad-hoc scripts to a temporary file, run and revise them as needed, and remove your temporary files when done.
24
+ - Simple one-line scripts may run directly in the shell.
25
+
26
+ ## User Override
27
+
28
+ - Explicit user instructions take precedence over these defaults.
@@ -0,0 +1,7 @@
1
+ import { SessionManager } from "../core/session-manager.ts";
2
+ import type { SettingsManager } from "../core/settings-manager.ts";
3
+ import type { Args } from "./args.ts";
4
+ export declare function validateForkFlags(parsed: Args): void;
5
+ export declare function validateSessionIdFlags(parsed: Args): void;
6
+ export declare function createSessionManager(parsed: Args, cwd: string, sessionDir: string | undefined, settingsManager: SettingsManager): Promise<SessionManager>;
7
+ export declare function applySessionName(parsed: Args, sessionManager: SessionManager): void;
@@ -0,0 +1,181 @@
1
+ import { createInterface } from "node:readline";
2
+ import chalk from "chalk";
3
+ import { assertValidSessionId, SessionManager } from "../core/session-manager.js";
4
+ import { stopThemeWatcher } from "../modes/interactive/theme/theme.js";
5
+ import { resolvePath } from "../utils/paths.js";
6
+ import { selectSession } from "./session-picker.js";
7
+ /**
8
+ * Resolve a session argument to a file path.
9
+ * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
10
+ */
11
+ async function findLocalSessionByExactId(sessionId, cwd, sessionDir) {
12
+ const localSessions = await SessionManager.list(cwd, sessionDir);
13
+ const localMatch = localSessions.find((s) => s.id === sessionId);
14
+ return localMatch ? { type: "local", path: localMatch.path } : undefined;
15
+ }
16
+ async function resolveSessionPath(sessionArg, cwd, sessionDir) {
17
+ // If it looks like a file path, resolve it before handing it to the session manager.
18
+ if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
19
+ return { type: "path", path: resolvePath(sessionArg, cwd) };
20
+ }
21
+ // Try to match as session ID in current project first
22
+ const localSessions = await SessionManager.list(cwd, sessionDir);
23
+ const localMatch = localSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));
24
+ if (localMatch) {
25
+ return { type: "local", path: localMatch.path };
26
+ }
27
+ // Try global search across all projects
28
+ const allSessions = await SessionManager.listAll(sessionDir);
29
+ const globalMatch = allSessions.find((s) => s.id === sessionArg) ?? allSessions.find((s) => s.id.startsWith(sessionArg));
30
+ if (globalMatch) {
31
+ return { type: "global", path: globalMatch.path, cwd: globalMatch.cwd };
32
+ }
33
+ // Not found anywhere
34
+ return { type: "not_found", arg: sessionArg };
35
+ }
36
+ /** Prompt user for yes/no confirmation */
37
+ async function promptConfirm(message) {
38
+ return new Promise((resolve) => {
39
+ const rl = createInterface({
40
+ input: process.stdin,
41
+ output: process.stdout,
42
+ });
43
+ rl.question(`${message} [y/N] `, (answer) => {
44
+ rl.close();
45
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
46
+ });
47
+ });
48
+ }
49
+ export function validateForkFlags(parsed) {
50
+ if (!parsed.fork)
51
+ return;
52
+ const conflictingFlags = [
53
+ parsed.session ? "--session" : undefined,
54
+ parsed.continue ? "--continue" : undefined,
55
+ parsed.resume ? "--resume" : undefined,
56
+ parsed.noSession ? "--no-session" : undefined,
57
+ ].filter((flag) => flag !== undefined);
58
+ if (conflictingFlags.length > 0) {
59
+ console.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(", ")}`));
60
+ process.exit(1);
61
+ }
62
+ }
63
+ export function validateSessionIdFlags(parsed) {
64
+ if (parsed.sessionId === undefined)
65
+ return;
66
+ const conflictingFlags = [
67
+ parsed.session ? "--session" : undefined,
68
+ parsed.continue ? "--continue" : undefined,
69
+ parsed.resume ? "--resume" : undefined,
70
+ ].filter((flag) => flag !== undefined);
71
+ if (conflictingFlags.length > 0) {
72
+ console.error(chalk.red(`Error: --session-id cannot be combined with ${conflictingFlags.join(", ")}`));
73
+ process.exit(1);
74
+ }
75
+ try {
76
+ assertValidSessionId(parsed.sessionId);
77
+ }
78
+ catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ console.error(chalk.red(`Error: ${message}`));
81
+ process.exit(1);
82
+ }
83
+ }
84
+ function openSessionOrExit(path, sessionDir) {
85
+ try {
86
+ return SessionManager.open(path, sessionDir);
87
+ }
88
+ catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ console.error(chalk.red(`Error: ${message}`));
91
+ process.exit(1);
92
+ }
93
+ }
94
+ function forkSessionOrExit(sourcePath, cwd, sessionDir, sessionId) {
95
+ try {
96
+ return SessionManager.forkFrom(sourcePath, cwd, sessionDir, { id: sessionId });
97
+ }
98
+ catch (error) {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ console.error(chalk.red(`Error: ${message}`));
101
+ process.exit(1);
102
+ }
103
+ }
104
+ export async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
105
+ if (parsed.noSession || parsed.help || parsed.listModels !== undefined) {
106
+ return SessionManager.inMemory(cwd, parsed.sessionId !== undefined ? { id: parsed.sessionId } : undefined);
107
+ }
108
+ if (parsed.fork) {
109
+ if (parsed.sessionId) {
110
+ const existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
111
+ if (existingTarget) {
112
+ console.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));
113
+ process.exit(1);
114
+ }
115
+ }
116
+ const resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);
117
+ switch (resolved.type) {
118
+ case "path":
119
+ case "local":
120
+ case "global":
121
+ return forkSessionOrExit(resolved.path, cwd, sessionDir, parsed.sessionId);
122
+ case "not_found":
123
+ console.error(chalk.red(`No session found matching '${resolved.arg}'`));
124
+ process.exit(1);
125
+ }
126
+ }
127
+ if (parsed.session) {
128
+ const resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);
129
+ switch (resolved.type) {
130
+ case "path":
131
+ case "local":
132
+ return openSessionOrExit(resolved.path, sessionDir);
133
+ case "global": {
134
+ console.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));
135
+ const shouldFork = await promptConfirm("Fork this session into current directory?");
136
+ if (!shouldFork) {
137
+ console.log(chalk.dim("Aborted."));
138
+ process.exit(0);
139
+ }
140
+ return forkSessionOrExit(resolved.path, cwd, sessionDir);
141
+ }
142
+ case "not_found":
143
+ console.error(chalk.red(`No session found matching '${resolved.arg}'`));
144
+ process.exit(1);
145
+ }
146
+ }
147
+ if (parsed.resume) {
148
+ try {
149
+ const selectedPath = await selectSession((onProgress) => SessionManager.list(cwd, sessionDir, onProgress), (onProgress) => SessionManager.listAll(sessionDir, onProgress), settingsManager);
150
+ if (!selectedPath) {
151
+ console.log(chalk.dim("No session selected"));
152
+ process.exit(0);
153
+ }
154
+ return SessionManager.open(selectedPath, sessionDir);
155
+ }
156
+ finally {
157
+ stopThemeWatcher();
158
+ }
159
+ }
160
+ if (parsed.continue) {
161
+ return SessionManager.continueRecent(cwd, sessionDir);
162
+ }
163
+ if (parsed.sessionId) {
164
+ const existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
165
+ if (existingSession) {
166
+ return SessionManager.open(existingSession.path, sessionDir);
167
+ }
168
+ console.error(chalk.yellow(`Warning: No project session found with id '${parsed.sessionId}'; creating a new session with that id.`));
169
+ }
170
+ return SessionManager.create(cwd, sessionDir, { id: parsed.sessionId });
171
+ }
172
+ export function applySessionName(parsed, sessionManager) {
173
+ if (parsed.name !== undefined) {
174
+ const name = parsed.name.trim();
175
+ if (!name) {
176
+ console.error(chalk.red("Error: --name requires a non-empty value"));
177
+ process.exit(1);
178
+ }
179
+ sessionManager.appendSessionInfo(name);
180
+ }
181
+ }
@@ -0,0 +1,21 @@
1
+ import type { EventBus } from "../event-bus.ts";
2
+ import type { Extension, ExtensionAPI, ExtensionFactory, ExtensionRuntime } from "./types.ts";
3
+ /**
4
+ * Create a runtime with throwing stubs for action methods.
5
+ * Runner.bindCore() replaces these with real implementations.
6
+ */
7
+ export declare function createExtensionRuntime(): ExtensionRuntime;
8
+ /**
9
+ * Create the ExtensionAPI for an extension.
10
+ * Registration methods write to the extension object.
11
+ * Action methods delegate to the shared runtime.
12
+ */
13
+ export declare function createExtensionAPI(extension: Extension, runtime: ExtensionRuntime, cwd: string, eventBus: EventBus): ExtensionAPI;
14
+ /**
15
+ * Create an Extension object with empty collections.
16
+ */
17
+ export declare function createExtension(extensionPath: string, resolvedPath: string): Extension;
18
+ /**
19
+ * Create an Extension from an inline factory function.
20
+ */
21
+ export declare function loadExtensionFromFactory(factory: ExtensionFactory, cwd: string, eventBus: EventBus, runtime: ExtensionRuntime, extensionPath?: string): Promise<Extension>;
@@ -0,0 +1,254 @@
1
+ import * as path from "node:path";
2
+ import { resolvePath } from "../../utils/paths.js";
3
+ import { execCommand } from "../exec.js";
4
+ import { createSyntheticSourceInfo } from "../source-info.js";
5
+ import { time } from "../timings.js";
6
+ /**
7
+ * Create a runtime with throwing stubs for action methods.
8
+ * Runner.bindCore() replaces these with real implementations.
9
+ */
10
+ export function createExtensionRuntime() {
11
+ const notInitialized = () => {
12
+ throw new Error("Extension runtime not initialized. Action methods cannot be called during extension loading.");
13
+ };
14
+ const state = {};
15
+ const eventBusUnsubscribers = new Set();
16
+ const assertActive = () => {
17
+ if (state.staleMessage) {
18
+ throw new Error(state.staleMessage);
19
+ }
20
+ };
21
+ const runtime = {
22
+ sendMessage: notInitialized,
23
+ sendUserMessage: notInitialized,
24
+ appendEntry: notInitialized,
25
+ setSessionName: notInitialized,
26
+ getSessionName: notInitialized,
27
+ setLabel: notInitialized,
28
+ getActiveTools: notInitialized,
29
+ getAllTools: notInitialized,
30
+ setActiveTools: notInitialized,
31
+ // registerTool() is valid during extension load; refresh is only needed post-bind.
32
+ refreshTools: () => { },
33
+ getCommands: notInitialized,
34
+ setModel: () => Promise.reject(new Error("Extension runtime not initialized")),
35
+ getThinkingLevel: notInitialized,
36
+ setThinkingLevel: notInitialized,
37
+ flagValues: new Map(),
38
+ pendingProviderRegistrations: [],
39
+ pendingNativeProviderRegistrations: [],
40
+ assertActive,
41
+ invalidate: (message) => {
42
+ if (state.staleMessage)
43
+ return;
44
+ state.staleMessage =
45
+ message ??
46
+ "This extension ctx is stale after session replacement or reload. Do not use a captured Diffex API or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().";
47
+ for (const unsubscribe of eventBusUnsubscribers)
48
+ unsubscribe();
49
+ eventBusUnsubscribers.clear();
50
+ },
51
+ trackEventBusSubscription: (unsubscribe) => {
52
+ let active = true;
53
+ const trackedUnsubscribe = () => {
54
+ if (!active)
55
+ return;
56
+ active = false;
57
+ eventBusUnsubscribers.delete(trackedUnsubscribe);
58
+ unsubscribe();
59
+ };
60
+ eventBusUnsubscribers.add(trackedUnsubscribe);
61
+ return trackedUnsubscribe;
62
+ },
63
+ // Pre-bind: queue registrations so bindCore() can flush them once the
64
+ // model registry is available. bindCore() replaces both with direct calls.
65
+ registerProvider: (name, config, extensionPath = "<unknown>") => {
66
+ runtime.pendingProviderRegistrations.push({ name, config, extensionPath });
67
+ },
68
+ registerNativeProvider: (provider, extensionPath = "<unknown>") => {
69
+ runtime.pendingNativeProviderRegistrations.push({ provider, extensionPath });
70
+ },
71
+ unregisterProvider: (name) => {
72
+ runtime.pendingProviderRegistrations = runtime.pendingProviderRegistrations.filter((r) => r.name !== name);
73
+ runtime.pendingNativeProviderRegistrations = runtime.pendingNativeProviderRegistrations.filter((r) => r.provider.id !== name);
74
+ },
75
+ };
76
+ return runtime;
77
+ }
78
+ /**
79
+ * Create the ExtensionAPI for an extension.
80
+ * Registration methods write to the extension object.
81
+ * Action methods delegate to the shared runtime.
82
+ */
83
+ export function createExtensionAPI(extension, runtime, cwd, eventBus) {
84
+ const api = {
85
+ // Registration methods - write to extension
86
+ on(event, handler) {
87
+ runtime.assertActive();
88
+ const list = extension.handlers.get(event) ?? [];
89
+ list.push(handler);
90
+ extension.handlers.set(event, list);
91
+ },
92
+ registerTool(tool) {
93
+ runtime.assertActive();
94
+ extension.tools.set(tool.name, {
95
+ definition: tool,
96
+ sourceInfo: extension.sourceInfo,
97
+ });
98
+ runtime.refreshTools();
99
+ },
100
+ registerCommand(name, options) {
101
+ runtime.assertActive();
102
+ extension.commands.set(name, {
103
+ name,
104
+ sourceInfo: extension.sourceInfo,
105
+ ...options,
106
+ });
107
+ },
108
+ registerShortcut(shortcut, options) {
109
+ runtime.assertActive();
110
+ extension.shortcuts.set(shortcut, { shortcut, extensionPath: extension.path, ...options });
111
+ },
112
+ registerFlag(name, options) {
113
+ runtime.assertActive();
114
+ extension.flags.set(name, { name, extensionPath: extension.path, ...options });
115
+ if (options.default !== undefined && !runtime.flagValues.has(name)) {
116
+ runtime.flagValues.set(name, options.default);
117
+ }
118
+ },
119
+ registerMessageRenderer(customType, renderer) {
120
+ runtime.assertActive();
121
+ extension.messageRenderers.set(customType, renderer);
122
+ },
123
+ registerMarkdownTransformer(transformer) {
124
+ runtime.assertActive();
125
+ extension.markdownTransformer = transformer;
126
+ },
127
+ registerEntryRenderer(customType, renderer) {
128
+ runtime.assertActive();
129
+ extension.entryRenderers ??= new Map();
130
+ extension.entryRenderers.set(customType, renderer);
131
+ },
132
+ // Flag access - checks extension registered it, reads from runtime
133
+ getFlag(name) {
134
+ runtime.assertActive();
135
+ if (!extension.flags.has(name))
136
+ return undefined;
137
+ return runtime.flagValues.get(name);
138
+ },
139
+ // Action methods - delegate to shared runtime
140
+ sendMessage(message, options) {
141
+ runtime.assertActive();
142
+ runtime.sendMessage(message, options);
143
+ },
144
+ sendUserMessage(content, options) {
145
+ runtime.assertActive();
146
+ runtime.sendUserMessage(content, options);
147
+ },
148
+ appendEntry(customType, data) {
149
+ runtime.assertActive();
150
+ runtime.appendEntry(customType, data);
151
+ },
152
+ setSessionName(name) {
153
+ runtime.assertActive();
154
+ runtime.setSessionName(name);
155
+ },
156
+ getSessionName() {
157
+ runtime.assertActive();
158
+ return runtime.getSessionName();
159
+ },
160
+ setLabel(entryId, label) {
161
+ runtime.assertActive();
162
+ runtime.setLabel(entryId, label);
163
+ },
164
+ exec(command, args, options) {
165
+ runtime.assertActive();
166
+ return execCommand(command, args, options?.cwd ?? cwd, options);
167
+ },
168
+ getActiveTools() {
169
+ runtime.assertActive();
170
+ return runtime.getActiveTools();
171
+ },
172
+ getAllTools() {
173
+ runtime.assertActive();
174
+ return runtime.getAllTools();
175
+ },
176
+ setActiveTools(toolNames) {
177
+ runtime.assertActive();
178
+ runtime.setActiveTools(toolNames);
179
+ },
180
+ getCommands() {
181
+ runtime.assertActive();
182
+ return runtime.getCommands();
183
+ },
184
+ setModel(model) {
185
+ runtime.assertActive();
186
+ return runtime.setModel(model);
187
+ },
188
+ getThinkingLevel() {
189
+ runtime.assertActive();
190
+ return runtime.getThinkingLevel();
191
+ },
192
+ setThinkingLevel(level) {
193
+ runtime.assertActive();
194
+ runtime.setThinkingLevel(level);
195
+ },
196
+ registerProvider(providerOrName, config) {
197
+ runtime.assertActive();
198
+ if (typeof providerOrName === "string") {
199
+ if (!config)
200
+ throw new Error("Provider config is required when registering by name");
201
+ runtime.registerProvider(providerOrName, config, extension.path);
202
+ return;
203
+ }
204
+ runtime.registerNativeProvider(providerOrName, extension.path);
205
+ },
206
+ unregisterProvider(name) {
207
+ runtime.assertActive();
208
+ runtime.unregisterProvider(name, extension.path);
209
+ },
210
+ events: {
211
+ emit(channel, data) {
212
+ runtime.assertActive();
213
+ eventBus.emit(channel, data);
214
+ },
215
+ on(channel, handler) {
216
+ runtime.assertActive();
217
+ return runtime.trackEventBusSubscription(eventBus.on(channel, handler));
218
+ },
219
+ },
220
+ };
221
+ return api;
222
+ }
223
+ /**
224
+ * Create an Extension object with empty collections.
225
+ */
226
+ export function createExtension(extensionPath, resolvedPath) {
227
+ const source = extensionPath.startsWith("<") && extensionPath.endsWith(">")
228
+ ? extensionPath.slice(1, -1).split(":")[0] || "temporary"
229
+ : "local";
230
+ const baseDir = extensionPath.startsWith("<") ? undefined : path.dirname(resolvedPath);
231
+ return {
232
+ path: extensionPath,
233
+ resolvedPath,
234
+ sourceInfo: createSyntheticSourceInfo(extensionPath, { source, baseDir }),
235
+ handlers: new Map(),
236
+ tools: new Map(),
237
+ messageRenderers: new Map(),
238
+ entryRenderers: new Map(),
239
+ commands: new Map(),
240
+ flags: new Map(),
241
+ shortcuts: new Map(),
242
+ };
243
+ }
244
+ /**
245
+ * Create an Extension from an inline factory function.
246
+ */
247
+ export async function loadExtensionFromFactory(factory, cwd, eventBus, runtime, extensionPath = "<inline>") {
248
+ const extension = createExtension(extensionPath, extensionPath);
249
+ const resolvedCwd = resolvePath(cwd);
250
+ const api = createExtensionAPI(extension, runtime, resolvedCwd, eventBus);
251
+ await factory(api);
252
+ time(`${extensionPath} factory`, "extensions");
253
+ return extension;
254
+ }
@@ -3,17 +3,9 @@
3
3
  *
4
4
  */
5
5
  import { type EventBus } from "../event-bus.ts";
6
- import type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from "./types.ts";
6
+ import type { ExtensionRuntime, LoadExtensionsResult } from "./types.ts";
7
+ export { createExtensionRuntime, loadExtensionFromFactory } from "./factory.ts";
7
8
  export declare function clearExtensionCache(): void;
8
- /**
9
- * Create a runtime with throwing stubs for action methods.
10
- * Runner.bindCore() replaces these with real implementations.
11
- */
12
- export declare function createExtensionRuntime(): ExtensionRuntime;
13
- /**
14
- * Create an Extension from an inline factory function.
15
- */
16
- export declare function loadExtensionFromFactory(factory: ExtensionFactory, cwd: string, eventBus: EventBus, runtime: ExtensionRuntime, extensionPath?: string): Promise<Extension>;
17
9
  export declare function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus, runtime?: ExtensionRuntime): Promise<LoadExtensionsResult>;
18
10
  export declare function loadExtensionsCached(paths: string[], cwd: string, eventBus?: EventBus, runtime?: ExtensionRuntime): Promise<LoadExtensionsResult>;
19
11
  /**