jinzd-ai-cli 0.4.277 → 0.4.279
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{batch-2I5TPZA2.js → batch-6QZSWCBL.js} +2 -2
- package/dist/{chunk-ZP3RCZKM.js → chunk-2BKRWTEQ.js} +1 -1
- package/dist/{chunk-Z752LDYF.js → chunk-5XHEXRMN.js} +1 -1
- package/dist/{chunk-7BO5H2QI.js → chunk-6WMT7OQG.js} +1 -1
- package/dist/{chunk-HJVKCOAG.js → chunk-7L4TEK7Q.js} +419 -3
- package/dist/{chunk-7YYUITED.js → chunk-CMNXAJLH.js} +1 -1
- package/dist/{chunk-KIGKL3PZ.js → chunk-FQJFGGKN.js} +4 -4
- package/dist/{chunk-UWQCTTP4.js → chunk-IIXZ7IPN.js} +1 -1
- package/dist/{chunk-7IPMONKZ.js → chunk-JLOQBYU4.js} +3 -3
- package/dist/{chunk-I3PYD2YN.js → chunk-JS6VAPWM.js} +3 -3
- package/dist/{chunk-3VFOXEIL.js → chunk-NZPHEUJM.js} +1 -1
- package/dist/{chunk-HCF442WL.js → chunk-RJY3HJS4.js} +1 -1
- package/dist/{chunk-BYNNKJUC.js → chunk-SIVM2YQQ.js} +1 -1
- package/dist/{chunk-OCOGYIRY.js → chunk-UD3BVNC5.js} +1 -1
- package/dist/{ci-JFC45LIS.js → ci-F4ESOYI5.js} +4 -4
- package/dist/{ci-format-P3LIZHUR.js → ci-format-AIRW3TVQ.js} +2 -2
- package/dist/{constants-N3CHKZJY.js → constants-3VMMZJDS.js} +1 -1
- package/dist/{doctor-cli-Z3TGHTSX.js → doctor-cli-WVMEWMYE.js} +4 -4
- package/dist/electron-server.js +699 -101
- package/dist/{hub-NEWIFMQ3.js → hub-JJCCCU3O.js} +1 -1
- package/dist/index.js +243 -466
- package/dist/{persistent-memory-6Q5FDFJ7.js → persistent-memory-KSZKM4JG.js} +2 -2
- package/dist/{persistent-memory-ZFR5GI7D.js → persistent-memory-QZN4Q3W4.js} +2 -2
- package/dist/{pr-ZUGSWQ5R.js → pr-GFB5JC26.js} +4 -4
- package/dist/{run-tests-76M6AC3G.js → run-tests-EL54INOI.js} +2 -2
- package/dist/{run-tests-USV7IJ7X.js → run-tests-HMHHIG7Y.js} +2 -2
- package/dist/{server-R7D5RR36.js → server-6C7GITHY.js} +5 -5
- package/dist/{server-C523CIGN.js → server-WUSJWC3A.js} +324 -106
- package/dist/{task-orchestrator-G2MQDNGQ.js → task-orchestrator-C7RUUA2Q.js} +5 -5
- package/dist/{usage-EFHXXMYX.js → usage-CW6BT2UV.js} +2 -2
- package/dist/web/client/app.js +18 -3
- package/dist/web/client/app.js.br +0 -0
- package/dist/web/client/app.js.gz +0 -0
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -40,14 +40,14 @@ import {
|
|
|
40
40
|
touchMemoryReferences,
|
|
41
41
|
updateMemoryApproval,
|
|
42
42
|
updateMemoryEntry
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-CMNXAJLH.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-
|
|
50
|
+
} from "./chunk-SIVM2YQQ.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-
|
|
83
|
+
} from "./chunk-5XHEXRMN.js";
|
|
84
84
|
import {
|
|
85
85
|
hasSemanticIndex,
|
|
86
86
|
semanticSearch
|
|
@@ -15543,48 +15543,259 @@ function applyThinkCommand(sub, state2) {
|
|
|
15543
15543
|
return stay(`Unknown /think option "${sub}" \u2014 expected on | off | low | high | max | status`);
|
|
15544
15544
|
}
|
|
15545
15545
|
|
|
15546
|
+
// src/core/provider-command.ts
|
|
15547
|
+
function preservedSuffix(messageCount) {
|
|
15548
|
+
return messageCount > 0 ? ` (conversation preserved: ${messageCount} messages)` : "";
|
|
15549
|
+
}
|
|
15550
|
+
function resolveProviderSwitch(input) {
|
|
15551
|
+
const id = input.targetId;
|
|
15552
|
+
if (!input.configured) {
|
|
15553
|
+
return {
|
|
15554
|
+
status: "not-configured",
|
|
15555
|
+
message: `Provider '${id}' is not configured. Run: aicli config`
|
|
15556
|
+
};
|
|
15557
|
+
}
|
|
15558
|
+
if (input.availableModels.length === 0) {
|
|
15559
|
+
return {
|
|
15560
|
+
status: "no-models",
|
|
15561
|
+
message: `Provider '${id}' has no available models. ` + (id === "ollama" ? "Make sure Ollama is running (`ollama serve`) and has models (`ollama pull <model>`)." : "Check provider configuration.")
|
|
15562
|
+
};
|
|
15563
|
+
}
|
|
15564
|
+
if (id === input.currentProvider) {
|
|
15565
|
+
return { status: "unchanged", message: `Already using provider: ${id}` };
|
|
15566
|
+
}
|
|
15567
|
+
const model = input.configuredDefaultModel || input.providerDefaultModel;
|
|
15568
|
+
return {
|
|
15569
|
+
status: "switched",
|
|
15570
|
+
providerId: id,
|
|
15571
|
+
model,
|
|
15572
|
+
message: `Switched to provider: ${id} (${model})` + preservedSuffix(input.messageCount)
|
|
15573
|
+
};
|
|
15574
|
+
}
|
|
15575
|
+
function parseModelCommand(args, currentProvider) {
|
|
15576
|
+
const raw = args[0];
|
|
15577
|
+
if (!raw) return { kind: "select" };
|
|
15578
|
+
const sub = raw.toLowerCase();
|
|
15579
|
+
if (sub === "refresh") return { kind: "refresh", providerId: args[1] ?? currentProvider };
|
|
15580
|
+
if (sub === "cache") {
|
|
15581
|
+
if ((args[1] ?? "").toLowerCase() === "clear") return { kind: "cache-clear", providerId: args[2] };
|
|
15582
|
+
return { kind: "cache-show" };
|
|
15583
|
+
}
|
|
15584
|
+
return { kind: "set", modelId: raw };
|
|
15585
|
+
}
|
|
15586
|
+
function resolveModelSwitch(input) {
|
|
15587
|
+
if (input.targetModel === input.currentModel) {
|
|
15588
|
+
return { status: "unchanged", message: `Already using model: ${input.targetModel}` };
|
|
15589
|
+
}
|
|
15590
|
+
const known = input.knownModels.includes(input.targetModel);
|
|
15591
|
+
return {
|
|
15592
|
+
status: "switched",
|
|
15593
|
+
model: input.targetModel,
|
|
15594
|
+
message: `Switched to model: ${input.targetModel}` + preservedSuffix(input.messageCount),
|
|
15595
|
+
warning: known ? void 0 : `'${input.targetModel}' is not in the known model list for ${input.provider}. If the provider rejects it, check the spelling or run /model refresh ${input.provider}.`
|
|
15596
|
+
};
|
|
15597
|
+
}
|
|
15598
|
+
var PLAN_MODE_TOOL_HINT = [
|
|
15599
|
+
"read_file \xB7 list_dir \xB7 grep_files \xB7 glob_files",
|
|
15600
|
+
"web_fetch \xB7 google_search \xB7 ask_user \xB7 write_todos"
|
|
15601
|
+
];
|
|
15602
|
+
function applyPlanCommand(sub, active) {
|
|
15603
|
+
const key = (sub ?? "enter").toLowerCase() === "toggle" ? active ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
|
|
15604
|
+
if (key === "enter") {
|
|
15605
|
+
if (active) {
|
|
15606
|
+
return {
|
|
15607
|
+
planMode: true,
|
|
15608
|
+
changed: false,
|
|
15609
|
+
status: "already-active",
|
|
15610
|
+
summary: "Already in Plan Mode. Use /plan execute to start executing, or /plan exit to leave.",
|
|
15611
|
+
details: []
|
|
15612
|
+
};
|
|
15613
|
+
}
|
|
15614
|
+
return {
|
|
15615
|
+
planMode: true,
|
|
15616
|
+
changed: true,
|
|
15617
|
+
status: "entered",
|
|
15618
|
+
summary: "\u{1F4CB} Plan Mode activated",
|
|
15619
|
+
details: [
|
|
15620
|
+
"AI can now ONLY use read-only tools:",
|
|
15621
|
+
...PLAN_MODE_TOOL_HINT,
|
|
15622
|
+
"Describe your task and the AI will analyze the codebase and create a detailed implementation plan.",
|
|
15623
|
+
"When ready: /plan execute | To cancel: /plan exit"
|
|
15624
|
+
]
|
|
15625
|
+
};
|
|
15626
|
+
}
|
|
15627
|
+
if (key === "execute") {
|
|
15628
|
+
if (!active) {
|
|
15629
|
+
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode. Enter first with /plan", details: [] };
|
|
15630
|
+
}
|
|
15631
|
+
return {
|
|
15632
|
+
planMode: false,
|
|
15633
|
+
changed: true,
|
|
15634
|
+
status: "executing",
|
|
15635
|
+
summary: "Plan Mode ended \u2014 switching to execute mode. AI now has full tool access.",
|
|
15636
|
+
details: []
|
|
15637
|
+
};
|
|
15638
|
+
}
|
|
15639
|
+
if (key === "exit" || key === "cancel") {
|
|
15640
|
+
if (!active) {
|
|
15641
|
+
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode.", details: [] };
|
|
15642
|
+
}
|
|
15643
|
+
return { planMode: false, changed: true, status: "cancelled", summary: "Plan Mode cancelled. Returning to normal mode.", details: [] };
|
|
15644
|
+
}
|
|
15645
|
+
if (key === "status" || key === "show") {
|
|
15646
|
+
return {
|
|
15647
|
+
planMode: active,
|
|
15648
|
+
changed: false,
|
|
15649
|
+
status: "status",
|
|
15650
|
+
summary: `Plan Mode: ${active ? "ACTIVE \u{1F4CB}" : "inactive"}`,
|
|
15651
|
+
details: active ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
|
|
15652
|
+
};
|
|
15653
|
+
}
|
|
15654
|
+
return {
|
|
15655
|
+
planMode: active,
|
|
15656
|
+
changed: false,
|
|
15657
|
+
status: "unknown",
|
|
15658
|
+
summary: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status`,
|
|
15659
|
+
details: []
|
|
15660
|
+
};
|
|
15661
|
+
}
|
|
15662
|
+
function parseRouteCommand(args) {
|
|
15663
|
+
const sub = (args[0] ?? "show").toLowerCase();
|
|
15664
|
+
if (sub === "on" || sub === "enable") return { kind: "enable" };
|
|
15665
|
+
if (sub === "off" || sub === "disable") return { kind: "disable" };
|
|
15666
|
+
if (sub === "test") {
|
|
15667
|
+
const message = args.slice(1).join(" ").trim();
|
|
15668
|
+
return message ? { kind: "test", message } : { kind: "usage", message: "Usage: /route test <message>" };
|
|
15669
|
+
}
|
|
15670
|
+
if (sub === "show" || sub === "status") return { kind: "show" };
|
|
15671
|
+
return { kind: "unknown", message: `Unknown subcommand: ${sub}. Usage: /route [on|off|show|test <message>]` };
|
|
15672
|
+
}
|
|
15673
|
+
function routingEmptyRulesNote(routing) {
|
|
15674
|
+
if (routing && routing.rules.length > 0) return null;
|
|
15675
|
+
return 'No rules configured yet. Add rules under `routing.rules` in ~/.aicli/config.json.\nExample: { match: { tag: "fast" }, model: "claude-haiku-4-5" }';
|
|
15676
|
+
}
|
|
15677
|
+
function describeRoutingRule(rule, index) {
|
|
15678
|
+
const parts = [];
|
|
15679
|
+
if (rule.match.tag) parts.push(`tag=#${rule.match.tag}`);
|
|
15680
|
+
if (rule.match.contains && rule.match.contains.length > 0) {
|
|
15681
|
+
const shown = rule.match.contains.slice(0, 3).join(", ");
|
|
15682
|
+
parts.push(`contains=[${shown}${rule.match.contains.length > 3 ? ", \u2026" : ""}]`);
|
|
15683
|
+
}
|
|
15684
|
+
if (typeof rule.match.maxLength === "number") parts.push(`maxLen=${rule.match.maxLength}`);
|
|
15685
|
+
if (typeof rule.match.minLength === "number") parts.push(`minLen=${rule.match.minLength}`);
|
|
15686
|
+
const empty = parts.length === 0;
|
|
15687
|
+
return {
|
|
15688
|
+
index,
|
|
15689
|
+
name: rule.name,
|
|
15690
|
+
condition: empty ? "(empty \u2014 never matches)" : parts.join(" & "),
|
|
15691
|
+
model: rule.model,
|
|
15692
|
+
empty
|
|
15693
|
+
};
|
|
15694
|
+
}
|
|
15695
|
+
function describeRouting(input) {
|
|
15696
|
+
const rules = input.routing?.rules ?? [];
|
|
15697
|
+
return {
|
|
15698
|
+
enabled: input.routing?.enabled === true,
|
|
15699
|
+
provider: input.provider,
|
|
15700
|
+
currentModel: input.currentModel,
|
|
15701
|
+
fallback: input.routing?.fallback,
|
|
15702
|
+
rules: rules.map((r, i) => describeRoutingRule(r, i)),
|
|
15703
|
+
noRulesNote: rules.length === 0 ? "(no rules configured \u2014 edit ~/.aicli/config.json `routing.rules`)" : null,
|
|
15704
|
+
hint: "Commands: /route on | off | test <msg> | show"
|
|
15705
|
+
};
|
|
15706
|
+
}
|
|
15707
|
+
function routeDecisionRows(message, currentModel, decision) {
|
|
15708
|
+
const rows = [
|
|
15709
|
+
{ label: "Input", value: message },
|
|
15710
|
+
{ label: "Current", value: currentModel },
|
|
15711
|
+
{ label: "Decision", value: `${decision.model} ${decision.overridden ? "\u2192 ROUTED" : "(unchanged)"}` },
|
|
15712
|
+
{ label: "Reason", value: decision.reason }
|
|
15713
|
+
];
|
|
15714
|
+
if (typeof decision.ruleIdx === "number") rows.push({ label: "Rule", value: `#${decision.ruleIdx}` });
|
|
15715
|
+
return rows;
|
|
15716
|
+
}
|
|
15717
|
+
|
|
15546
15718
|
// src/web/commands/provider-commands.ts
|
|
15719
|
+
function syncSessionProvider(ctx) {
|
|
15720
|
+
const session = ctx.sessions.current;
|
|
15721
|
+
if (!session) return;
|
|
15722
|
+
session.updateProvider(ctx.currentProvider, ctx.currentModel);
|
|
15723
|
+
ctx.saveIfNeeded();
|
|
15724
|
+
}
|
|
15547
15725
|
async function handleProvider(args, ctx) {
|
|
15548
15726
|
const id = args[0];
|
|
15549
15727
|
if (!id) {
|
|
15550
15728
|
ctx.send({ type: "error", message: "Usage: /provider <id>" });
|
|
15551
15729
|
return;
|
|
15552
15730
|
}
|
|
15553
|
-
const
|
|
15554
|
-
|
|
15555
|
-
|
|
15731
|
+
const configured = ctx.providers.has(id);
|
|
15732
|
+
let availableModels = [];
|
|
15733
|
+
let providerDefaultModel = "";
|
|
15734
|
+
if (configured) {
|
|
15735
|
+
const target = ctx.providers.get(id);
|
|
15736
|
+
providerDefaultModel = target.info.defaultModel;
|
|
15737
|
+
availableModels = target.info.models.map((m) => m.id);
|
|
15738
|
+
if (availableModels.length === 0) {
|
|
15739
|
+
try {
|
|
15740
|
+
availableModels = (await target.listModels()).map((m) => m.id);
|
|
15741
|
+
} catch {
|
|
15742
|
+
availableModels = [];
|
|
15743
|
+
}
|
|
15744
|
+
}
|
|
15745
|
+
}
|
|
15746
|
+
const outcome = resolveProviderSwitch({
|
|
15747
|
+
targetId: id,
|
|
15748
|
+
currentProvider: ctx.currentProvider,
|
|
15749
|
+
configured,
|
|
15750
|
+
availableModels,
|
|
15751
|
+
configuredDefaultModel: ctx.config.get("defaultModels")[id],
|
|
15752
|
+
providerDefaultModel,
|
|
15753
|
+
messageCount: ctx.sessions.current?.messages.length ?? 0
|
|
15754
|
+
});
|
|
15755
|
+
if (outcome.status === "not-configured" || outcome.status === "no-models") {
|
|
15756
|
+
ctx.send({ type: "error", message: outcome.message });
|
|
15556
15757
|
return;
|
|
15557
15758
|
}
|
|
15558
|
-
|
|
15559
|
-
|
|
15759
|
+
if (outcome.status === "unchanged") {
|
|
15760
|
+
ctx.send({ type: "info", message: outcome.message });
|
|
15761
|
+
return;
|
|
15762
|
+
}
|
|
15763
|
+
ctx.currentProvider = outcome.providerId;
|
|
15764
|
+
ctx.currentModel = outcome.model;
|
|
15560
15765
|
ctx.updateContextWindow();
|
|
15561
|
-
|
|
15766
|
+
syncSessionProvider(ctx);
|
|
15767
|
+
ctx.send({ type: "info", message: outcome.message });
|
|
15562
15768
|
ctx.sendStatus();
|
|
15563
15769
|
}
|
|
15564
15770
|
async function handleModel(args, ctx) {
|
|
15565
|
-
const
|
|
15566
|
-
if (
|
|
15771
|
+
const intent = parseModelCommand(args, ctx.currentProvider);
|
|
15772
|
+
if (intent.kind === "select") {
|
|
15567
15773
|
ctx.send({ type: "error", message: "Usage: /model <id>|refresh [provider]|cache [clear [provider]]" });
|
|
15568
15774
|
return;
|
|
15569
15775
|
}
|
|
15570
|
-
if (
|
|
15571
|
-
|
|
15776
|
+
if (intent.kind === "refresh") {
|
|
15777
|
+
if (!ctx.providers.has(intent.providerId)) {
|
|
15778
|
+
ctx.send({ type: "error", message: `Provider is not configured: ${intent.providerId}` });
|
|
15779
|
+
return;
|
|
15780
|
+
}
|
|
15572
15781
|
try {
|
|
15573
|
-
const models2 = await ctx.providers.refreshModels(providerId);
|
|
15574
|
-
ctx.send({ type: "info", message: `Refreshed ${models2.length} model(s) for ${providerId}.` });
|
|
15782
|
+
const models2 = await ctx.providers.refreshModels(intent.providerId);
|
|
15783
|
+
ctx.send({ type: "info", message: `Refreshed ${models2.length} model(s) for ${intent.providerId}.` });
|
|
15575
15784
|
ctx.sendStatus();
|
|
15576
15785
|
} catch (err) {
|
|
15577
15786
|
ctx.send({ type: "error", message: `Model refresh failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
15578
15787
|
}
|
|
15579
15788
|
return;
|
|
15580
15789
|
}
|
|
15581
|
-
if (
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15790
|
+
if (intent.kind === "cache-clear") {
|
|
15791
|
+
ctx.providers.clearModelCache(intent.providerId);
|
|
15792
|
+
ctx.send({
|
|
15793
|
+
type: "info",
|
|
15794
|
+
message: intent.providerId ? `Cleared model cache for ${intent.providerId}.` : "Cleared model cache."
|
|
15795
|
+
});
|
|
15796
|
+
return;
|
|
15797
|
+
}
|
|
15798
|
+
if (intent.kind === "cache-show") {
|
|
15588
15799
|
const rows = ctx.providers.getModelCacheStatus();
|
|
15589
15800
|
ctx.send({
|
|
15590
15801
|
type: "info",
|
|
@@ -15592,16 +15803,25 @@ async function handleModel(args, ctx) {
|
|
|
15592
15803
|
});
|
|
15593
15804
|
return;
|
|
15594
15805
|
}
|
|
15595
|
-
const modelId = sub;
|
|
15596
15806
|
const models = await ctx.providers.listModels(ctx.currentProvider);
|
|
15597
|
-
const
|
|
15598
|
-
|
|
15599
|
-
ctx.
|
|
15807
|
+
const outcome = resolveModelSwitch({
|
|
15808
|
+
targetModel: intent.modelId,
|
|
15809
|
+
currentModel: ctx.currentModel,
|
|
15810
|
+
provider: ctx.currentProvider,
|
|
15811
|
+
knownModels: models.map((m) => m.id),
|
|
15812
|
+
messageCount: ctx.sessions.current?.messages.length ?? 0
|
|
15813
|
+
});
|
|
15814
|
+
if (outcome.status === "unchanged") {
|
|
15815
|
+
ctx.send({ type: "info", message: outcome.message });
|
|
15600
15816
|
return;
|
|
15601
15817
|
}
|
|
15602
|
-
ctx.currentModel =
|
|
15818
|
+
ctx.currentModel = outcome.model;
|
|
15603
15819
|
ctx.updateContextWindow();
|
|
15604
|
-
ctx
|
|
15820
|
+
syncSessionProvider(ctx);
|
|
15821
|
+
ctx.send({
|
|
15822
|
+
type: "info",
|
|
15823
|
+
message: outcome.warning ? `${outcome.message} \u2014 ${outcome.warning}` : outcome.message
|
|
15824
|
+
});
|
|
15605
15825
|
ctx.sendStatus();
|
|
15606
15826
|
}
|
|
15607
15827
|
async function handleThink(args, ctx) {
|
|
@@ -15611,7 +15831,7 @@ async function handleThink(args, ctx) {
|
|
|
15611
15831
|
runtimeEffort: ctx.runtimeThinkingEffort,
|
|
15612
15832
|
configuredThinking: modelParams?.thinking,
|
|
15613
15833
|
configuredEffort: modelParams?.thinkingEffort,
|
|
15614
|
-
supportsEffort: ctx.providers.get(ctx.currentProvider)
|
|
15834
|
+
supportsEffort: ctx.providers.has(ctx.currentProvider) ? ctx.providers.get(ctx.currentProvider).supportsReasoningEffort : false,
|
|
15615
15835
|
provider: ctx.currentProvider
|
|
15616
15836
|
});
|
|
15617
15837
|
if (outcome.changed) {
|
|
@@ -15625,24 +15845,59 @@ async function handleThink(args, ctx) {
|
|
|
15625
15845
|
if (outcome.changed) ctx.sendStatus();
|
|
15626
15846
|
}
|
|
15627
15847
|
async function handlePlan(args, ctx) {
|
|
15628
|
-
const
|
|
15629
|
-
if (
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15848
|
+
const outcome = applyPlanCommand(args[0], ctx.planMode);
|
|
15849
|
+
if (outcome.changed) ctx.planMode = outcome.planMode;
|
|
15850
|
+
ctx.send({
|
|
15851
|
+
type: outcome.status === "unknown" ? "error" : "info",
|
|
15852
|
+
message: [outcome.summary, ...outcome.details].join(" \u2014 ")
|
|
15853
|
+
});
|
|
15854
|
+
if (outcome.changed) ctx.sendStatus();
|
|
15855
|
+
}
|
|
15856
|
+
async function handleRoute(args, ctx) {
|
|
15857
|
+
const intent = parseRouteCommand(args);
|
|
15858
|
+
const routing = ctx.config.get("routing");
|
|
15859
|
+
if (intent.kind === "enable" || intent.kind === "disable") {
|
|
15860
|
+
const on = intent.kind === "enable";
|
|
15861
|
+
ctx.config.setByPath("routing.enabled", on ? "true" : "false");
|
|
15862
|
+
const note = on ? routingEmptyRulesNote(routing) : null;
|
|
15863
|
+
ctx.send({
|
|
15864
|
+
type: "info",
|
|
15865
|
+
message: `Smart model routing ${on ? "enabled" : "disabled"}.` + (note ? `
|
|
15866
|
+
${note}` : "")
|
|
15867
|
+
});
|
|
15868
|
+
ctx.sendStatus();
|
|
15637
15869
|
return;
|
|
15638
|
-
}
|
|
15639
|
-
|
|
15870
|
+
}
|
|
15871
|
+
if (intent.kind === "usage" || intent.kind === "unknown") {
|
|
15872
|
+
ctx.send({ type: "error", message: intent.message });
|
|
15640
15873
|
return;
|
|
15874
|
+
}
|
|
15875
|
+
if (intent.kind === "test") {
|
|
15876
|
+
const decision = ctx.computeRoutingDecision(intent.message);
|
|
15877
|
+
ctx.send({
|
|
15878
|
+
type: "info",
|
|
15879
|
+
message: routeDecisionRows(intent.message, ctx.currentModel, decision).map((row) => `${row.label}: ${row.value}`).join("\n")
|
|
15880
|
+
});
|
|
15881
|
+
return;
|
|
15882
|
+
}
|
|
15883
|
+
const view = describeRouting({ routing, provider: ctx.currentProvider, currentModel: ctx.currentModel });
|
|
15884
|
+
const lines = [
|
|
15885
|
+
"Smart Model Routing",
|
|
15886
|
+
`Status: ${view.enabled ? "enabled" : "disabled"}`,
|
|
15887
|
+
`Provider: ${view.provider}`,
|
|
15888
|
+
`Current: ${view.currentModel}`
|
|
15889
|
+
];
|
|
15890
|
+
if (view.fallback) lines.push(`Fallback: ${view.fallback}`);
|
|
15891
|
+
if (view.noRulesNote) {
|
|
15892
|
+
lines.push(view.noRulesNote);
|
|
15641
15893
|
} else {
|
|
15642
|
-
|
|
15643
|
-
|
|
15894
|
+
lines.push("Rules (evaluated top-to-bottom):");
|
|
15895
|
+
for (const rule of view.rules) {
|
|
15896
|
+
lines.push(` #${rule.index} ${rule.name ? `${rule.name} ` : ""}${rule.condition} \u2192 ${rule.model}`);
|
|
15897
|
+
}
|
|
15644
15898
|
}
|
|
15645
|
-
|
|
15899
|
+
lines.push(view.hint);
|
|
15900
|
+
ctx.send({ type: "info", message: lines.join("\n") });
|
|
15646
15901
|
}
|
|
15647
15902
|
|
|
15648
15903
|
// src/core/session-cost.ts
|
|
@@ -16152,6 +16407,232 @@ function lastAssistantText(messages) {
|
|
|
16152
16407
|
return void 0;
|
|
16153
16408
|
}
|
|
16154
16409
|
|
|
16410
|
+
// src/core/session-command.ts
|
|
16411
|
+
var DEFAULT_SESSION_LIST_LIMIT = 50;
|
|
16412
|
+
function parseSessionListArgs(args) {
|
|
16413
|
+
let limit = DEFAULT_SESSION_LIST_LIMIT;
|
|
16414
|
+
let all = false;
|
|
16415
|
+
const filter = {};
|
|
16416
|
+
const warnings = [];
|
|
16417
|
+
let search = null;
|
|
16418
|
+
let days = null;
|
|
16419
|
+
let since = null;
|
|
16420
|
+
let sort = { field: "updated", order: "desc" };
|
|
16421
|
+
let json = false;
|
|
16422
|
+
let stale = null;
|
|
16423
|
+
let verbose = false;
|
|
16424
|
+
for (let i = 1; i < args.length; i++) {
|
|
16425
|
+
const a = args[i];
|
|
16426
|
+
if (a === "--all" || a === "-a") {
|
|
16427
|
+
all = true;
|
|
16428
|
+
} else if ((a === "--limit" || a === "-n") && i + 1 < args.length) {
|
|
16429
|
+
const n = Number(args[i + 1]);
|
|
16430
|
+
if (Number.isFinite(n) && n > 0) {
|
|
16431
|
+
limit = n;
|
|
16432
|
+
i++;
|
|
16433
|
+
}
|
|
16434
|
+
} else if (a === "--filter" && i + 1 < args.length) {
|
|
16435
|
+
const val = args[++i];
|
|
16436
|
+
const eq = val.indexOf("=");
|
|
16437
|
+
if (eq > 0) {
|
|
16438
|
+
const key = val.slice(0, eq);
|
|
16439
|
+
const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
|
|
16440
|
+
if (key === "provider") filter.provider = vals;
|
|
16441
|
+
else if (key === "model") filter.model = vals;
|
|
16442
|
+
else if (key === "messages") {
|
|
16443
|
+
const n = Number(vals[0]);
|
|
16444
|
+
if (Number.isFinite(n) && n >= 0) filter.messages = n;
|
|
16445
|
+
else warnings.push(`Invalid filter value for "messages": expected a non-negative integer, got "${vals[0]}"`);
|
|
16446
|
+
} else {
|
|
16447
|
+
warnings.push(`Unknown filter key: "${key}". Supported keys: provider, model, messages`);
|
|
16448
|
+
}
|
|
16449
|
+
} else {
|
|
16450
|
+
warnings.push(`Invalid --filter syntax: "${val}". Expected key=value`);
|
|
16451
|
+
}
|
|
16452
|
+
} else if ((a === "--search" || a === "-s") && i + 1 < args.length) {
|
|
16453
|
+
search = args[++i];
|
|
16454
|
+
} else if (a === "--days" && i + 1 < args.length) {
|
|
16455
|
+
const n = Number(args[++i]);
|
|
16456
|
+
if (Number.isFinite(n) && n > 0) days = n;
|
|
16457
|
+
} else if (a === "--since" && i + 1 < args.length) {
|
|
16458
|
+
since = args[++i];
|
|
16459
|
+
} else if (a === "--sort" && i + 1 < args.length) {
|
|
16460
|
+
const sv = args[++i];
|
|
16461
|
+
const colon = sv.indexOf(":");
|
|
16462
|
+
const field = colon > 0 ? sv.slice(0, colon) : sv;
|
|
16463
|
+
const order = colon > 0 ? sv.slice(colon + 1) : "desc";
|
|
16464
|
+
if (field === "updated" || field === "created" || field === "messages" || field === "size") {
|
|
16465
|
+
sort = { field, order: order === "asc" ? "asc" : "desc" };
|
|
16466
|
+
}
|
|
16467
|
+
} else if (a === "--json") {
|
|
16468
|
+
json = true;
|
|
16469
|
+
} else if (a === "--verbose" || a === "-v") {
|
|
16470
|
+
verbose = true;
|
|
16471
|
+
} else if (a === "--stale" && i + 1 < args.length) {
|
|
16472
|
+
const n = Number(args[++i]);
|
|
16473
|
+
if (Number.isFinite(n) && n > 0) stale = n;
|
|
16474
|
+
}
|
|
16475
|
+
}
|
|
16476
|
+
if (all) limit = -1;
|
|
16477
|
+
return { limit, all, filter, search, days, since, sort, json, stale, verbose, warnings };
|
|
16478
|
+
}
|
|
16479
|
+
function sessionListNeedsFileSize(opts) {
|
|
16480
|
+
return opts.verbose || opts.sort.field === "size";
|
|
16481
|
+
}
|
|
16482
|
+
function selectSessions(sessions, opts, now = Date.now()) {
|
|
16483
|
+
let matched = sessions;
|
|
16484
|
+
const hasFilters = !!opts.filter.provider || !!opts.filter.model || opts.filter.messages !== void 0;
|
|
16485
|
+
const hasSearch = !!opts.search;
|
|
16486
|
+
const hasTimeFilter = opts.days !== null || opts.since !== null;
|
|
16487
|
+
const hasStale = opts.stale !== null;
|
|
16488
|
+
if (hasFilters) {
|
|
16489
|
+
const p = opts.filter.provider;
|
|
16490
|
+
const m = opts.filter.model;
|
|
16491
|
+
const msg = opts.filter.messages;
|
|
16492
|
+
matched = matched.filter((s) => {
|
|
16493
|
+
if (p && !p.includes(s.provider.toLowerCase())) return false;
|
|
16494
|
+
if (m && !m.some((kw) => s.model.toLowerCase().includes(kw))) return false;
|
|
16495
|
+
if (msg !== void 0 && s.messageCount !== msg) return false;
|
|
16496
|
+
return true;
|
|
16497
|
+
});
|
|
16498
|
+
}
|
|
16499
|
+
if (hasSearch) {
|
|
16500
|
+
const q = opts.search.toLowerCase();
|
|
16501
|
+
matched = matched.filter(
|
|
16502
|
+
(s) => (s.title ?? "").toLowerCase().includes(q) || s.id.toLowerCase().includes(q)
|
|
16503
|
+
);
|
|
16504
|
+
}
|
|
16505
|
+
if (hasTimeFilter) {
|
|
16506
|
+
let cutoff = 0;
|
|
16507
|
+
if (opts.since) cutoff = new Date(opts.since).getTime();
|
|
16508
|
+
else if (opts.days !== null) cutoff = now - opts.days * 24 * 60 * 60 * 1e3;
|
|
16509
|
+
if (!Number.isNaN(cutoff)) {
|
|
16510
|
+
matched = matched.filter((s) => s.updated.getTime() >= cutoff);
|
|
16511
|
+
}
|
|
16512
|
+
}
|
|
16513
|
+
const staleCutoff = hasStale ? now - opts.stale * 24 * 60 * 60 * 1e3 : null;
|
|
16514
|
+
if (staleCutoff !== null) {
|
|
16515
|
+
matched = matched.filter((s) => s.updated.getTime() < staleCutoff);
|
|
16516
|
+
}
|
|
16517
|
+
const defaultSort = opts.sort.field === "updated" && opts.sort.order === "desc";
|
|
16518
|
+
if (!defaultSort) {
|
|
16519
|
+
const mul = opts.sort.order === "asc" ? 1 : -1;
|
|
16520
|
+
matched = [...matched].sort((a, b) => {
|
|
16521
|
+
switch (opts.sort.field) {
|
|
16522
|
+
case "created":
|
|
16523
|
+
return (a.created.getTime() - b.created.getTime()) * mul;
|
|
16524
|
+
case "messages":
|
|
16525
|
+
return (a.messageCount - b.messageCount) * mul;
|
|
16526
|
+
case "size":
|
|
16527
|
+
return ((a.fileSize ?? 0) - (b.fileSize ?? 0)) * mul;
|
|
16528
|
+
default:
|
|
16529
|
+
return (a.updated.getTime() - b.updated.getTime()) * mul;
|
|
16530
|
+
}
|
|
16531
|
+
});
|
|
16532
|
+
}
|
|
16533
|
+
const filterTags = [];
|
|
16534
|
+
if (hasFilters) filterTags.push("filtered");
|
|
16535
|
+
if (hasSearch) filterTags.push("searched");
|
|
16536
|
+
if (hasTimeFilter) filterTags.push("time-limited");
|
|
16537
|
+
if (hasStale) filterTags.push(`stale >${opts.stale}d`);
|
|
16538
|
+
return {
|
|
16539
|
+
matched,
|
|
16540
|
+
shown: opts.limit > 0 ? matched.slice(0, opts.limit) : matched,
|
|
16541
|
+
totalCount: matched.length,
|
|
16542
|
+
totalMessages: matched.reduce((sum, s) => sum + s.messageCount, 0),
|
|
16543
|
+
staleCutoff,
|
|
16544
|
+
filterTags,
|
|
16545
|
+
needsFileSize: sessionListNeedsFileSize(opts)
|
|
16546
|
+
};
|
|
16547
|
+
}
|
|
16548
|
+
function sessionListSummary(view) {
|
|
16549
|
+
const tag = view.filterTags.length > 0 ? ` (${view.filterTags.join(", ")})` : "";
|
|
16550
|
+
return `${view.shown.length} of ${view.totalCount} sessions${tag} \xB7 ${view.totalMessages} messages total`;
|
|
16551
|
+
}
|
|
16552
|
+
function sessionListJsonRow(s, includeFileSize) {
|
|
16553
|
+
return {
|
|
16554
|
+
id: s.id,
|
|
16555
|
+
title: s.title ?? null,
|
|
16556
|
+
provider: s.provider,
|
|
16557
|
+
model: s.model,
|
|
16558
|
+
messageCount: s.messageCount,
|
|
16559
|
+
created: s.created.toISOString(),
|
|
16560
|
+
updated: s.updated.toISOString(),
|
|
16561
|
+
...includeFileSize ? { fileSize: s.fileSize } : {},
|
|
16562
|
+
...s.branchCount !== void 0 ? { branchCount: s.branchCount, activeBranch: s.activeBranch } : {}
|
|
16563
|
+
};
|
|
16564
|
+
}
|
|
16565
|
+
function resolveSessionRef(sessions, prefix, policy) {
|
|
16566
|
+
const matches2 = sessions.filter((s) => s.id.startsWith(prefix));
|
|
16567
|
+
if (matches2.length === 0) {
|
|
16568
|
+
return { status: "not-found", message: `Session '${prefix}' not found.` };
|
|
16569
|
+
}
|
|
16570
|
+
if (matches2.length > 1 && policy === "strict") {
|
|
16571
|
+
return {
|
|
16572
|
+
status: "ambiguous",
|
|
16573
|
+
matches: matches2,
|
|
16574
|
+
message: `Ambiguous prefix '${prefix}' matches ${matches2.length} sessions \u2014 be more specific:`
|
|
16575
|
+
};
|
|
16576
|
+
}
|
|
16577
|
+
return {
|
|
16578
|
+
status: "ok",
|
|
16579
|
+
match: matches2[0],
|
|
16580
|
+
ambiguousWith: matches2.length > 1 ? matches2 : [],
|
|
16581
|
+
message: matches2.length > 1 ? `\u26A0 Ambiguous prefix '${prefix}' matches ${matches2.length} sessions \u2014 loading most recent:` : void 0
|
|
16582
|
+
};
|
|
16583
|
+
}
|
|
16584
|
+
function formatSessionSummaryLine(s, titleWidth = 60) {
|
|
16585
|
+
const title = s.title ?? "(untitled)";
|
|
16586
|
+
const shown = title.length > titleWidth ? `${title.slice(0, titleWidth - 1)}\u2026` : title;
|
|
16587
|
+
return `${s.id.slice(0, 12)} ${s.provider}/${s.model} ${String(s.messageCount).padStart(4)} msgs ${s.updated.toISOString().slice(0, 10)} ${shown}`;
|
|
16588
|
+
}
|
|
16589
|
+
function parseBatchDeleteArgs(args) {
|
|
16590
|
+
let messages;
|
|
16591
|
+
let dryRun = false;
|
|
16592
|
+
for (let i = 1; i < args.length; i++) {
|
|
16593
|
+
if (args[i] === "--filter" && i + 1 < args.length) {
|
|
16594
|
+
const val = args[++i];
|
|
16595
|
+
const eq = val.indexOf("=");
|
|
16596
|
+
if (eq <= 0) {
|
|
16597
|
+
return { dryRun, error: `Invalid --filter syntax: "${val}". Expected key=value (e.g., messages=0)` };
|
|
16598
|
+
}
|
|
16599
|
+
const key = val.slice(0, eq);
|
|
16600
|
+
const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
|
|
16601
|
+
if (key !== "messages") {
|
|
16602
|
+
return { dryRun, error: `Unsupported filter key for delete: "${key}". Only "messages" is supported.` };
|
|
16603
|
+
}
|
|
16604
|
+
const n = Number(vals[0]);
|
|
16605
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
16606
|
+
return { dryRun, error: `Invalid filter value for "messages": expected a non-negative integer, got "${vals[0]}"` };
|
|
16607
|
+
}
|
|
16608
|
+
messages = n;
|
|
16609
|
+
} else if (args[i] === "--dry-run") {
|
|
16610
|
+
dryRun = true;
|
|
16611
|
+
}
|
|
16612
|
+
}
|
|
16613
|
+
if (messages === void 0) {
|
|
16614
|
+
return { dryRun, error: "Usage: /session delete --filter messages=N [--dry-run]" };
|
|
16615
|
+
}
|
|
16616
|
+
return { messages, dryRun };
|
|
16617
|
+
}
|
|
16618
|
+
function parseCleanArgs(args) {
|
|
16619
|
+
let staleDays = 90;
|
|
16620
|
+
let dryRun = false;
|
|
16621
|
+
for (let i = 1; i < args.length; i++) {
|
|
16622
|
+
if (args[i] === "--stale" && i + 1 < args.length) {
|
|
16623
|
+
const n = Number(args[++i]);
|
|
16624
|
+
if (Number.isFinite(n) && n > 0) staleDays = n;
|
|
16625
|
+
} else if (args[i] === "--dry-run") {
|
|
16626
|
+
dryRun = true;
|
|
16627
|
+
}
|
|
16628
|
+
}
|
|
16629
|
+
return { staleDays, dryRun };
|
|
16630
|
+
}
|
|
16631
|
+
function selectStaleSessions(sessions, staleDays, now = Date.now()) {
|
|
16632
|
+
const cutoff = now - staleDays * 24 * 60 * 60 * 1e3;
|
|
16633
|
+
return sessions.filter((s) => s.updated.getTime() < cutoff);
|
|
16634
|
+
}
|
|
16635
|
+
|
|
16155
16636
|
// src/web/commands/session-commands.ts
|
|
16156
16637
|
async function handleClear(_args, ctx) {
|
|
16157
16638
|
ctx.saveIfNeeded();
|
|
@@ -16175,7 +16656,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
16175
16656
|
Cost: ${formatCost(cost)}` : "";
|
|
16176
16657
|
let memoryLine = "";
|
|
16177
16658
|
try {
|
|
16178
|
-
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-
|
|
16659
|
+
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-QZN4Q3W4.js");
|
|
16179
16660
|
const pending = countPendingMemories2(ctx.config.getConfigDir());
|
|
16180
16661
|
if (pending > 0) memoryLine = `
|
|
16181
16662
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -16218,11 +16699,17 @@ async function handleSession(args, ctx) {
|
|
|
16218
16699
|
ctx.sendSessionList();
|
|
16219
16700
|
return;
|
|
16220
16701
|
}
|
|
16221
|
-
const
|
|
16222
|
-
|
|
16223
|
-
|
|
16702
|
+
const ref = resolveSessionRef(ctx.sessions.listSessions(), targetId, "most-recent");
|
|
16703
|
+
if (ref.status === "ok") {
|
|
16704
|
+
const found = ref.match;
|
|
16224
16705
|
ctx.sessions.loadSession(found.id);
|
|
16225
16706
|
ctx.resetWebSessionUsage();
|
|
16707
|
+
if (ref.message) {
|
|
16708
|
+
ctx.send({
|
|
16709
|
+
type: "info",
|
|
16710
|
+
message: [ref.message, ...ref.ambiguousWith.slice(0, 5).map((m) => ` ${formatSessionSummaryLine(m)}`)].join("\n")
|
|
16711
|
+
});
|
|
16712
|
+
}
|
|
16226
16713
|
ctx.send({ type: "info", message: `Loaded session: ${found.id.slice(0, 8)} "${found.title ?? ""}" (${found.messageCount} messages)` });
|
|
16227
16714
|
const loadedSession = ctx.sessions.current;
|
|
16228
16715
|
if (loadedSession && loadedSession.messages.length > 0) {
|
|
@@ -16252,52 +16739,81 @@ async function handleSession(args, ctx) {
|
|
|
16252
16739
|
return;
|
|
16253
16740
|
}
|
|
16254
16741
|
if (sub === "list") {
|
|
16255
|
-
|
|
16742
|
+
if (args.length <= 1) {
|
|
16743
|
+
ctx.sendSessionList();
|
|
16744
|
+
return;
|
|
16745
|
+
}
|
|
16746
|
+
const opts = parseSessionListArgs(args);
|
|
16747
|
+
const all = ctx.sessions.listSessions({ includeFileSize: sessionListNeedsFileSize(opts) });
|
|
16748
|
+
if (all.length === 0) {
|
|
16749
|
+
ctx.send({ type: "info", message: "No saved sessions." });
|
|
16750
|
+
return;
|
|
16751
|
+
}
|
|
16752
|
+
const view = selectSessions(all, opts);
|
|
16753
|
+
if (view.totalCount === 0) {
|
|
16754
|
+
ctx.send({
|
|
16755
|
+
type: "info",
|
|
16756
|
+
message: [...opts.warnings.map((w) => `\u26A0 ${w}`), "No sessions match the filter criteria."].join("\n")
|
|
16757
|
+
});
|
|
16758
|
+
return;
|
|
16759
|
+
}
|
|
16760
|
+
if (opts.json) {
|
|
16761
|
+
ctx.send({
|
|
16762
|
+
type: "info",
|
|
16763
|
+
message: JSON.stringify(view.shown.map((s) => sessionListJsonRow(s, view.needsFileSize)), null, 2)
|
|
16764
|
+
});
|
|
16765
|
+
return;
|
|
16766
|
+
}
|
|
16767
|
+
const lines = [
|
|
16768
|
+
...opts.warnings.map((w) => `\u26A0 ${w}`),
|
|
16769
|
+
...view.shown.map((s) => formatSessionSummaryLine(s)),
|
|
16770
|
+
`\u2500\u2500 ${sessionListSummary(view)}`
|
|
16771
|
+
];
|
|
16772
|
+
if (view.shown.length < view.totalCount) lines.push("Use --all to show all, or --limit N to change count.");
|
|
16773
|
+
ctx.send({ type: "info", message: lines.join("\n") });
|
|
16256
16774
|
return;
|
|
16257
16775
|
}
|
|
16258
16776
|
if (sub === "delete" && args[1]) {
|
|
16259
16777
|
const targetId = args[1];
|
|
16260
16778
|
if (targetId === "--filter") {
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
const val = args[i + 1];
|
|
16265
|
-
i++;
|
|
16266
|
-
const eq = val.indexOf("=");
|
|
16267
|
-
if (eq > 0) {
|
|
16268
|
-
const key = val.slice(0, eq);
|
|
16269
|
-
const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
|
|
16270
|
-
if (key === "messages") {
|
|
16271
|
-
const n = Number(vals[0]);
|
|
16272
|
-
if (Number.isFinite(n) && n >= 0) filterMessages = n;
|
|
16273
|
-
}
|
|
16274
|
-
}
|
|
16275
|
-
}
|
|
16276
|
-
}
|
|
16277
|
-
if (filterMessages === void 0) {
|
|
16278
|
-
ctx.send({ type: "error", message: "Usage: /session delete --filter messages=N" });
|
|
16779
|
+
const opts = parseBatchDeleteArgs(args);
|
|
16780
|
+
if (opts.error) {
|
|
16781
|
+
ctx.send({ type: "error", message: opts.error });
|
|
16279
16782
|
return;
|
|
16280
16783
|
}
|
|
16281
|
-
const
|
|
16282
|
-
const matches2 = list2.filter((s) => s.messageCount === filterMessages);
|
|
16784
|
+
const matches2 = ctx.sessions.listSessions().filter((s) => s.messageCount === opts.messages);
|
|
16283
16785
|
if (matches2.length === 0) {
|
|
16284
|
-
ctx.send({ type: "info", message: `No sessions with ${
|
|
16786
|
+
ctx.send({ type: "info", message: `No sessions with ${opts.messages} message(s) found.` });
|
|
16787
|
+
return;
|
|
16788
|
+
}
|
|
16789
|
+
const preview = [
|
|
16790
|
+
`Found ${matches2.length} session(s) with ${opts.messages} message(s):`,
|
|
16791
|
+
...matches2.slice(0, 20).map((s) => ` ${formatSessionSummaryLine(s)}`),
|
|
16792
|
+
...matches2.length > 20 ? [` ... and ${matches2.length - 20} more`] : []
|
|
16793
|
+
];
|
|
16794
|
+
if (opts.dryRun) {
|
|
16795
|
+
ctx.send({
|
|
16796
|
+
type: "info",
|
|
16797
|
+
message: [...preview, "--dry-run: no sessions were deleted. Remove --dry-run to actually delete."].join("\n")
|
|
16798
|
+
});
|
|
16285
16799
|
return;
|
|
16286
16800
|
}
|
|
16287
16801
|
let deleted = 0;
|
|
16288
16802
|
let failed = 0;
|
|
16289
16803
|
for (const s of matches2) {
|
|
16290
|
-
if (ctx.sessions.deleteSession(s.id))
|
|
16291
|
-
|
|
16292
|
-
} else {
|
|
16293
|
-
failed++;
|
|
16294
|
-
}
|
|
16804
|
+
if (ctx.sessions.deleteSession(s.id)) deleted++;
|
|
16805
|
+
else failed++;
|
|
16295
16806
|
}
|
|
16296
16807
|
if (!ctx.sessions.current) {
|
|
16297
|
-
ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
16808
|
+
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
16809
|
+
ctx.unsavedSessions.set(created.id, created);
|
|
16298
16810
|
ctx.resetWebSessionUsage();
|
|
16299
16811
|
}
|
|
16300
|
-
ctx.send({
|
|
16812
|
+
ctx.send({
|
|
16813
|
+
type: "info",
|
|
16814
|
+
message: [...preview, `Deleted ${deleted} session(s)${failed > 0 ? `, ${failed} failed` : ""}.`].join("\n")
|
|
16815
|
+
});
|
|
16816
|
+
ctx.sendStatus();
|
|
16301
16817
|
ctx.sendSessionList();
|
|
16302
16818
|
return;
|
|
16303
16819
|
}
|
|
@@ -16306,20 +16822,79 @@ async function handleSession(args, ctx) {
|
|
|
16306
16822
|
ctx.unsavedSessions.delete(cachedKey);
|
|
16307
16823
|
if (ctx.sessions.current?.id === cachedKey) {
|
|
16308
16824
|
ctx.sessions.clearCurrent();
|
|
16825
|
+
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
16826
|
+
ctx.unsavedSessions.set(created.id, created);
|
|
16827
|
+
ctx.resetWebSessionUsage();
|
|
16828
|
+
ctx.sendStatus();
|
|
16309
16829
|
}
|
|
16310
16830
|
ctx.send({ type: "info", message: `Deleted session: ${cachedKey.slice(0, 8)}` });
|
|
16311
16831
|
ctx.sendSessionList();
|
|
16312
16832
|
return;
|
|
16313
16833
|
}
|
|
16314
|
-
const
|
|
16315
|
-
|
|
16316
|
-
if (found) {
|
|
16317
|
-
ctx.sessions.deleteSession(found.id);
|
|
16318
|
-
ctx.send({ type: "info", message: `Deleted session: ${found.id.slice(0, 8)}` });
|
|
16319
|
-
ctx.sendSessionList();
|
|
16320
|
-
} else {
|
|
16834
|
+
const ref = resolveSessionRef(ctx.sessions.listSessions(), targetId, "strict");
|
|
16835
|
+
if (ref.status === "not-found") {
|
|
16321
16836
|
ctx.send({ type: "error", message: `Session not found: ${targetId}` });
|
|
16837
|
+
return;
|
|
16838
|
+
}
|
|
16839
|
+
if (ref.status === "ambiguous") {
|
|
16840
|
+
ctx.send({
|
|
16841
|
+
type: "error",
|
|
16842
|
+
message: [ref.message, ...ref.matches.slice(0, 10).map((m) => ` ${formatSessionSummaryLine(m)}`)].join("\n")
|
|
16843
|
+
});
|
|
16844
|
+
return;
|
|
16845
|
+
}
|
|
16846
|
+
const wasCurrent = ctx.sessions.current?.id === ref.match.id;
|
|
16847
|
+
if (!ctx.sessions.deleteSession(ref.match.id)) {
|
|
16848
|
+
ctx.send({ type: "error", message: "Delete failed \u2014 session file not found on disk." });
|
|
16849
|
+
return;
|
|
16322
16850
|
}
|
|
16851
|
+
ctx.send({ type: "info", message: `Deleted session: ${ref.match.id.slice(0, 8)}` });
|
|
16852
|
+
if (wasCurrent) {
|
|
16853
|
+
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
16854
|
+
ctx.unsavedSessions.set(created.id, created);
|
|
16855
|
+
ctx.resetWebSessionUsage();
|
|
16856
|
+
ctx.send({ type: "info", message: "Current session was deleted \u2014 started a fresh session." });
|
|
16857
|
+
ctx.sendStatus();
|
|
16858
|
+
}
|
|
16859
|
+
ctx.sendSessionList();
|
|
16860
|
+
return;
|
|
16861
|
+
}
|
|
16862
|
+
if (sub === "clean") {
|
|
16863
|
+
const opts = parseCleanArgs(args);
|
|
16864
|
+
const stale = selectStaleSessions(ctx.sessions.listSessions(), opts.staleDays);
|
|
16865
|
+
if (stale.length === 0) {
|
|
16866
|
+
ctx.send({ type: "info", message: `No sessions older than ${opts.staleDays} days found.` });
|
|
16867
|
+
return;
|
|
16868
|
+
}
|
|
16869
|
+
const preview = [
|
|
16870
|
+
`Found ${stale.length} session(s) not updated in ${opts.staleDays}+ days:`,
|
|
16871
|
+
...stale.slice(0, 20).map((s) => ` ${formatSessionSummaryLine(s)}`),
|
|
16872
|
+
...stale.length > 20 ? [` ... and ${stale.length - 20} more`] : []
|
|
16873
|
+
];
|
|
16874
|
+
if (opts.dryRun) {
|
|
16875
|
+
ctx.send({
|
|
16876
|
+
type: "info",
|
|
16877
|
+
message: [...preview, "--dry-run: no sessions were deleted. Remove --dry-run to actually delete."].join("\n")
|
|
16878
|
+
});
|
|
16879
|
+
return;
|
|
16880
|
+
}
|
|
16881
|
+
let deleted = 0;
|
|
16882
|
+
let failed = 0;
|
|
16883
|
+
for (const s of stale) {
|
|
16884
|
+
if (ctx.sessions.deleteSession(s.id)) deleted++;
|
|
16885
|
+
else failed++;
|
|
16886
|
+
}
|
|
16887
|
+
if (!ctx.sessions.current) {
|
|
16888
|
+
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
16889
|
+
ctx.unsavedSessions.set(created.id, created);
|
|
16890
|
+
ctx.resetWebSessionUsage();
|
|
16891
|
+
ctx.sendStatus();
|
|
16892
|
+
}
|
|
16893
|
+
ctx.send({
|
|
16894
|
+
type: "info",
|
|
16895
|
+
message: [...preview, `Cleaned up: ${deleted} deleted${failed > 0 ? `, ${failed} failed` : ""}.`].join("\n")
|
|
16896
|
+
});
|
|
16897
|
+
ctx.sendSessionList();
|
|
16323
16898
|
return;
|
|
16324
16899
|
}
|
|
16325
16900
|
if (sub === "batch-delete") {
|
|
@@ -16343,6 +16918,12 @@ async function handleSession(args, ctx) {
|
|
|
16343
16918
|
deleted++;
|
|
16344
16919
|
}
|
|
16345
16920
|
}
|
|
16921
|
+
if (!ctx.sessions.current) {
|
|
16922
|
+
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
16923
|
+
ctx.unsavedSessions.set(created.id, created);
|
|
16924
|
+
ctx.resetWebSessionUsage();
|
|
16925
|
+
ctx.sendStatus();
|
|
16926
|
+
}
|
|
16346
16927
|
ctx.send({ type: "info", message: `Deleted ${deleted} session(s).` });
|
|
16347
16928
|
ctx.sendSessionList();
|
|
16348
16929
|
return;
|
|
@@ -16356,26 +16937,40 @@ async function handleSession(args, ctx) {
|
|
|
16356
16937
|
}
|
|
16357
16938
|
const cachedKey = ctx.unsavedSessions.has(targetId) ? targetId : [...ctx.unsavedSessions.keys()].find((k) => k.startsWith(targetId));
|
|
16358
16939
|
if (cachedKey) {
|
|
16359
|
-
const
|
|
16360
|
-
|
|
16940
|
+
const session2 = ctx.unsavedSessions.get(cachedKey);
|
|
16941
|
+
session2.title = newTitle;
|
|
16361
16942
|
ctx.send({ type: "info", message: `Renamed session: "${newTitle}"` });
|
|
16362
16943
|
ctx.sendSessionList();
|
|
16363
16944
|
return;
|
|
16364
16945
|
}
|
|
16365
|
-
const
|
|
16366
|
-
|
|
16367
|
-
if (found) {
|
|
16368
|
-
const session = ctx.sessions.loadSession(found.id);
|
|
16369
|
-
session.title = newTitle;
|
|
16370
|
-
ctx.sessions.save();
|
|
16371
|
-
ctx.send({ type: "info", message: `Renamed session: "${newTitle}"` });
|
|
16372
|
-
ctx.sendSessionList();
|
|
16373
|
-
} else {
|
|
16946
|
+
const ref = resolveSessionRef(ctx.sessions.listSessions(), targetId, "strict");
|
|
16947
|
+
if (ref.status === "not-found") {
|
|
16374
16948
|
ctx.send({ type: "error", message: `Session not found: ${targetId}` });
|
|
16949
|
+
return;
|
|
16950
|
+
}
|
|
16951
|
+
if (ref.status === "ambiguous") {
|
|
16952
|
+
ctx.send({
|
|
16953
|
+
type: "error",
|
|
16954
|
+
message: [ref.message, ...ref.matches.slice(0, 10).map((m) => ` ${formatSessionSummaryLine(m)}`)].join("\n")
|
|
16955
|
+
});
|
|
16956
|
+
return;
|
|
16375
16957
|
}
|
|
16958
|
+
const priorId = ctx.sessions.current?.id ?? null;
|
|
16959
|
+
const isCurrent = priorId === ref.match.id;
|
|
16960
|
+
const session = isCurrent ? ctx.sessions.current : ctx.sessions.loadSession(ref.match.id);
|
|
16961
|
+
session.title = newTitle;
|
|
16962
|
+
ctx.sessions.save();
|
|
16963
|
+
if (!isCurrent && priorId) {
|
|
16964
|
+
ctx.sessions.loadSession(priorId);
|
|
16965
|
+
}
|
|
16966
|
+
ctx.send({ type: "info", message: `Renamed session: "${newTitle}"` });
|
|
16967
|
+
ctx.sendSessionList();
|
|
16376
16968
|
return;
|
|
16377
16969
|
}
|
|
16378
|
-
ctx.send({
|
|
16970
|
+
ctx.send({
|
|
16971
|
+
type: "info",
|
|
16972
|
+
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>"
|
|
16973
|
+
});
|
|
16379
16974
|
}
|
|
16380
16975
|
async function handleExport(args, ctx) {
|
|
16381
16976
|
const format = args[0] === "json" ? "json" : "md";
|
|
@@ -16988,7 +17583,7 @@ async function handleTest(args, ctx) {
|
|
|
16988
17583
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
16989
17584
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
16990
17585
|
}
|
|
16991
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
17586
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-HMHHIG7Y.js")).executeTests;
|
|
16992
17587
|
const report = await runTests(testArgs);
|
|
16993
17588
|
ctx.send({ type: "info", message: report });
|
|
16994
17589
|
} catch (err) {
|
|
@@ -17324,7 +17919,7 @@ async function handleMemory(args, ctx) {
|
|
|
17324
17919
|
ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
|
|
17325
17920
|
} else if (sub === "export") {
|
|
17326
17921
|
if (args[1] === "md") {
|
|
17327
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
17922
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-QZN4Q3W4.js");
|
|
17328
17923
|
ctx.send({
|
|
17329
17924
|
type: "export_data",
|
|
17330
17925
|
format: "md",
|
|
@@ -17766,8 +18361,9 @@ async function handleHelp(_args, ctx) {
|
|
|
17766
18361
|
" /clear \u2014 Clear conversation & start new session",
|
|
17767
18362
|
" /compact [hint] \u2014 Compress conversation history",
|
|
17768
18363
|
" /think [on|off] \u2014 Toggle extended thinking mode",
|
|
17769
|
-
" /plan [
|
|
17770
|
-
" /
|
|
18364
|
+
" /plan [execute|exit|status] \u2014 Read-only planning mode (bare /plan enters it)",
|
|
18365
|
+
" /route [on|off|show|test <msg>] \u2014 Smart model routing",
|
|
18366
|
+
" /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",
|
|
17771
18367
|
" /system [prompt|clear] \u2014 Set/view/reset system prompt",
|
|
17772
18368
|
" /context [status|reload] \u2014 Show/reload context layers",
|
|
17773
18369
|
" /status \u2014 Show session info & token usage",
|
|
@@ -17903,8 +18499,9 @@ var CLI_ONLY_WEB_COMMANDS = /* @__PURE__ */ new Map([
|
|
|
17903
18499
|
["snapshot", "Dev-state snapshots are written when the terminal REPL exits."],
|
|
17904
18500
|
["hooks", "Hook trust is granted per machine from the terminal."],
|
|
17905
18501
|
["hook", "Hook trust is granted per machine from the terminal."],
|
|
17906
|
-
//
|
|
17907
|
-
|
|
18502
|
+
// 以下一条并非「终端才有意义」,而是 Web 侧尚未实现——如实说明,别假装是设计。
|
|
18503
|
+
// (`route` 曾在这里,理由写的是「Web 尚未应用智能路由」;v0.4.269 让 routing 在 Web
|
|
18504
|
+
// 上真正生效之后那句话就不再成立,命令已在 Web 实现,见 commands/provider-commands.ts。)
|
|
17908
18505
|
["security", "Redaction status and toggles are terminal-only for now; /status shows the current profile."]
|
|
17909
18506
|
]);
|
|
17910
18507
|
var webCommandHandlers = {
|
|
@@ -17912,6 +18509,7 @@ var webCommandHandlers = {
|
|
|
17912
18509
|
model: handleModel,
|
|
17913
18510
|
think: handleThink,
|
|
17914
18511
|
plan: handlePlan,
|
|
18512
|
+
route: handleRoute,
|
|
17915
18513
|
clear: handleClear,
|
|
17916
18514
|
compact: handleCompact,
|
|
17917
18515
|
status: handleStatus,
|