@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.
- package/CHANGELOG.md +14 -0
- package/dist/AGENTS.md +28 -0
- package/dist/cli/session-selection.d.ts +7 -0
- package/dist/cli/session-selection.js +181 -0
- package/dist/core/extensions/factory.d.ts +21 -0
- package/dist/core/extensions/factory.js +254 -0
- package/dist/core/extensions/loader.d.ts +2 -10
- package/dist/core/extensions/loader.js +2 -251
- package/dist/core/resource-loader.js +7 -2
- package/dist/core/tools/subagents.js +1 -0
- package/dist/main.js +3 -176
- package/dist/modes/interactive/interactive-mode.js +2 -19
- package/dist/node_modules/@diffexai/diffex-agent-core/distribution-components.json +4 -4
- package/dist/node_modules/@diffexai/diffex-agent-core/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-agent-core/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/image-models.generated.d.ts +180 -0
- package/dist/node_modules/@diffexai/diffex-ai/dist/image-models.generated.js +182 -2
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/.manifest.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/kimi-coding.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/nvidia.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/openrouter.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/distribution-components.json +3 -3
- package/dist/node_modules/@diffexai/diffex-ai/distribution-files.json +9 -9
- package/dist/node_modules/@diffexai/diffex-ai/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-client/distribution-components.json +3 -3
- package/dist/node_modules/@diffexai/diffex-client/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-client/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-harness-state/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-harness-state/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-harness-state/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-protocol/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-protocol/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-protocol/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-telemetry/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-telemetry/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-telemetry/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-tui/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-tui/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-tui/package.json +1 -1
- package/distribution-components.json +11 -11
- package/distribution-files.json +56 -36
- package/docs/usage.md +10 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/release/distribution-manifest.json +4 -4
- package/release/install-package-lock.json +5 -5
- package/release/install-package.json +2 -2
|
@@ -25,9 +25,9 @@ import * as _bundledDiffexCodingAgent from "../../index.js";
|
|
|
25
25
|
import { resolvePath } from "../../utils/paths.js";
|
|
26
26
|
import { readDiffexManifest } from "../diffex-manifest.js";
|
|
27
27
|
import { createEventBus } from "../event-bus.js";
|
|
28
|
-
import { execCommand } from "../exec.js";
|
|
29
|
-
import { createSyntheticSourceInfo } from "../source-info.js";
|
|
30
28
|
import { time } from "../timings.js";
|
|
29
|
+
import { createExtension, createExtensionAPI, createExtensionRuntime } from "./factory.js";
|
|
30
|
+
export { createExtensionRuntime, loadExtensionFromFactory } from "./factory.js";
|
|
31
31
|
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
|
|
32
32
|
const VIRTUAL_MODULES = {
|
|
33
33
|
typebox: _bundledTypebox,
|
|
@@ -112,223 +112,6 @@ function useExtensionCacheCwd(cwd) {
|
|
|
112
112
|
extensionCacheCwd = resolvedCwd;
|
|
113
113
|
return { cwd: resolvedCwd, generation: extensionCacheGeneration };
|
|
114
114
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Create a runtime with throwing stubs for action methods.
|
|
117
|
-
* Runner.bindCore() replaces these with real implementations.
|
|
118
|
-
*/
|
|
119
|
-
export function createExtensionRuntime() {
|
|
120
|
-
const notInitialized = () => {
|
|
121
|
-
throw new Error("Extension runtime not initialized. Action methods cannot be called during extension loading.");
|
|
122
|
-
};
|
|
123
|
-
const state = {};
|
|
124
|
-
const eventBusUnsubscribers = new Set();
|
|
125
|
-
const assertActive = () => {
|
|
126
|
-
if (state.staleMessage) {
|
|
127
|
-
throw new Error(state.staleMessage);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
const runtime = {
|
|
131
|
-
sendMessage: notInitialized,
|
|
132
|
-
sendUserMessage: notInitialized,
|
|
133
|
-
appendEntry: notInitialized,
|
|
134
|
-
setSessionName: notInitialized,
|
|
135
|
-
getSessionName: notInitialized,
|
|
136
|
-
setLabel: notInitialized,
|
|
137
|
-
getActiveTools: notInitialized,
|
|
138
|
-
getAllTools: notInitialized,
|
|
139
|
-
setActiveTools: notInitialized,
|
|
140
|
-
// registerTool() is valid during extension load; refresh is only needed post-bind.
|
|
141
|
-
refreshTools: () => { },
|
|
142
|
-
getCommands: notInitialized,
|
|
143
|
-
setModel: () => Promise.reject(new Error("Extension runtime not initialized")),
|
|
144
|
-
getThinkingLevel: notInitialized,
|
|
145
|
-
setThinkingLevel: notInitialized,
|
|
146
|
-
flagValues: new Map(),
|
|
147
|
-
pendingProviderRegistrations: [],
|
|
148
|
-
pendingNativeProviderRegistrations: [],
|
|
149
|
-
assertActive,
|
|
150
|
-
invalidate: (message) => {
|
|
151
|
-
if (state.staleMessage)
|
|
152
|
-
return;
|
|
153
|
-
state.staleMessage =
|
|
154
|
-
message ??
|
|
155
|
-
"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().";
|
|
156
|
-
for (const unsubscribe of eventBusUnsubscribers)
|
|
157
|
-
unsubscribe();
|
|
158
|
-
eventBusUnsubscribers.clear();
|
|
159
|
-
},
|
|
160
|
-
trackEventBusSubscription: (unsubscribe) => {
|
|
161
|
-
let active = true;
|
|
162
|
-
const trackedUnsubscribe = () => {
|
|
163
|
-
if (!active)
|
|
164
|
-
return;
|
|
165
|
-
active = false;
|
|
166
|
-
eventBusUnsubscribers.delete(trackedUnsubscribe);
|
|
167
|
-
unsubscribe();
|
|
168
|
-
};
|
|
169
|
-
eventBusUnsubscribers.add(trackedUnsubscribe);
|
|
170
|
-
return trackedUnsubscribe;
|
|
171
|
-
},
|
|
172
|
-
// Pre-bind: queue registrations so bindCore() can flush them once the
|
|
173
|
-
// model registry is available. bindCore() replaces both with direct calls.
|
|
174
|
-
registerProvider: (name, config, extensionPath = "<unknown>") => {
|
|
175
|
-
runtime.pendingProviderRegistrations.push({ name, config, extensionPath });
|
|
176
|
-
},
|
|
177
|
-
registerNativeProvider: (provider, extensionPath = "<unknown>") => {
|
|
178
|
-
runtime.pendingNativeProviderRegistrations.push({ provider, extensionPath });
|
|
179
|
-
},
|
|
180
|
-
unregisterProvider: (name) => {
|
|
181
|
-
runtime.pendingProviderRegistrations = runtime.pendingProviderRegistrations.filter((r) => r.name !== name);
|
|
182
|
-
runtime.pendingNativeProviderRegistrations = runtime.pendingNativeProviderRegistrations.filter((r) => r.provider.id !== name);
|
|
183
|
-
},
|
|
184
|
-
};
|
|
185
|
-
return runtime;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Create the ExtensionAPI for an extension.
|
|
189
|
-
* Registration methods write to the extension object.
|
|
190
|
-
* Action methods delegate to the shared runtime.
|
|
191
|
-
*/
|
|
192
|
-
function createExtensionAPI(extension, runtime, cwd, eventBus) {
|
|
193
|
-
const api = {
|
|
194
|
-
// Registration methods - write to extension
|
|
195
|
-
on(event, handler) {
|
|
196
|
-
runtime.assertActive();
|
|
197
|
-
const list = extension.handlers.get(event) ?? [];
|
|
198
|
-
list.push(handler);
|
|
199
|
-
extension.handlers.set(event, list);
|
|
200
|
-
},
|
|
201
|
-
registerTool(tool) {
|
|
202
|
-
runtime.assertActive();
|
|
203
|
-
extension.tools.set(tool.name, {
|
|
204
|
-
definition: tool,
|
|
205
|
-
sourceInfo: extension.sourceInfo,
|
|
206
|
-
});
|
|
207
|
-
runtime.refreshTools();
|
|
208
|
-
},
|
|
209
|
-
registerCommand(name, options) {
|
|
210
|
-
runtime.assertActive();
|
|
211
|
-
extension.commands.set(name, {
|
|
212
|
-
name,
|
|
213
|
-
sourceInfo: extension.sourceInfo,
|
|
214
|
-
...options,
|
|
215
|
-
});
|
|
216
|
-
},
|
|
217
|
-
registerShortcut(shortcut, options) {
|
|
218
|
-
runtime.assertActive();
|
|
219
|
-
extension.shortcuts.set(shortcut, { shortcut, extensionPath: extension.path, ...options });
|
|
220
|
-
},
|
|
221
|
-
registerFlag(name, options) {
|
|
222
|
-
runtime.assertActive();
|
|
223
|
-
extension.flags.set(name, { name, extensionPath: extension.path, ...options });
|
|
224
|
-
if (options.default !== undefined && !runtime.flagValues.has(name)) {
|
|
225
|
-
runtime.flagValues.set(name, options.default);
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
registerMessageRenderer(customType, renderer) {
|
|
229
|
-
runtime.assertActive();
|
|
230
|
-
extension.messageRenderers.set(customType, renderer);
|
|
231
|
-
},
|
|
232
|
-
registerMarkdownTransformer(transformer) {
|
|
233
|
-
runtime.assertActive();
|
|
234
|
-
extension.markdownTransformer = transformer;
|
|
235
|
-
},
|
|
236
|
-
registerEntryRenderer(customType, renderer) {
|
|
237
|
-
runtime.assertActive();
|
|
238
|
-
extension.entryRenderers ??= new Map();
|
|
239
|
-
extension.entryRenderers.set(customType, renderer);
|
|
240
|
-
},
|
|
241
|
-
// Flag access - checks extension registered it, reads from runtime
|
|
242
|
-
getFlag(name) {
|
|
243
|
-
runtime.assertActive();
|
|
244
|
-
if (!extension.flags.has(name))
|
|
245
|
-
return undefined;
|
|
246
|
-
return runtime.flagValues.get(name);
|
|
247
|
-
},
|
|
248
|
-
// Action methods - delegate to shared runtime
|
|
249
|
-
sendMessage(message, options) {
|
|
250
|
-
runtime.assertActive();
|
|
251
|
-
runtime.sendMessage(message, options);
|
|
252
|
-
},
|
|
253
|
-
sendUserMessage(content, options) {
|
|
254
|
-
runtime.assertActive();
|
|
255
|
-
runtime.sendUserMessage(content, options);
|
|
256
|
-
},
|
|
257
|
-
appendEntry(customType, data) {
|
|
258
|
-
runtime.assertActive();
|
|
259
|
-
runtime.appendEntry(customType, data);
|
|
260
|
-
},
|
|
261
|
-
setSessionName(name) {
|
|
262
|
-
runtime.assertActive();
|
|
263
|
-
runtime.setSessionName(name);
|
|
264
|
-
},
|
|
265
|
-
getSessionName() {
|
|
266
|
-
runtime.assertActive();
|
|
267
|
-
return runtime.getSessionName();
|
|
268
|
-
},
|
|
269
|
-
setLabel(entryId, label) {
|
|
270
|
-
runtime.assertActive();
|
|
271
|
-
runtime.setLabel(entryId, label);
|
|
272
|
-
},
|
|
273
|
-
exec(command, args, options) {
|
|
274
|
-
runtime.assertActive();
|
|
275
|
-
return execCommand(command, args, options?.cwd ?? cwd, options);
|
|
276
|
-
},
|
|
277
|
-
getActiveTools() {
|
|
278
|
-
runtime.assertActive();
|
|
279
|
-
return runtime.getActiveTools();
|
|
280
|
-
},
|
|
281
|
-
getAllTools() {
|
|
282
|
-
runtime.assertActive();
|
|
283
|
-
return runtime.getAllTools();
|
|
284
|
-
},
|
|
285
|
-
setActiveTools(toolNames) {
|
|
286
|
-
runtime.assertActive();
|
|
287
|
-
runtime.setActiveTools(toolNames);
|
|
288
|
-
},
|
|
289
|
-
getCommands() {
|
|
290
|
-
runtime.assertActive();
|
|
291
|
-
return runtime.getCommands();
|
|
292
|
-
},
|
|
293
|
-
setModel(model) {
|
|
294
|
-
runtime.assertActive();
|
|
295
|
-
return runtime.setModel(model);
|
|
296
|
-
},
|
|
297
|
-
getThinkingLevel() {
|
|
298
|
-
runtime.assertActive();
|
|
299
|
-
return runtime.getThinkingLevel();
|
|
300
|
-
},
|
|
301
|
-
setThinkingLevel(level) {
|
|
302
|
-
runtime.assertActive();
|
|
303
|
-
runtime.setThinkingLevel(level);
|
|
304
|
-
},
|
|
305
|
-
registerProvider(providerOrName, config) {
|
|
306
|
-
runtime.assertActive();
|
|
307
|
-
if (typeof providerOrName === "string") {
|
|
308
|
-
if (!config)
|
|
309
|
-
throw new Error("Provider config is required when registering by name");
|
|
310
|
-
runtime.registerProvider(providerOrName, config, extension.path);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
runtime.registerNativeProvider(providerOrName, extension.path);
|
|
314
|
-
},
|
|
315
|
-
unregisterProvider(name) {
|
|
316
|
-
runtime.assertActive();
|
|
317
|
-
runtime.unregisterProvider(name, extension.path);
|
|
318
|
-
},
|
|
319
|
-
events: {
|
|
320
|
-
emit(channel, data) {
|
|
321
|
-
runtime.assertActive();
|
|
322
|
-
eventBus.emit(channel, data);
|
|
323
|
-
},
|
|
324
|
-
on(channel, handler) {
|
|
325
|
-
runtime.assertActive();
|
|
326
|
-
return runtime.trackEventBusSubscription(eventBus.on(channel, handler));
|
|
327
|
-
},
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
return api;
|
|
331
|
-
}
|
|
332
115
|
function isCurrentCacheToken(cacheToken) {
|
|
333
116
|
return (cacheToken !== undefined &&
|
|
334
117
|
extensionCacheCwd === cacheToken.cwd &&
|
|
@@ -361,27 +144,6 @@ async function loadExtensionModule(extensionPath, cacheToken) {
|
|
|
361
144
|
}
|
|
362
145
|
return factory;
|
|
363
146
|
}
|
|
364
|
-
/**
|
|
365
|
-
* Create an Extension object with empty collections.
|
|
366
|
-
*/
|
|
367
|
-
function createExtension(extensionPath, resolvedPath) {
|
|
368
|
-
const source = extensionPath.startsWith("<") && extensionPath.endsWith(">")
|
|
369
|
-
? extensionPath.slice(1, -1).split(":")[0] || "temporary"
|
|
370
|
-
: "local";
|
|
371
|
-
const baseDir = extensionPath.startsWith("<") ? undefined : path.dirname(resolvedPath);
|
|
372
|
-
return {
|
|
373
|
-
path: extensionPath,
|
|
374
|
-
resolvedPath,
|
|
375
|
-
sourceInfo: createSyntheticSourceInfo(extensionPath, { source, baseDir }),
|
|
376
|
-
handlers: new Map(),
|
|
377
|
-
tools: new Map(),
|
|
378
|
-
messageRenderers: new Map(),
|
|
379
|
-
entryRenderers: new Map(),
|
|
380
|
-
commands: new Map(),
|
|
381
|
-
flags: new Map(),
|
|
382
|
-
shortcuts: new Map(),
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
147
|
async function loadExtension(extensionPath, cwd, eventBus, runtime, cacheToken) {
|
|
386
148
|
const resolvedPath = resolvePath(extensionPath, cwd, { normalizeUnicodeSpaces: true });
|
|
387
149
|
try {
|
|
@@ -401,17 +163,6 @@ async function loadExtension(extensionPath, cwd, eventBus, runtime, cacheToken)
|
|
|
401
163
|
return { extension: null, error: `Failed to load extension: ${message}` };
|
|
402
164
|
}
|
|
403
165
|
}
|
|
404
|
-
/**
|
|
405
|
-
* Create an Extension from an inline factory function.
|
|
406
|
-
*/
|
|
407
|
-
export async function loadExtensionFromFactory(factory, cwd, eventBus, runtime, extensionPath = "<inline>") {
|
|
408
|
-
const extension = createExtension(extensionPath, extensionPath);
|
|
409
|
-
const resolvedCwd = resolvePath(cwd);
|
|
410
|
-
const api = createExtensionAPI(extension, runtime, resolvedCwd, eventBus);
|
|
411
|
-
await factory(api);
|
|
412
|
-
time(`${extensionPath} factory`, "extensions");
|
|
413
|
-
return extension;
|
|
414
|
-
}
|
|
415
166
|
/**
|
|
416
167
|
* Load extensions from paths.
|
|
417
168
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { CONFIG_DIR_NAME } from "../config.js";
|
|
4
|
+
import { CONFIG_DIR_NAME, getPackageDir, IS_PRODUCTION_DISTRIBUTION } from "../config.js";
|
|
5
5
|
import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
|
|
6
6
|
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
|
|
7
7
|
import { createEventBus } from "./event-bus.js";
|
|
@@ -83,8 +83,13 @@ export function loadProjectContextFiles(options) {
|
|
|
83
83
|
const resolvedAgentDir = resolvePath(options.agentDir);
|
|
84
84
|
const contextFiles = [];
|
|
85
85
|
const seenPaths = new Set();
|
|
86
|
+
if (IS_PRODUCTION_DISTRIBUTION) {
|
|
87
|
+
const path = join(getPackageDir(), "dist", "AGENTS.md");
|
|
88
|
+
contextFiles.push({ path, content: readFileSync(path, "utf-8") });
|
|
89
|
+
seenPaths.add(path);
|
|
90
|
+
}
|
|
86
91
|
const globalContext = loadContextFileFromDir(resolvedAgentDir);
|
|
87
|
-
if (globalContext) {
|
|
92
|
+
if (globalContext && !seenPaths.has(globalContext.path)) {
|
|
88
93
|
contextFiles.push(globalContext);
|
|
89
94
|
seenPaths.add(globalContext.path);
|
|
90
95
|
}
|
|
@@ -104,6 +104,7 @@ export function createSpawnAgentTool(spawn, assertCanSpawn = () => { }) {
|
|
|
104
104
|
label: "Spawn Agent",
|
|
105
105
|
description: "Start an independent child agent asynchronously. The tool returns after acceptance, but the enclosing parent prompt remains active until coordinated child work is delivered.",
|
|
106
106
|
promptGuidelines: [
|
|
107
|
+
"For broad research, audits, or implementation work with independent areas, use spawn_agent to delegate focused subtasks in parallel when available. Request file references and supporting evidence for research and audits, and assign clear, non-overlapping file ownership for implementation work.",
|
|
107
108
|
"After spawning child agents, you may finish the current response. The session waits for the frozen child batch and automatically sends one hidden, name-sorted result message before the parent prompt settles.",
|
|
108
109
|
],
|
|
109
110
|
parameters: spawnAgentSchema,
|
package/dist/main.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* This file handles CLI argument parsing and translates them into
|
|
5
5
|
* createAgentSession() options. The SDK does the heavy lifting.
|
|
6
6
|
*/
|
|
7
|
-
import { createInterface } from "node:readline";
|
|
8
7
|
import { modelsAreEqual } from "@diffexai/diffex-ai";
|
|
9
8
|
import chalk from "chalk";
|
|
10
9
|
import { parseArgs, printHelp } from "./cli/args.js";
|
|
@@ -15,7 +14,7 @@ import { processFileArguments } from "./cli/file-processor.js";
|
|
|
15
14
|
import { buildInitialMessage } from "./cli/initial-message.js";
|
|
16
15
|
import { listModels } from "./cli/list-models.js";
|
|
17
16
|
import { createProjectTrustContext } from "./cli/project-trust.js";
|
|
18
|
-
import {
|
|
17
|
+
import { applySessionName, createSessionManager, validateForkFlags, validateSessionIdFlags, } from "./cli/session-selection.js";
|
|
19
18
|
import { shouldRunFirstTimeSetup, showFirstTimeSetup, showStartupSelector } from "./cli/startup-ui.js";
|
|
20
19
|
import { APP_NAME, ENV_SESSION_DIR, expandTildePath, getAgentDir, getPackageDir, VERSION } from "./config.js";
|
|
21
20
|
import { createAgentSessionRuntime, } from "./core/agent-session-runtime.js";
|
|
@@ -32,7 +31,7 @@ import { ModelRuntime } from "./core/model-runtime.js";
|
|
|
32
31
|
import { restoreStdout, takeOverStdout } from "./core/output-guard.js";
|
|
33
32
|
import { resolveProjectTrusted } from "./core/project-trust.js";
|
|
34
33
|
import { formatMissingSessionCwdPrompt, getMissingSessionCwdIssue, MissingSessionCwdError, } from "./core/session-cwd.js";
|
|
35
|
-
import {
|
|
34
|
+
import { SessionManager } from "./core/session-manager.js";
|
|
36
35
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
37
36
|
import { printTimings, resetTimings, time } from "./core/timings.js";
|
|
38
37
|
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "./core/trust-manager.js";
|
|
@@ -192,171 +191,6 @@ async function prepareInitialMessage(parsed, autoResizeImages, stdinContent) {
|
|
|
192
191
|
stdinContent,
|
|
193
192
|
});
|
|
194
193
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Resolve a session argument to a file path.
|
|
197
|
-
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
|
|
198
|
-
*/
|
|
199
|
-
async function findLocalSessionByExactId(sessionId, cwd, sessionDir) {
|
|
200
|
-
const localSessions = await SessionManager.list(cwd, sessionDir);
|
|
201
|
-
const localMatch = localSessions.find((s) => s.id === sessionId);
|
|
202
|
-
return localMatch ? { type: "local", path: localMatch.path } : undefined;
|
|
203
|
-
}
|
|
204
|
-
async function resolveSessionPath(sessionArg, cwd, sessionDir) {
|
|
205
|
-
// If it looks like a file path, resolve it before handing it to the session manager.
|
|
206
|
-
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
|
|
207
|
-
return { type: "path", path: resolvePath(sessionArg, cwd) };
|
|
208
|
-
}
|
|
209
|
-
// Try to match as session ID in current project first
|
|
210
|
-
const localSessions = await SessionManager.list(cwd, sessionDir);
|
|
211
|
-
const localMatch = localSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));
|
|
212
|
-
if (localMatch) {
|
|
213
|
-
return { type: "local", path: localMatch.path };
|
|
214
|
-
}
|
|
215
|
-
// Try global search across all projects
|
|
216
|
-
const allSessions = await SessionManager.listAll(sessionDir);
|
|
217
|
-
const globalMatch = allSessions.find((s) => s.id === sessionArg) ?? allSessions.find((s) => s.id.startsWith(sessionArg));
|
|
218
|
-
if (globalMatch) {
|
|
219
|
-
return { type: "global", path: globalMatch.path, cwd: globalMatch.cwd };
|
|
220
|
-
}
|
|
221
|
-
// Not found anywhere
|
|
222
|
-
return { type: "not_found", arg: sessionArg };
|
|
223
|
-
}
|
|
224
|
-
/** Prompt user for yes/no confirmation */
|
|
225
|
-
async function promptConfirm(message) {
|
|
226
|
-
return new Promise((resolve) => {
|
|
227
|
-
const rl = createInterface({
|
|
228
|
-
input: process.stdin,
|
|
229
|
-
output: process.stdout,
|
|
230
|
-
});
|
|
231
|
-
rl.question(`${message} [y/N] `, (answer) => {
|
|
232
|
-
rl.close();
|
|
233
|
-
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
function validateForkFlags(parsed) {
|
|
238
|
-
if (!parsed.fork)
|
|
239
|
-
return;
|
|
240
|
-
const conflictingFlags = [
|
|
241
|
-
parsed.session ? "--session" : undefined,
|
|
242
|
-
parsed.continue ? "--continue" : undefined,
|
|
243
|
-
parsed.resume ? "--resume" : undefined,
|
|
244
|
-
parsed.noSession ? "--no-session" : undefined,
|
|
245
|
-
].filter((flag) => flag !== undefined);
|
|
246
|
-
if (conflictingFlags.length > 0) {
|
|
247
|
-
console.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(", ")}`));
|
|
248
|
-
process.exit(1);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function validateSessionIdFlags(parsed) {
|
|
252
|
-
if (parsed.sessionId === undefined)
|
|
253
|
-
return;
|
|
254
|
-
const conflictingFlags = [
|
|
255
|
-
parsed.session ? "--session" : undefined,
|
|
256
|
-
parsed.continue ? "--continue" : undefined,
|
|
257
|
-
parsed.resume ? "--resume" : undefined,
|
|
258
|
-
].filter((flag) => flag !== undefined);
|
|
259
|
-
if (conflictingFlags.length > 0) {
|
|
260
|
-
console.error(chalk.red(`Error: --session-id cannot be combined with ${conflictingFlags.join(", ")}`));
|
|
261
|
-
process.exit(1);
|
|
262
|
-
}
|
|
263
|
-
try {
|
|
264
|
-
assertValidSessionId(parsed.sessionId);
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
269
|
-
process.exit(1);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function openSessionOrExit(path, sessionDir) {
|
|
273
|
-
try {
|
|
274
|
-
return SessionManager.open(path, sessionDir);
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
278
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
function forkSessionOrExit(sourcePath, cwd, sessionDir, sessionId) {
|
|
283
|
-
try {
|
|
284
|
-
return SessionManager.forkFrom(sourcePath, cwd, sessionDir, { id: sessionId });
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
288
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
293
|
-
if (parsed.noSession || parsed.help || parsed.listModels !== undefined) {
|
|
294
|
-
return SessionManager.inMemory(cwd, parsed.sessionId !== undefined ? { id: parsed.sessionId } : undefined);
|
|
295
|
-
}
|
|
296
|
-
if (parsed.fork) {
|
|
297
|
-
if (parsed.sessionId) {
|
|
298
|
-
const existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
|
|
299
|
-
if (existingTarget) {
|
|
300
|
-
console.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);
|
|
305
|
-
switch (resolved.type) {
|
|
306
|
-
case "path":
|
|
307
|
-
case "local":
|
|
308
|
-
case "global":
|
|
309
|
-
return forkSessionOrExit(resolved.path, cwd, sessionDir, parsed.sessionId);
|
|
310
|
-
case "not_found":
|
|
311
|
-
console.error(chalk.red(`No session found matching '${resolved.arg}'`));
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (parsed.session) {
|
|
316
|
-
const resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);
|
|
317
|
-
switch (resolved.type) {
|
|
318
|
-
case "path":
|
|
319
|
-
case "local":
|
|
320
|
-
return openSessionOrExit(resolved.path, sessionDir);
|
|
321
|
-
case "global": {
|
|
322
|
-
console.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));
|
|
323
|
-
const shouldFork = await promptConfirm("Fork this session into current directory?");
|
|
324
|
-
if (!shouldFork) {
|
|
325
|
-
console.log(chalk.dim("Aborted."));
|
|
326
|
-
process.exit(0);
|
|
327
|
-
}
|
|
328
|
-
return forkSessionOrExit(resolved.path, cwd, sessionDir);
|
|
329
|
-
}
|
|
330
|
-
case "not_found":
|
|
331
|
-
console.error(chalk.red(`No session found matching '${resolved.arg}'`));
|
|
332
|
-
process.exit(1);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
if (parsed.resume) {
|
|
336
|
-
try {
|
|
337
|
-
const selectedPath = await selectSession((onProgress) => SessionManager.list(cwd, sessionDir, onProgress), (onProgress) => SessionManager.listAll(sessionDir, onProgress), settingsManager);
|
|
338
|
-
if (!selectedPath) {
|
|
339
|
-
console.log(chalk.dim("No session selected"));
|
|
340
|
-
process.exit(0);
|
|
341
|
-
}
|
|
342
|
-
return SessionManager.open(selectedPath, sessionDir);
|
|
343
|
-
}
|
|
344
|
-
finally {
|
|
345
|
-
stopThemeWatcher();
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (parsed.continue) {
|
|
349
|
-
return SessionManager.continueRecent(cwd, sessionDir);
|
|
350
|
-
}
|
|
351
|
-
if (parsed.sessionId) {
|
|
352
|
-
const existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
|
|
353
|
-
if (existingSession) {
|
|
354
|
-
return SessionManager.open(existingSession.path, sessionDir);
|
|
355
|
-
}
|
|
356
|
-
console.error(chalk.yellow(`Warning: No project session found with id '${parsed.sessionId}'; creating a new session with that id.`));
|
|
357
|
-
}
|
|
358
|
-
return SessionManager.create(cwd, sessionDir, { id: parsed.sessionId });
|
|
359
|
-
}
|
|
360
194
|
function buildSessionOptions(parsed, scopedModels, hasExistingSession, modelRuntime, settingsManager) {
|
|
361
195
|
const options = {};
|
|
362
196
|
const diagnostics = [];
|
|
@@ -557,14 +391,7 @@ export async function main(args, options) {
|
|
|
557
391
|
process.exit(1);
|
|
558
392
|
}
|
|
559
393
|
}
|
|
560
|
-
|
|
561
|
-
const name = parsed.name.trim();
|
|
562
|
-
if (!name) {
|
|
563
|
-
console.error(chalk.red("Error: --name requires a non-empty value"));
|
|
564
|
-
process.exit(1);
|
|
565
|
-
}
|
|
566
|
-
sessionManager.appendSessionInfo(name);
|
|
567
|
-
}
|
|
394
|
+
applySessionName(parsed, sessionManager);
|
|
568
395
|
const initialWorkspace = await resolveHarnessWorkspace(sessionManager.getCwd());
|
|
569
396
|
const invocationTraceResult = await createInvocationTrace({
|
|
570
397
|
agentDir: resolveEvolutionWorkspaceStateDirectory(agentDir, initialWorkspace.workspace),
|
|
@@ -7,7 +7,7 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import * as TuiLayouts from "@diffexai/diffex-tui";
|
|
10
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter,
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getGlyphs, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@diffexai/diffex-tui";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { spawn, spawnSync } from "child_process";
|
|
13
13
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
@@ -149,7 +149,6 @@ const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
|
149
149
|
const DIFFEX_ACCOUNT_LOGIN_ID = "diffex";
|
|
150
150
|
const DIFFEX_ACCOUNT_SELECTOR_ID = "diffex-account";
|
|
151
151
|
const DIFFEX_ACCOUNT_LOGIN_LABEL = "Sign in to Diffex";
|
|
152
|
-
const DIFFEX_UPDATE_AVAILABLE_NOTIFICATION_ENABLED = false;
|
|
153
152
|
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
154
153
|
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
155
154
|
if (filtered.length === 0)
|
|
@@ -833,7 +832,7 @@ export class InteractiveMode {
|
|
|
833
832
|
}
|
|
834
833
|
// Start version check asynchronously
|
|
835
834
|
checkForNewDiffexVersion(this.version).then((newRelease) => {
|
|
836
|
-
if (newRelease
|
|
835
|
+
if (newRelease) {
|
|
837
836
|
this.showNewVersionNotification(newRelease);
|
|
838
837
|
}
|
|
839
838
|
});
|
|
@@ -3934,25 +3933,9 @@ export class InteractiveMode {
|
|
|
3934
3933
|
this.pendingUserDock = undefined;
|
|
3935
3934
|
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
3936
3935
|
const updateInstruction = theme.fg("muted", `New version ${release.version} is available. Run `) + action;
|
|
3937
|
-
const changelogUrl = "https://github.com/diffexai/diffex/blob/main/packages/coding-agent/CHANGELOG.md";
|
|
3938
|
-
const changelogLink = getCapabilities().hyperlinks
|
|
3939
|
-
? hyperlink(theme.fg("accent", changelogUrl), changelogUrl)
|
|
3940
|
-
: theme.fg("accent", changelogUrl);
|
|
3941
|
-
const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
|
|
3942
|
-
const note = release.note?.trim();
|
|
3943
3936
|
this.chatContainer.addChild(new Spacer(1));
|
|
3944
3937
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
3945
3938
|
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
|
|
3946
|
-
if (note) {
|
|
3947
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
3948
|
-
this.chatContainer.addChild(new Markdown(note, 1, 0, this.getMarkdownThemeWithSettings(), {
|
|
3949
|
-
color: (text) => theme.fg("muted", text),
|
|
3950
|
-
}, {
|
|
3951
|
-
tableStyle: "compact",
|
|
3952
|
-
}));
|
|
3953
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
3954
|
-
}
|
|
3955
|
-
this.chatContainer.addChild(new Text(changelogLine, 1, 0));
|
|
3956
3939
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
3957
3940
|
this.ui.requestRender();
|
|
3958
3941
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
3
|
"name": "@diffexai/diffex-agent-core",
|
|
4
|
-
"version": "0.2.
|
|
5
|
-
"sourceCommit": "
|
|
4
|
+
"version": "0.2.4",
|
|
5
|
+
"sourceCommit": "f668f241cac464e993edd0d15ba1e57cd2cb8a32",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"portions": "Diffex contributions; Pi-derived portions retain MIT terms in LICENSE",
|
|
8
8
|
"notice": "LICENSE",
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"name": "@diffexai/diffex-ai",
|
|
13
|
-
"version": "^0.2.
|
|
13
|
+
"version": "^0.2.4",
|
|
14
14
|
"distribution": "resolved separately by package manager; not bundled in this npm tarball"
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"name": "@diffexai/diffex-telemetry",
|
|
18
|
-
"version": "^0.2.
|
|
18
|
+
"version": "^0.2.4",
|
|
19
19
|
"distribution": "resolved separately by package manager; not bundled in this npm tarball"
|
|
20
20
|
},
|
|
21
21
|
{
|