@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
@@ -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 { selectSession } from "./cli/session-picker.js";
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 { assertValidSessionId, SessionManager } from "./core/session-manager.js";
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
- if (parsed.name !== undefined) {
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, getCapabilities, getGlyphs, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@diffexai/diffex-tui";
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 && DIFFEX_UPDATE_AVAILABLE_NOTIFICATION_ENABLED) {
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.2",
5
- "sourceCommit": "dbe80a9dfe730c776a99be1ea9c439fe6b3cc023",
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.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.2",
18
+ "version": "^0.2.4",
19
19
  "distribution": "resolved separately by package manager; not bundled in this npm tarball"
20
20
  },
21
21
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "sourceCommit": "dbe80a9dfe730c776a99be1ea9c439fe6b3cc023",
2
+ "sourceCommit": "f668f241cac464e993edd0d15ba1e57cd2cb8a32",
3
3
  "algorithm": "sha256",
4
4
  "files": [
5
5
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diffexai/diffex-agent-core",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",