jinzd-ai-cli 0.4.278 → 0.4.280

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 (34) hide show
  1. package/dist/{batch-7Y4NAA6N.js → batch-F27NOTWC.js} +2 -2
  2. package/dist/{chunk-LTZFUYBT.js → chunk-B3FHA6IN.js} +3 -3
  3. package/dist/{chunk-WOXIKQRI.js → chunk-CP5LAA52.js} +4 -4
  4. package/dist/{chunk-2BYQP6JX.js → chunk-EOP775JU.js} +1 -1
  5. package/dist/{chunk-JJCNNQTU.js → chunk-IIO2SMEJ.js} +354 -3
  6. package/dist/{chunk-H7QC5VMQ.js → chunk-LPZNOYRJ.js} +1 -1
  7. package/dist/{chunk-LOXEYWC3.js → chunk-NHJ4GHL4.js} +1 -1
  8. package/dist/{chunk-SJCOE4C7.js → chunk-PN42JO74.js} +1 -1
  9. package/dist/{chunk-LXXQTWT2.js → chunk-PUC277TH.js} +1 -1
  10. package/dist/{chunk-4Z6GAYB5.js → chunk-TAIZZY3N.js} +3 -3
  11. package/dist/{chunk-X3722RFA.js → chunk-UBPRUSAF.js} +1 -1
  12. package/dist/{chunk-L3CPM472.js → chunk-UMWDNNPS.js} +1 -1
  13. package/dist/{chunk-RFSKPCYC.js → chunk-V7EZ4SDC.js} +1 -1
  14. package/dist/{chunk-SB4XONS4.js → chunk-VQCFEA4Z.js} +1 -1
  15. package/dist/{ci-WDF2CYR4.js → ci-HJ4OVHCM.js} +4 -4
  16. package/dist/{ci-format-7B5VAZGA.js → ci-format-NKJRZ3QI.js} +2 -2
  17. package/dist/{constants-DC3TNLTL.js → constants-6LCZOBJC.js} +1 -1
  18. package/dist/{doctor-cli-CDDG5XOY.js → doctor-cli-7JGPC5G4.js} +4 -4
  19. package/dist/electron-server.js +525 -140
  20. package/dist/{hub-TO7ODOIA.js → hub-NX7HYN7V.js} +1 -1
  21. package/dist/index.js +151 -429
  22. package/dist/{persistent-memory-JCENBMCJ.js → persistent-memory-E6WECUTW.js} +2 -2
  23. package/dist/{persistent-memory-BU63CSGK.js → persistent-memory-MHHX7F25.js} +2 -2
  24. package/dist/{pr-GVSZYX6I.js → pr-GLUFBQLI.js} +4 -4
  25. package/dist/{run-tests-2Q5MJHXB.js → run-tests-LWAFPVS3.js} +2 -2
  26. package/dist/{run-tests-R4IQS6IF.js → run-tests-YPJDOEJA.js} +2 -2
  27. package/dist/{server-KAURBKFI.js → server-CXXYIPMU.js} +226 -145
  28. package/dist/{server-WXLE53HG.js → server-P6DI27CY.js} +5 -5
  29. package/dist/{task-orchestrator-RJ6FXYY5.js → task-orchestrator-FW4LBPTS.js} +5 -5
  30. package/dist/{usage-V3R7DA7M.js → usage-CJUHQDT2.js} +2 -2
  31. package/dist/web/client/app.js +14 -2
  32. package/dist/web/client/app.js.br +0 -0
  33. package/dist/web/client/app.js.gz +0 -0
  34. package/package.json +1 -1
@@ -40,14 +40,14 @@ import {
40
40
  touchMemoryReferences,
41
41
  updateMemoryApproval,
42
42
  updateMemoryEntry
43
- } from "./chunk-LXXQTWT2.js";
43
+ } from "./chunk-PUC277TH.js";
44
44
  import {
45
45
  redactJson,
46
46
  scanString
47
47
  } from "./chunk-FSC6KEWU.js";
48
48
  import {
49
49
  runTestsTool
50
- } from "./chunk-LOXEYWC3.js";
50
+ } from "./chunk-NHJ4GHL4.js";
51
51
  import {
52
52
  AGENTIC_BEHAVIOR_GUIDELINE,
53
53
  APP_NAME,
@@ -80,7 +80,7 @@ import {
80
80
  SUBAGENT_MAX_ROUNDS_LIMIT,
81
81
  VERSION,
82
82
  buildUserIdentityPrompt
83
- } from "./chunk-SJCOE4C7.js";
83
+ } from "./chunk-PN42JO74.js";
84
84
  import {
85
85
  hasSemanticIndex,
86
86
  semanticSearch
@@ -14370,6 +14370,101 @@ function decideModel(input) {
14370
14370
  return pickModel(input.userInput, input.currentModel, input.routingConfig, input.availableModels);
14371
14371
  }
14372
14372
 
14373
+ // src/core/session-export.ts
14374
+ function defaultExportFilename(session, format) {
14375
+ const dateTag = session.created.toISOString().slice(0, 10);
14376
+ return `session-${dateTag}-${session.id.slice(0, 8)}.${format === "json" ? "json" : "md"}`;
14377
+ }
14378
+ function markdownHeading(msg) {
14379
+ switch (msg.role) {
14380
+ case "user":
14381
+ return "## \u{1F464} You";
14382
+ case "assistant":
14383
+ return "## \u{1F916} Assistant";
14384
+ case "tool":
14385
+ return `### \u{1F527} Tool result: ${msg.toolName ?? "tool"}${msg.isError ? " (error)" : ""}`;
14386
+ default:
14387
+ return `## \u2699 ${msg.role}`;
14388
+ }
14389
+ }
14390
+ function buildSessionExport(session, format, outFile) {
14391
+ const filename = outFile || defaultExportFilename(session, format);
14392
+ if (format === "json") {
14393
+ const payload = session.toJSON ? session.toJSON() : {
14394
+ id: session.id,
14395
+ title: session.title ?? null,
14396
+ provider: session.provider,
14397
+ model: session.model,
14398
+ created: session.created.toISOString(),
14399
+ messages: session.messages
14400
+ };
14401
+ return { filename, content: JSON.stringify(payload, null, 2), messageCount: session.messages.length };
14402
+ }
14403
+ const dateStr = session.created.toISOString().replace("T", " ").slice(0, 19);
14404
+ let md = `# ${session.title ?? "Conversation Export"}
14405
+
14406
+ `;
14407
+ md += `| Field | Value |
14408
+ |----------|-------|
14409
+ `;
14410
+ md += `| Provider | ${session.provider} |
14411
+ `;
14412
+ md += `| Model | ${session.model} |
14413
+ `;
14414
+ md += `| Date | ${dateStr} |
14415
+ `;
14416
+ md += `| Session | ${session.id.slice(0, 8)} |
14417
+
14418
+ ---
14419
+
14420
+ `;
14421
+ for (const msg of session.messages) {
14422
+ const text = getContentText(msg.content);
14423
+ md += `${markdownHeading(msg)}
14424
+
14425
+ `;
14426
+ if (text.trim()) md += `${text}
14427
+
14428
+ `;
14429
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
14430
+ for (const tc of msg.toolCalls) {
14431
+ md += `\`\`\`json
14432
+ ${JSON.stringify({ tool: tc.name, arguments: tc.arguments ?? {} }, null, 2)}
14433
+ \`\`\`
14434
+
14435
+ `;
14436
+ }
14437
+ }
14438
+ md += `---
14439
+
14440
+ `;
14441
+ }
14442
+ return { filename, content: md, messageCount: session.messages.length };
14443
+ }
14444
+ function parseCheckpointCommand(args) {
14445
+ const sub = args[0]?.toLowerCase();
14446
+ if (!sub || sub === "list") return { kind: "list" };
14447
+ const name = args.slice(1).join(" ").trim();
14448
+ if (sub === "save" || sub === "restore" || sub === "delete") {
14449
+ if (!name) return { kind: "usage", message: `Usage: /checkpoint ${sub} <name>` };
14450
+ return { kind: sub, name };
14451
+ }
14452
+ return { kind: "unknown", message: `Unknown subcommand: ${sub}. Use save/restore/list/delete.` };
14453
+ }
14454
+ function formatCheckpointList(checkpoints) {
14455
+ if (checkpoints.length === 0) {
14456
+ return ["No checkpoints. Use /checkpoint save <name> to create one."];
14457
+ }
14458
+ return [
14459
+ `Checkpoints (${checkpoints.length}):`,
14460
+ ...checkpoints.map((cp) => ` ${cp.name.padEnd(20)} msg #${cp.messageIndex} ${cp.timestamp.toLocaleString()}`)
14461
+ ];
14462
+ }
14463
+ var checkpointSavedMessage = (name, messageCount) => `\u2713 Checkpoint "${name}" saved at message #${messageCount}`;
14464
+ var checkpointRestoredMessage = (name, messageCount) => `\u2713 Restored to checkpoint "${name}" (${messageCount} messages, token usage reset)`;
14465
+ var checkpointDeletedMessage = (name) => `\u2713 Deleted checkpoint "${name}"`;
14466
+ var checkpointNotFoundMessage = (name) => `Checkpoint "${name}" not found.`;
14467
+
14373
14468
  // src/core/context-files.ts
14374
14469
  import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
14375
14470
  import { join as join19, relative as relative4, resolve as resolve8 } from "path";
@@ -16407,6 +16502,232 @@ function lastAssistantText(messages) {
16407
16502
  return void 0;
16408
16503
  }
16409
16504
 
16505
+ // src/core/session-command.ts
16506
+ var DEFAULT_SESSION_LIST_LIMIT = 50;
16507
+ function parseSessionListArgs(args) {
16508
+ let limit = DEFAULT_SESSION_LIST_LIMIT;
16509
+ let all = false;
16510
+ const filter = {};
16511
+ const warnings = [];
16512
+ let search = null;
16513
+ let days = null;
16514
+ let since = null;
16515
+ let sort = { field: "updated", order: "desc" };
16516
+ let json = false;
16517
+ let stale = null;
16518
+ let verbose = false;
16519
+ for (let i = 1; i < args.length; i++) {
16520
+ const a = args[i];
16521
+ if (a === "--all" || a === "-a") {
16522
+ all = true;
16523
+ } else if ((a === "--limit" || a === "-n") && i + 1 < args.length) {
16524
+ const n = Number(args[i + 1]);
16525
+ if (Number.isFinite(n) && n > 0) {
16526
+ limit = n;
16527
+ i++;
16528
+ }
16529
+ } else if (a === "--filter" && i + 1 < args.length) {
16530
+ const val = args[++i];
16531
+ const eq = val.indexOf("=");
16532
+ if (eq > 0) {
16533
+ const key = val.slice(0, eq);
16534
+ const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
16535
+ if (key === "provider") filter.provider = vals;
16536
+ else if (key === "model") filter.model = vals;
16537
+ else if (key === "messages") {
16538
+ const n = Number(vals[0]);
16539
+ if (Number.isFinite(n) && n >= 0) filter.messages = n;
16540
+ else warnings.push(`Invalid filter value for "messages": expected a non-negative integer, got "${vals[0]}"`);
16541
+ } else {
16542
+ warnings.push(`Unknown filter key: "${key}". Supported keys: provider, model, messages`);
16543
+ }
16544
+ } else {
16545
+ warnings.push(`Invalid --filter syntax: "${val}". Expected key=value`);
16546
+ }
16547
+ } else if ((a === "--search" || a === "-s") && i + 1 < args.length) {
16548
+ search = args[++i];
16549
+ } else if (a === "--days" && i + 1 < args.length) {
16550
+ const n = Number(args[++i]);
16551
+ if (Number.isFinite(n) && n > 0) days = n;
16552
+ } else if (a === "--since" && i + 1 < args.length) {
16553
+ since = args[++i];
16554
+ } else if (a === "--sort" && i + 1 < args.length) {
16555
+ const sv = args[++i];
16556
+ const colon = sv.indexOf(":");
16557
+ const field = colon > 0 ? sv.slice(0, colon) : sv;
16558
+ const order = colon > 0 ? sv.slice(colon + 1) : "desc";
16559
+ if (field === "updated" || field === "created" || field === "messages" || field === "size") {
16560
+ sort = { field, order: order === "asc" ? "asc" : "desc" };
16561
+ }
16562
+ } else if (a === "--json") {
16563
+ json = true;
16564
+ } else if (a === "--verbose" || a === "-v") {
16565
+ verbose = true;
16566
+ } else if (a === "--stale" && i + 1 < args.length) {
16567
+ const n = Number(args[++i]);
16568
+ if (Number.isFinite(n) && n > 0) stale = n;
16569
+ }
16570
+ }
16571
+ if (all) limit = -1;
16572
+ return { limit, all, filter, search, days, since, sort, json, stale, verbose, warnings };
16573
+ }
16574
+ function sessionListNeedsFileSize(opts) {
16575
+ return opts.verbose || opts.sort.field === "size";
16576
+ }
16577
+ function selectSessions(sessions, opts, now = Date.now()) {
16578
+ let matched = sessions;
16579
+ const hasFilters = !!opts.filter.provider || !!opts.filter.model || opts.filter.messages !== void 0;
16580
+ const hasSearch = !!opts.search;
16581
+ const hasTimeFilter = opts.days !== null || opts.since !== null;
16582
+ const hasStale = opts.stale !== null;
16583
+ if (hasFilters) {
16584
+ const p = opts.filter.provider;
16585
+ const m = opts.filter.model;
16586
+ const msg = opts.filter.messages;
16587
+ matched = matched.filter((s) => {
16588
+ if (p && !p.includes(s.provider.toLowerCase())) return false;
16589
+ if (m && !m.some((kw) => s.model.toLowerCase().includes(kw))) return false;
16590
+ if (msg !== void 0 && s.messageCount !== msg) return false;
16591
+ return true;
16592
+ });
16593
+ }
16594
+ if (hasSearch) {
16595
+ const q = opts.search.toLowerCase();
16596
+ matched = matched.filter(
16597
+ (s) => (s.title ?? "").toLowerCase().includes(q) || s.id.toLowerCase().includes(q)
16598
+ );
16599
+ }
16600
+ if (hasTimeFilter) {
16601
+ let cutoff = 0;
16602
+ if (opts.since) cutoff = new Date(opts.since).getTime();
16603
+ else if (opts.days !== null) cutoff = now - opts.days * 24 * 60 * 60 * 1e3;
16604
+ if (!Number.isNaN(cutoff)) {
16605
+ matched = matched.filter((s) => s.updated.getTime() >= cutoff);
16606
+ }
16607
+ }
16608
+ const staleCutoff = hasStale ? now - opts.stale * 24 * 60 * 60 * 1e3 : null;
16609
+ if (staleCutoff !== null) {
16610
+ matched = matched.filter((s) => s.updated.getTime() < staleCutoff);
16611
+ }
16612
+ const defaultSort = opts.sort.field === "updated" && opts.sort.order === "desc";
16613
+ if (!defaultSort) {
16614
+ const mul = opts.sort.order === "asc" ? 1 : -1;
16615
+ matched = [...matched].sort((a, b) => {
16616
+ switch (opts.sort.field) {
16617
+ case "created":
16618
+ return (a.created.getTime() - b.created.getTime()) * mul;
16619
+ case "messages":
16620
+ return (a.messageCount - b.messageCount) * mul;
16621
+ case "size":
16622
+ return ((a.fileSize ?? 0) - (b.fileSize ?? 0)) * mul;
16623
+ default:
16624
+ return (a.updated.getTime() - b.updated.getTime()) * mul;
16625
+ }
16626
+ });
16627
+ }
16628
+ const filterTags = [];
16629
+ if (hasFilters) filterTags.push("filtered");
16630
+ if (hasSearch) filterTags.push("searched");
16631
+ if (hasTimeFilter) filterTags.push("time-limited");
16632
+ if (hasStale) filterTags.push(`stale >${opts.stale}d`);
16633
+ return {
16634
+ matched,
16635
+ shown: opts.limit > 0 ? matched.slice(0, opts.limit) : matched,
16636
+ totalCount: matched.length,
16637
+ totalMessages: matched.reduce((sum, s) => sum + s.messageCount, 0),
16638
+ staleCutoff,
16639
+ filterTags,
16640
+ needsFileSize: sessionListNeedsFileSize(opts)
16641
+ };
16642
+ }
16643
+ function sessionListSummary(view) {
16644
+ const tag = view.filterTags.length > 0 ? ` (${view.filterTags.join(", ")})` : "";
16645
+ return `${view.shown.length} of ${view.totalCount} sessions${tag} \xB7 ${view.totalMessages} messages total`;
16646
+ }
16647
+ function sessionListJsonRow(s, includeFileSize) {
16648
+ return {
16649
+ id: s.id,
16650
+ title: s.title ?? null,
16651
+ provider: s.provider,
16652
+ model: s.model,
16653
+ messageCount: s.messageCount,
16654
+ created: s.created.toISOString(),
16655
+ updated: s.updated.toISOString(),
16656
+ ...includeFileSize ? { fileSize: s.fileSize } : {},
16657
+ ...s.branchCount !== void 0 ? { branchCount: s.branchCount, activeBranch: s.activeBranch } : {}
16658
+ };
16659
+ }
16660
+ function resolveSessionRef(sessions, prefix, policy) {
16661
+ const matches2 = sessions.filter((s) => s.id.startsWith(prefix));
16662
+ if (matches2.length === 0) {
16663
+ return { status: "not-found", message: `Session '${prefix}' not found.` };
16664
+ }
16665
+ if (matches2.length > 1 && policy === "strict") {
16666
+ return {
16667
+ status: "ambiguous",
16668
+ matches: matches2,
16669
+ message: `Ambiguous prefix '${prefix}' matches ${matches2.length} sessions \u2014 be more specific:`
16670
+ };
16671
+ }
16672
+ return {
16673
+ status: "ok",
16674
+ match: matches2[0],
16675
+ ambiguousWith: matches2.length > 1 ? matches2 : [],
16676
+ message: matches2.length > 1 ? `\u26A0 Ambiguous prefix '${prefix}' matches ${matches2.length} sessions \u2014 loading most recent:` : void 0
16677
+ };
16678
+ }
16679
+ function formatSessionSummaryLine(s, titleWidth = 60) {
16680
+ const title = s.title ?? "(untitled)";
16681
+ const shown = title.length > titleWidth ? `${title.slice(0, titleWidth - 1)}\u2026` : title;
16682
+ return `${s.id.slice(0, 12)} ${s.provider}/${s.model} ${String(s.messageCount).padStart(4)} msgs ${s.updated.toISOString().slice(0, 10)} ${shown}`;
16683
+ }
16684
+ function parseBatchDeleteArgs(args) {
16685
+ let messages;
16686
+ let dryRun = false;
16687
+ for (let i = 1; i < args.length; i++) {
16688
+ if (args[i] === "--filter" && i + 1 < args.length) {
16689
+ const val = args[++i];
16690
+ const eq = val.indexOf("=");
16691
+ if (eq <= 0) {
16692
+ return { dryRun, error: `Invalid --filter syntax: "${val}". Expected key=value (e.g., messages=0)` };
16693
+ }
16694
+ const key = val.slice(0, eq);
16695
+ const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
16696
+ if (key !== "messages") {
16697
+ return { dryRun, error: `Unsupported filter key for delete: "${key}". Only "messages" is supported.` };
16698
+ }
16699
+ const n = Number(vals[0]);
16700
+ if (!Number.isFinite(n) || n < 0) {
16701
+ return { dryRun, error: `Invalid filter value for "messages": expected a non-negative integer, got "${vals[0]}"` };
16702
+ }
16703
+ messages = n;
16704
+ } else if (args[i] === "--dry-run") {
16705
+ dryRun = true;
16706
+ }
16707
+ }
16708
+ if (messages === void 0) {
16709
+ return { dryRun, error: "Usage: /session delete --filter messages=N [--dry-run]" };
16710
+ }
16711
+ return { messages, dryRun };
16712
+ }
16713
+ function parseCleanArgs(args) {
16714
+ let staleDays = 90;
16715
+ let dryRun = false;
16716
+ for (let i = 1; i < args.length; i++) {
16717
+ if (args[i] === "--stale" && i + 1 < args.length) {
16718
+ const n = Number(args[++i]);
16719
+ if (Number.isFinite(n) && n > 0) staleDays = n;
16720
+ } else if (args[i] === "--dry-run") {
16721
+ dryRun = true;
16722
+ }
16723
+ }
16724
+ return { staleDays, dryRun };
16725
+ }
16726
+ function selectStaleSessions(sessions, staleDays, now = Date.now()) {
16727
+ const cutoff = now - staleDays * 24 * 60 * 60 * 1e3;
16728
+ return sessions.filter((s) => s.updated.getTime() < cutoff);
16729
+ }
16730
+
16410
16731
  // src/web/commands/session-commands.ts
16411
16732
  async function handleClear(_args, ctx) {
16412
16733
  ctx.saveIfNeeded();
@@ -16430,7 +16751,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
16430
16751
  Cost: ${formatCost(cost)}` : "";
16431
16752
  let memoryLine = "";
16432
16753
  try {
16433
- const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-JCENBMCJ.js");
16754
+ const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-E6WECUTW.js");
16434
16755
  const pending = countPendingMemories2(ctx.config.getConfigDir());
16435
16756
  if (pending > 0) memoryLine = `
16436
16757
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -16473,11 +16794,17 @@ async function handleSession(args, ctx) {
16473
16794
  ctx.sendSessionList();
16474
16795
  return;
16475
16796
  }
16476
- const list = ctx.sessions.listSessions();
16477
- const found = list.find((s) => s.id.startsWith(targetId));
16478
- if (found) {
16797
+ const ref = resolveSessionRef(ctx.sessions.listSessions(), targetId, "most-recent");
16798
+ if (ref.status === "ok") {
16799
+ const found = ref.match;
16479
16800
  ctx.sessions.loadSession(found.id);
16480
16801
  ctx.resetWebSessionUsage();
16802
+ if (ref.message) {
16803
+ ctx.send({
16804
+ type: "info",
16805
+ message: [ref.message, ...ref.ambiguousWith.slice(0, 5).map((m) => ` ${formatSessionSummaryLine(m)}`)].join("\n")
16806
+ });
16807
+ }
16481
16808
  ctx.send({ type: "info", message: `Loaded session: ${found.id.slice(0, 8)} "${found.title ?? ""}" (${found.messageCount} messages)` });
16482
16809
  const loadedSession = ctx.sessions.current;
16483
16810
  if (loadedSession && loadedSession.messages.length > 0) {
@@ -16507,52 +16834,81 @@ async function handleSession(args, ctx) {
16507
16834
  return;
16508
16835
  }
16509
16836
  if (sub === "list") {
16510
- ctx.sendSessionList();
16837
+ if (args.length <= 1) {
16838
+ ctx.sendSessionList();
16839
+ return;
16840
+ }
16841
+ const opts = parseSessionListArgs(args);
16842
+ const all = ctx.sessions.listSessions({ includeFileSize: sessionListNeedsFileSize(opts) });
16843
+ if (all.length === 0) {
16844
+ ctx.send({ type: "info", message: "No saved sessions." });
16845
+ return;
16846
+ }
16847
+ const view = selectSessions(all, opts);
16848
+ if (view.totalCount === 0) {
16849
+ ctx.send({
16850
+ type: "info",
16851
+ message: [...opts.warnings.map((w) => `\u26A0 ${w}`), "No sessions match the filter criteria."].join("\n")
16852
+ });
16853
+ return;
16854
+ }
16855
+ if (opts.json) {
16856
+ ctx.send({
16857
+ type: "info",
16858
+ message: JSON.stringify(view.shown.map((s) => sessionListJsonRow(s, view.needsFileSize)), null, 2)
16859
+ });
16860
+ return;
16861
+ }
16862
+ const lines = [
16863
+ ...opts.warnings.map((w) => `\u26A0 ${w}`),
16864
+ ...view.shown.map((s) => formatSessionSummaryLine(s)),
16865
+ `\u2500\u2500 ${sessionListSummary(view)}`
16866
+ ];
16867
+ if (view.shown.length < view.totalCount) lines.push("Use --all to show all, or --limit N to change count.");
16868
+ ctx.send({ type: "info", message: lines.join("\n") });
16511
16869
  return;
16512
16870
  }
16513
16871
  if (sub === "delete" && args[1]) {
16514
16872
  const targetId = args[1];
16515
16873
  if (targetId === "--filter") {
16516
- let filterMessages;
16517
- for (let i = 1; i < args.length; i++) {
16518
- if (args[i] === "--filter" && i + 1 < args.length) {
16519
- const val = args[i + 1];
16520
- i++;
16521
- const eq = val.indexOf("=");
16522
- if (eq > 0) {
16523
- const key = val.slice(0, eq);
16524
- const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
16525
- if (key === "messages") {
16526
- const n = Number(vals[0]);
16527
- if (Number.isFinite(n) && n >= 0) filterMessages = n;
16528
- }
16529
- }
16530
- }
16531
- }
16532
- if (filterMessages === void 0) {
16533
- ctx.send({ type: "error", message: "Usage: /session delete --filter messages=N" });
16874
+ const opts = parseBatchDeleteArgs(args);
16875
+ if (opts.error) {
16876
+ ctx.send({ type: "error", message: opts.error });
16534
16877
  return;
16535
16878
  }
16536
- const list2 = ctx.sessions.listSessions();
16537
- const matches2 = list2.filter((s) => s.messageCount === filterMessages);
16879
+ const matches2 = ctx.sessions.listSessions().filter((s) => s.messageCount === opts.messages);
16538
16880
  if (matches2.length === 0) {
16539
- ctx.send({ type: "info", message: `No sessions with ${filterMessages} message(s) found.` });
16881
+ ctx.send({ type: "info", message: `No sessions with ${opts.messages} message(s) found.` });
16882
+ return;
16883
+ }
16884
+ const preview = [
16885
+ `Found ${matches2.length} session(s) with ${opts.messages} message(s):`,
16886
+ ...matches2.slice(0, 20).map((s) => ` ${formatSessionSummaryLine(s)}`),
16887
+ ...matches2.length > 20 ? [` ... and ${matches2.length - 20} more`] : []
16888
+ ];
16889
+ if (opts.dryRun) {
16890
+ ctx.send({
16891
+ type: "info",
16892
+ message: [...preview, "--dry-run: no sessions were deleted. Remove --dry-run to actually delete."].join("\n")
16893
+ });
16540
16894
  return;
16541
16895
  }
16542
16896
  let deleted = 0;
16543
16897
  let failed = 0;
16544
16898
  for (const s of matches2) {
16545
- if (ctx.sessions.deleteSession(s.id)) {
16546
- deleted++;
16547
- } else {
16548
- failed++;
16549
- }
16899
+ if (ctx.sessions.deleteSession(s.id)) deleted++;
16900
+ else failed++;
16550
16901
  }
16551
16902
  if (!ctx.sessions.current) {
16552
- ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
16903
+ const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
16904
+ ctx.unsavedSessions.set(created.id, created);
16553
16905
  ctx.resetWebSessionUsage();
16554
16906
  }
16555
- ctx.send({ type: "info", message: `Deleted ${deleted} session(s) with ${filterMessages} message(s)${failed > 0 ? `, ${failed} failed` : ""}.` });
16907
+ ctx.send({
16908
+ type: "info",
16909
+ message: [...preview, `Deleted ${deleted} session(s)${failed > 0 ? `, ${failed} failed` : ""}.`].join("\n")
16910
+ });
16911
+ ctx.sendStatus();
16556
16912
  ctx.sendSessionList();
16557
16913
  return;
16558
16914
  }
@@ -16561,20 +16917,79 @@ async function handleSession(args, ctx) {
16561
16917
  ctx.unsavedSessions.delete(cachedKey);
16562
16918
  if (ctx.sessions.current?.id === cachedKey) {
16563
16919
  ctx.sessions.clearCurrent();
16920
+ const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
16921
+ ctx.unsavedSessions.set(created.id, created);
16922
+ ctx.resetWebSessionUsage();
16923
+ ctx.sendStatus();
16564
16924
  }
16565
16925
  ctx.send({ type: "info", message: `Deleted session: ${cachedKey.slice(0, 8)}` });
16566
16926
  ctx.sendSessionList();
16567
16927
  return;
16568
16928
  }
16569
- const list = ctx.sessions.listSessions();
16570
- const found = list.find((s) => s.id.startsWith(targetId));
16571
- if (found) {
16572
- ctx.sessions.deleteSession(found.id);
16573
- ctx.send({ type: "info", message: `Deleted session: ${found.id.slice(0, 8)}` });
16574
- ctx.sendSessionList();
16575
- } else {
16929
+ const ref = resolveSessionRef(ctx.sessions.listSessions(), targetId, "strict");
16930
+ if (ref.status === "not-found") {
16576
16931
  ctx.send({ type: "error", message: `Session not found: ${targetId}` });
16932
+ return;
16577
16933
  }
16934
+ if (ref.status === "ambiguous") {
16935
+ ctx.send({
16936
+ type: "error",
16937
+ message: [ref.message, ...ref.matches.slice(0, 10).map((m) => ` ${formatSessionSummaryLine(m)}`)].join("\n")
16938
+ });
16939
+ return;
16940
+ }
16941
+ const wasCurrent = ctx.sessions.current?.id === ref.match.id;
16942
+ if (!ctx.sessions.deleteSession(ref.match.id)) {
16943
+ ctx.send({ type: "error", message: "Delete failed \u2014 session file not found on disk." });
16944
+ return;
16945
+ }
16946
+ ctx.send({ type: "info", message: `Deleted session: ${ref.match.id.slice(0, 8)}` });
16947
+ if (wasCurrent) {
16948
+ const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
16949
+ ctx.unsavedSessions.set(created.id, created);
16950
+ ctx.resetWebSessionUsage();
16951
+ ctx.send({ type: "info", message: "Current session was deleted \u2014 started a fresh session." });
16952
+ ctx.sendStatus();
16953
+ }
16954
+ ctx.sendSessionList();
16955
+ return;
16956
+ }
16957
+ if (sub === "clean") {
16958
+ const opts = parseCleanArgs(args);
16959
+ const stale = selectStaleSessions(ctx.sessions.listSessions(), opts.staleDays);
16960
+ if (stale.length === 0) {
16961
+ ctx.send({ type: "info", message: `No sessions older than ${opts.staleDays} days found.` });
16962
+ return;
16963
+ }
16964
+ const preview = [
16965
+ `Found ${stale.length} session(s) not updated in ${opts.staleDays}+ days:`,
16966
+ ...stale.slice(0, 20).map((s) => ` ${formatSessionSummaryLine(s)}`),
16967
+ ...stale.length > 20 ? [` ... and ${stale.length - 20} more`] : []
16968
+ ];
16969
+ if (opts.dryRun) {
16970
+ ctx.send({
16971
+ type: "info",
16972
+ message: [...preview, "--dry-run: no sessions were deleted. Remove --dry-run to actually delete."].join("\n")
16973
+ });
16974
+ return;
16975
+ }
16976
+ let deleted = 0;
16977
+ let failed = 0;
16978
+ for (const s of stale) {
16979
+ if (ctx.sessions.deleteSession(s.id)) deleted++;
16980
+ else failed++;
16981
+ }
16982
+ if (!ctx.sessions.current) {
16983
+ const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
16984
+ ctx.unsavedSessions.set(created.id, created);
16985
+ ctx.resetWebSessionUsage();
16986
+ ctx.sendStatus();
16987
+ }
16988
+ ctx.send({
16989
+ type: "info",
16990
+ message: [...preview, `Cleaned up: ${deleted} deleted${failed > 0 ? `, ${failed} failed` : ""}.`].join("\n")
16991
+ });
16992
+ ctx.sendSessionList();
16578
16993
  return;
16579
16994
  }
16580
16995
  if (sub === "batch-delete") {
@@ -16598,6 +17013,12 @@ async function handleSession(args, ctx) {
16598
17013
  deleted++;
16599
17014
  }
16600
17015
  }
17016
+ if (!ctx.sessions.current) {
17017
+ const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
17018
+ ctx.unsavedSessions.set(created.id, created);
17019
+ ctx.resetWebSessionUsage();
17020
+ ctx.sendStatus();
17021
+ }
16601
17022
  ctx.send({ type: "info", message: `Deleted ${deleted} session(s).` });
16602
17023
  ctx.sendSessionList();
16603
17024
  return;
@@ -16611,26 +17032,40 @@ async function handleSession(args, ctx) {
16611
17032
  }
16612
17033
  const cachedKey = ctx.unsavedSessions.has(targetId) ? targetId : [...ctx.unsavedSessions.keys()].find((k) => k.startsWith(targetId));
16613
17034
  if (cachedKey) {
16614
- const session = ctx.unsavedSessions.get(cachedKey);
16615
- session.title = newTitle;
17035
+ const session2 = ctx.unsavedSessions.get(cachedKey);
17036
+ session2.title = newTitle;
16616
17037
  ctx.send({ type: "info", message: `Renamed session: "${newTitle}"` });
16617
17038
  ctx.sendSessionList();
16618
17039
  return;
16619
17040
  }
16620
- const list = ctx.sessions.listSessions();
16621
- const found = list.find((s) => s.id.startsWith(targetId));
16622
- if (found) {
16623
- const session = ctx.sessions.loadSession(found.id);
16624
- session.title = newTitle;
16625
- ctx.sessions.save();
16626
- ctx.send({ type: "info", message: `Renamed session: "${newTitle}"` });
16627
- ctx.sendSessionList();
16628
- } else {
17041
+ const ref = resolveSessionRef(ctx.sessions.listSessions(), targetId, "strict");
17042
+ if (ref.status === "not-found") {
16629
17043
  ctx.send({ type: "error", message: `Session not found: ${targetId}` });
17044
+ return;
17045
+ }
17046
+ if (ref.status === "ambiguous") {
17047
+ ctx.send({
17048
+ type: "error",
17049
+ message: [ref.message, ...ref.matches.slice(0, 10).map((m) => ` ${formatSessionSummaryLine(m)}`)].join("\n")
17050
+ });
17051
+ return;
16630
17052
  }
17053
+ const priorId = ctx.sessions.current?.id ?? null;
17054
+ const isCurrent = priorId === ref.match.id;
17055
+ const session = isCurrent ? ctx.sessions.current : ctx.sessions.loadSession(ref.match.id);
17056
+ session.title = newTitle;
17057
+ ctx.sessions.save();
17058
+ if (!isCurrent && priorId) {
17059
+ ctx.sessions.loadSession(priorId);
17060
+ }
17061
+ ctx.send({ type: "info", message: `Renamed session: "${newTitle}"` });
17062
+ ctx.sendSessionList();
16631
17063
  return;
16632
17064
  }
16633
- ctx.send({ type: "info", message: "Usage: /session new | list | load <id> | delete <id> | delete --filter messages=N | rename <id> <title>" });
17065
+ ctx.send({
17066
+ type: "info",
17067
+ message: "Usage: /session new | list [--filter k=v] [--search kw] [--limit N|--all] [--sort f[:o]] [--json] | load <id> | delete <id> | delete --filter messages=N [--dry-run] | clean --stale N [--dry-run] | rename <id> <title>"
17068
+ });
16634
17069
  }
16635
17070
  async function handleExport(args, ctx) {
16636
17071
  const format = args[0] === "json" ? "json" : "md";
@@ -16677,56 +17112,36 @@ async function handleCheckpoint(args, ctx) {
16677
17112
  ctx.send({ type: "error", message: "No active session." });
16678
17113
  return;
16679
17114
  }
16680
- const sub = args[0]?.toLowerCase();
16681
- if (!sub || sub === "list") {
16682
- const cps = session.checkpoints;
16683
- if (cps.length === 0) {
16684
- ctx.send({ type: "info", message: "No checkpoints. Use /checkpoint save <name> to create one." });
16685
- } else {
16686
- const lines = [`\u{1F4CC} Checkpoints (${cps.length}):`, ""];
16687
- for (const cp of cps) {
16688
- const ts = cp.timestamp.toLocaleString();
16689
- lines.push(` ${cp.name.padEnd(20)} msg #${cp.messageIndex} ${ts}`);
16690
- }
16691
- ctx.send({ type: "info", message: lines.join("\n") });
16692
- }
17115
+ const intent = parseCheckpointCommand(args);
17116
+ if (intent.kind === "list") {
17117
+ ctx.send({ type: "info", message: formatCheckpointList(session.checkpoints).join("\n") });
16693
17118
  return;
16694
17119
  }
16695
- const cpName = args.slice(1).join(" ").trim();
16696
- if (sub === "save") {
16697
- if (!cpName) {
16698
- ctx.send({ type: "error", message: "Usage: /checkpoint save <name>" });
16699
- return;
16700
- }
16701
- session.createCheckpoint(cpName);
16702
- ctx.send({ type: "info", message: `\u2713 Checkpoint "${cpName}" saved at message #${session.messages.length}` });
16703
- } else if (sub === "restore") {
16704
- if (!cpName) {
16705
- ctx.send({ type: "error", message: "Usage: /checkpoint restore <name>" });
16706
- return;
16707
- }
16708
- const ok = session.restoreCheckpoint(cpName);
16709
- if (ok) {
16710
- ctx.send({ type: "info", message: `\u2713 Restored to checkpoint "${cpName}" (${session.messages.length} messages)` });
16711
- ctx.sendSessionMessages();
16712
- ctx.sendStatus();
16713
- } else {
16714
- ctx.send({ type: "error", message: `Checkpoint "${cpName}" not found.` });
16715
- }
16716
- } else if (sub === "delete") {
16717
- if (!cpName) {
16718
- ctx.send({ type: "error", message: "Usage: /checkpoint delete <name>" });
17120
+ if (intent.kind === "usage" || intent.kind === "unknown") {
17121
+ ctx.send({ type: "error", message: intent.message });
17122
+ return;
17123
+ }
17124
+ if (intent.kind === "save") {
17125
+ session.createCheckpoint(intent.name);
17126
+ ctx.send({ type: "info", message: checkpointSavedMessage(intent.name, session.messages.length) });
17127
+ return;
17128
+ }
17129
+ if (intent.kind === "restore") {
17130
+ if (!session.restoreCheckpoint(intent.name)) {
17131
+ ctx.send({ type: "error", message: checkpointNotFoundMessage(intent.name) });
16719
17132
  return;
16720
17133
  }
16721
- const ok = session.deleteCheckpoint(cpName);
16722
- if (ok) {
16723
- ctx.send({ type: "info", message: `\u2713 Deleted checkpoint "${cpName}"` });
16724
- } else {
16725
- ctx.send({ type: "error", message: `Checkpoint "${cpName}" not found.` });
16726
- }
16727
- } else {
16728
- ctx.send({ type: "error", message: `Unknown subcommand: ${sub}. Use save/restore/list/delete.` });
17134
+ ctx.resetWebSessionUsage();
17135
+ ctx.send({ type: "info", message: checkpointRestoredMessage(intent.name, session.messages.length) });
17136
+ ctx.sendSessionMessages();
17137
+ ctx.sendStatus();
17138
+ return;
17139
+ }
17140
+ if (!session.deleteCheckpoint(intent.name)) {
17141
+ ctx.send({ type: "error", message: checkpointNotFoundMessage(intent.name) });
17142
+ return;
16729
17143
  }
17144
+ ctx.send({ type: "info", message: checkpointDeletedMessage(intent.name) });
16730
17145
  }
16731
17146
  async function handleBranch(args, ctx) {
16732
17147
  const session = ctx.sessions.current;
@@ -17243,7 +17658,7 @@ async function handleTest(args, ctx) {
17243
17658
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
17244
17659
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
17245
17660
  }
17246
- const runTests = ctx.runTests ?? (await import("./run-tests-2Q5MJHXB.js")).executeTests;
17661
+ const runTests = ctx.runTests ?? (await import("./run-tests-LWAFPVS3.js")).executeTests;
17247
17662
  const report = await runTests(testArgs);
17248
17663
  ctx.send({ type: "info", message: report });
17249
17664
  } catch (err) {
@@ -17579,7 +17994,7 @@ async function handleMemory(args, ctx) {
17579
17994
  ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
17580
17995
  } else if (sub === "export") {
17581
17996
  if (args[1] === "md") {
17582
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-JCENBMCJ.js");
17997
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-E6WECUTW.js");
17583
17998
  ctx.send({
17584
17999
  type: "export_data",
17585
18000
  format: "md",
@@ -18023,7 +18438,7 @@ async function handleHelp(_args, ctx) {
18023
18438
  " /think [on|off] \u2014 Toggle extended thinking mode",
18024
18439
  " /plan [execute|exit|status] \u2014 Read-only planning mode (bare /plan enters it)",
18025
18440
  " /route [on|off|show|test <msg>] \u2014 Smart model routing",
18026
- " /session new|list|load|delete <id> \u2014 Session management",
18441
+ " /session new|list [filters]|load <id>|delete <id>|delete --filter messages=N [--dry-run]|clean --stale N [--dry-run]|rename <id> <title> \u2014 Session management",
18027
18442
  " /system [prompt|clear] \u2014 Set/view/reset system prompt",
18028
18443
  " /context [status|reload] \u2014 Show/reload context layers",
18029
18444
  " /status \u2014 Show session info & token usage",
@@ -19424,43 +19839,13 @@ ${summaryContent}`,
19424
19839
  this.send({ type: "info", message: "No messages to export." });
19425
19840
  return;
19426
19841
  }
19427
- const title = session.title ?? "Untitled";
19428
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19429
- if (format === "json") {
19430
- const data = {
19431
- title,
19432
- provider: this.currentProvider,
19433
- model: this.currentModel,
19434
- exported: (/* @__PURE__ */ new Date()).toISOString(),
19435
- messages: session.messages.map((m) => ({
19436
- role: m.role,
19437
- content: getContentText(m.content),
19438
- timestamp: m.timestamp?.toISOString()
19439
- }))
19440
- };
19441
- this.send({
19442
- type: "export_data",
19443
- format: "json",
19444
- filename: `aicli-${timestamp}.json`,
19445
- content: JSON.stringify(data, null, 2)
19446
- });
19447
- } else {
19448
- const lines = [`# ${title}`, "", `> Exported: ${(/* @__PURE__ */ new Date()).toLocaleString()} | Provider: ${this.currentProvider} | Model: ${this.currentModel}`, ""];
19449
- for (const m of session.messages) {
19450
- const text = getContentText(m.content);
19451
- if (m.role === "user") {
19452
- lines.push(`## \u{1F9D1} User`, "", text, "");
19453
- } else if (m.role === "assistant") {
19454
- lines.push(`## \u{1F916} Assistant`, "", text, "");
19455
- }
19456
- }
19457
- this.send({
19458
- type: "export_data",
19459
- format: "md",
19460
- filename: `aicli-${timestamp}.md`,
19461
- content: lines.join("\n")
19462
- });
19463
- }
19842
+ const exported = buildSessionExport(session, format);
19843
+ this.send({
19844
+ type: "export_data",
19845
+ format,
19846
+ filename: exported.filename,
19847
+ content: exported.content
19848
+ });
19464
19849
  }
19465
19850
  memoryShow(args = []) {
19466
19851
  const configDir = this.config.getConfigDir();