mindweave 2.4.7 → 2.4.9
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/README.md +5 -3
- package/dist/cli/App.js +70 -23
- package/dist/cli/App.js.map +1 -1
- package/dist/cli/chatAnchor.js +22 -0
- package/dist/cli/chatAnchor.js.map +1 -1
- package/dist/cli/commandArgs.js +60 -16
- package/dist/cli/commandArgs.js.map +1 -1
- package/dist/cli/components/Picker.js +61 -19
- package/dist/cli/components/Picker.js.map +1 -1
- package/dist/cli/scrollPill.js +4 -4
- package/dist/cli/scrollPill.js.map +1 -1
- package/dist/drivers/clientId.js +2 -3
- package/dist/drivers/clientId.js.map +1 -1
- package/dist/drivers/openaiCompat/wire.js +55 -3
- package/dist/drivers/openaiCompat/wire.js.map +1 -1
- package/dist/drivers/openrouter/catalog.js +136 -0
- package/dist/drivers/openrouter/catalog.js.map +1 -0
- package/dist/drivers/openrouter/client.js +102 -0
- package/dist/drivers/openrouter/client.js.map +1 -0
- package/dist/drivers/openrouter/endpoint.js +20 -0
- package/dist/drivers/openrouter/endpoint.js.map +1 -0
- package/dist/drivers/openrouter/index.js +5 -0
- package/dist/drivers/openrouter/index.js.map +1 -0
- package/dist/drivers/openrouter/manifest.js +83 -0
- package/dist/drivers/openrouter/manifest.js.map +1 -0
- package/dist/drivers/providerError.js +51 -0
- package/dist/drivers/providerError.js.map +1 -1
- package/dist/drivers/registry.js +111 -3
- package/dist/drivers/registry.js.map +1 -1
- package/dist/dynamo/model.js +63 -4
- package/dist/dynamo/model.js.map +1 -1
- package/package.json +74 -74
package/README.md
CHANGED
|
@@ -25,7 +25,6 @@ It is built lean on purpose. Most of a coding agent's context budget goes on sca
|
|
|
25
25
|
the model never needed. Mindweave keeps prompts thin and leaves the room for the model to
|
|
26
26
|
reason about your code.
|
|
27
27
|
|
|
28
|
-
Questions are welcome at zallinimann@gmail.com.
|
|
29
28
|
|
|
30
29
|
## Mindweave 1
|
|
31
30
|
|
|
@@ -71,8 +70,9 @@ else is configured inside a session.
|
|
|
71
70
|
|
|
72
71
|
Short version, one line each. The depth is in the linked pages.
|
|
73
72
|
|
|
74
|
-
- **
|
|
75
|
-
Mistral, Groq, Cerebras, Qwen, Kimi, GLM, Meta, MiniMax, Tencent
|
|
73
|
+
- **15 providers, 53 models, one key** — DeepSeek, Anthropic, OpenAI, Gemini, xAI,
|
|
74
|
+
Mistral, Groq, Cerebras, Qwen, Kimi, GLM, Meta, MiniMax, Tencent, plus every
|
|
75
|
+
tool-capable model on OpenRouter. Only the driver you
|
|
76
76
|
use is loaded. Switch with `/provider` and `/model`; remembered per project.
|
|
77
77
|
[PROVIDERS.md](src/drivers/PROVIDERS.md)
|
|
78
78
|
- **Real tools** — read and edit files, multi-file edits, ripgrep search, a shell with
|
|
@@ -137,6 +137,8 @@ running it on real projects and fixing what breaks, and nearly everything in the
|
|
|
137
137
|
changelog started as a failure someone watched happen. [What to include, and what is
|
|
138
138
|
especially worth reporting.](CONTRIBUTING.md#reporting-a-bug)
|
|
139
139
|
|
|
140
|
+
Questions are welcome at zallinimann@gmail.com
|
|
141
|
+
|
|
140
142
|
## License
|
|
141
143
|
|
|
142
144
|
[Apache License 2.0](LICENSE).
|
package/dist/cli/App.js
CHANGED
|
@@ -46,10 +46,10 @@ import { TrustGate } from "./components/TrustGate.js";
|
|
|
46
46
|
import { rootBreadth, breadthWarning, trustPersists, isTrusted, rememberTrust } from "./trust.js";
|
|
47
47
|
import { projectDir } from "../memory/store.js";
|
|
48
48
|
import { parseUndoArg, undoNotice } from "../tools/checkpoints.js";
|
|
49
|
-
import { DEFAULT_MODEL_CONFIG, thinkLevels, thinkLabel, modelLabel, modelsOfProvider, providerOf, usableFallback, needsKeySetup, withModel, saveModelConfig, refreshModels } from "../dynamo/model.js";
|
|
49
|
+
import { DEFAULT_MODEL_CONFIG, thinkLevels, thinkLabel, modelLabel, modelsOfProvider, providerOf, usableFallback, needsKeySetup, withModel, saveModelConfig, refreshModels, DISCOVERY_TTL_MS } from "../dynamo/model.js";
|
|
50
50
|
import { allProviders, manifestForModel, modelsOf } from "../drivers/registry.js";
|
|
51
51
|
import { orderProviders, orderModels } from "./pickerOrder.js";
|
|
52
|
-
import { accessRefusal } from "../drivers/providerError.js";
|
|
52
|
+
import { accessRefusal, providerOutage } from "../drivers/providerError.js";
|
|
53
53
|
import { resolveAttachments, stripAttachments } from "./attachments.js";
|
|
54
54
|
import { collapsePastes, wrapPastedText } from "../memory/pastedText.js";
|
|
55
55
|
import { createDropHandles, expandHandles } from "./dropHandles.js";
|
|
@@ -79,7 +79,7 @@ import { applySelection, ctrlCShouldCopy, isEmpty, selectionText } from "./selec
|
|
|
79
79
|
import { latestScreen, repaintOverlay, setFrameOverlay } from "./framebuffer/overlay.js";
|
|
80
80
|
import { requestFullRepaint } from "./framebuffer/writer.js";
|
|
81
81
|
import { copyToClipboard } from "./clipboard.js";
|
|
82
|
-
import { chatLayout, reflowScroll } from "./chatAnchor.js";
|
|
82
|
+
import { chatLayout, reflowScroll, growScroll } from "./chatAnchor.js";
|
|
83
83
|
import { growFill, INLINE_LIVE_RESERVE, NO_FILL } from "./startupFill.js";
|
|
84
84
|
import { setRowsBelowCaret } from "./exitCursor.js";
|
|
85
85
|
import { caretCell } from "./caretPark.js";
|
|
@@ -89,7 +89,7 @@ import { perf, perfEnabled } from "./perfLog.js";
|
|
|
89
89
|
import { isGroupMember, groupSettled, planGroupReveal, planStandaloneReveal, resultQueued, STANDALONE_HOLD_MS } from "./groupReveal.js";
|
|
90
90
|
import { drain as drainQueue, popAll as popAllQueued, queueMessage, takeSteerable, visibleQueue } from "./messageQueue.js";
|
|
91
91
|
import { routeCommand, parseCommandLine, unknownCommandMessage } from "./commandRoute.js";
|
|
92
|
-
import { resolveChoice } from "./commandArgs.js";
|
|
92
|
+
import { resolveChoice, splitModelArg } from "./commandArgs.js";
|
|
93
93
|
import { carryAcrossFreshSession } from "./sessionCarry.js";
|
|
94
94
|
import { toolDisplay, isGroupable, KIND_COLOR } from "./toolDisplay.js";
|
|
95
95
|
import { workingVerb } from "./workingVerb.js";
|
|
@@ -141,6 +141,11 @@ function orderedProviderList() {
|
|
|
141
141
|
function orderedModelList(model) {
|
|
142
142
|
return orderModels(modelsOfProvider(model));
|
|
143
143
|
}
|
|
144
|
+
/** The same order, for a provider named by id rather than by one of its models. */
|
|
145
|
+
function orderedModelsOf(providerId) {
|
|
146
|
+
const provider = allProviders().find((p) => p.id === providerId);
|
|
147
|
+
return provider ? orderModels(modelsOf(provider)) : [];
|
|
148
|
+
}
|
|
144
149
|
// After you pick a session in /continue, the three ways to resume it.
|
|
145
150
|
const RESUME_MODES = [
|
|
146
151
|
{ label: "Compact & continue", description: "summarize the old chat first so it won't eat your context, then pick up where you left off" },
|
|
@@ -254,6 +259,9 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
254
259
|
/** Reading position captured at a width change, pending the first measurement at the
|
|
255
260
|
* new width. Null except across that one frame. See the width-change block below. */
|
|
256
261
|
const reflowFrom = useRef(null);
|
|
262
|
+
/** `contentHeight` as of the last measurement, so growth between renders is a delta
|
|
263
|
+
* the scroll position can be compensated by. See the growth-compensation block below. */
|
|
264
|
+
const prevContentHeight = useRef(null);
|
|
257
265
|
// Each block's real rendered height, so blocks off screen can be replaced by a
|
|
258
266
|
// spacer of the exact same size instead of being laid out in full — see
|
|
259
267
|
// `virtualWindow.ts` for why that is the whole performance story, and why exact
|
|
@@ -1169,6 +1177,15 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1169
1177
|
const next = reflowScroll(from.scrolled, from.maxScroll, Math.max(0, height - chatRows));
|
|
1170
1178
|
setScrollUp((s) => (s === next ? s : next));
|
|
1171
1179
|
}
|
|
1180
|
+
else if (prevContentHeight.current !== null) {
|
|
1181
|
+
// Ordinary growth, not a resize — see growScroll for why this has to hold the
|
|
1182
|
+
// reader's absolute position rather than leave `scrollUp` where it was.
|
|
1183
|
+
const prev = prevContentHeight.current;
|
|
1184
|
+
const next = growScroll(scrollUp, prev, height);
|
|
1185
|
+
if (next !== scrollUp)
|
|
1186
|
+
setScrollUp(next);
|
|
1187
|
+
}
|
|
1188
|
+
prevContentHeight.current = height;
|
|
1172
1189
|
});
|
|
1173
1190
|
// Measure each block that was rendered without a known height yet, so the next
|
|
1174
1191
|
// frame can replace it with an exact spacer when it scrolls out of view.
|
|
@@ -2001,7 +2018,10 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2001
2018
|
// the provider's own sentence quoted inside it. Anything else — a malformed
|
|
2002
2019
|
// request, a bug of ours — stays loud, which is the point of classifying
|
|
2003
2020
|
// narrowly. See drivers/providerError.ts.
|
|
2004
|
-
|
|
2021
|
+
// A provider falling over after the retries ran out gets the same calm treatment:
|
|
2022
|
+
// it is not our crash, and the useful news is "wait, or switch model".
|
|
2023
|
+
const refusal = accessRefusal(error, providerOf(s.modelConfig.model).label, otherProviderHasKey(s.modelConfig.model)) ??
|
|
2024
|
+
providerOutage(error, providerOf(s.modelConfig.model).label, modelLabel(s.modelConfig.model));
|
|
2005
2025
|
if (refusal)
|
|
2006
2026
|
enqueueReveal({ type: "notice", title: refusal.title, body: refusal.body });
|
|
2007
2027
|
else
|
|
@@ -2136,15 +2156,28 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2136
2156
|
}
|
|
2137
2157
|
// Apply a /model pick: switch model (clamping reasoning to a valid level), persist
|
|
2138
2158
|
// the choice for this project, and confirm.
|
|
2139
|
-
async function applyModel(index) {
|
|
2159
|
+
async function applyModel(index, providerId) {
|
|
2140
2160
|
const s = session.current;
|
|
2141
|
-
// Index into the SAME list the picker rendered —
|
|
2142
|
-
//
|
|
2143
|
-
//
|
|
2144
|
-
//
|
|
2145
|
-
const
|
|
2161
|
+
// Index into the SAME list the picker rendered — that provider's models in display
|
|
2162
|
+
// order, not every model everywhere. Indexing a differently-ordered list here would
|
|
2163
|
+
// silently select a different model than the one on screen, and would type-check
|
|
2164
|
+
// perfectly.
|
|
2165
|
+
const list = !s ? [] : providerId ? orderedModelsOf(providerId) : orderedModelList(s.modelConfig.model);
|
|
2166
|
+
const choice = list[index];
|
|
2146
2167
|
if (!s || !choice)
|
|
2147
2168
|
return;
|
|
2169
|
+
// A model on another provider is a provider switch, and gets the same key check.
|
|
2170
|
+
const target = manifestForModel(choice.id);
|
|
2171
|
+
if (target.id !== providerOf(s.modelConfig.model).id) {
|
|
2172
|
+
if (missingKeyFor(choice.id)) {
|
|
2173
|
+
pendingSwitch.current = { model: choice.id, apiKeyEnv: target.apiKeyEnv, label: target.label };
|
|
2174
|
+
note(`${target.label} has no key yet — add one and the switch will finish.`);
|
|
2175
|
+
setKeysOpen(true);
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
await switchTo(choice.id, target.label);
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2148
2181
|
s.modelConfig = withModel(s.modelConfig, choice.id);
|
|
2149
2182
|
await saveModelConfig(s.cwd, s.modelConfig);
|
|
2150
2183
|
note(`model → ${modelLabel(s.modelConfig.model)} · ${thinkLabel(s.modelConfig)}`);
|
|
@@ -2190,6 +2223,10 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2190
2223
|
s.modelConfig = withModel(s.modelConfig, model);
|
|
2191
2224
|
await saveModelConfig(s.cwd, s.modelConfig);
|
|
2192
2225
|
note(`provider → ${providerLabel} · model → ${modelLabel(s.modelConfig.model)} · ${thinkLabel(s.modelConfig)}`);
|
|
2226
|
+
// Where prompts go is worth a sentence at the moment someone moves their work there.
|
|
2227
|
+
const notice = manifestForModel(model).notice;
|
|
2228
|
+
if (notice)
|
|
2229
|
+
note(notice);
|
|
2193
2230
|
}
|
|
2194
2231
|
// Apply a /think pick for the current model: set thinking + effort, persist, confirm.
|
|
2195
2232
|
async function applyThink(index) {
|
|
@@ -2244,7 +2281,7 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2244
2281
|
else if (o.kind === "provider")
|
|
2245
2282
|
void applyProvider(index);
|
|
2246
2283
|
else if (o.kind === "model")
|
|
2247
|
-
void applyModel(index);
|
|
2284
|
+
void applyModel(index, o.providerId);
|
|
2248
2285
|
else if (o.kind === "think")
|
|
2249
2286
|
void applyThink(index);
|
|
2250
2287
|
else if (o.kind === "screen") {
|
|
@@ -2608,12 +2645,12 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2608
2645
|
setOverlay({ kind: "sessions", items: sessions });
|
|
2609
2646
|
return;
|
|
2610
2647
|
}
|
|
2611
|
-
// Both pickers refresh discovered providers
|
|
2612
|
-
//
|
|
2648
|
+
// Both pickers refresh discovered providers whose list has gone stale, so a newly
|
|
2649
|
+
// served model appears without a restart. Awaited rather than fired off: the
|
|
2613
2650
|
// picker renders from the list, and opening on a stale one that then changes
|
|
2614
2651
|
// under the cursor is the exact "two-stage reveal" the UI work removed.
|
|
2615
2652
|
if (name === "/provider") {
|
|
2616
|
-
await refreshModels();
|
|
2653
|
+
await refreshModels({ maxAgeMs: DISCOVERY_TTL_MS });
|
|
2617
2654
|
setOverlay({ kind: "provider" });
|
|
2618
2655
|
return;
|
|
2619
2656
|
}
|
|
@@ -2629,12 +2666,21 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2629
2666
|
// getting the picker anyway — which is what happened before, because the argument
|
|
2630
2667
|
// was dropped without a word — reads as the app not having heard you.
|
|
2631
2668
|
if (name === "/model") {
|
|
2632
|
-
await refreshModels();
|
|
2669
|
+
await refreshModels({ maxAgeMs: DISCOVERY_TTL_MS });
|
|
2633
2670
|
if (arg) {
|
|
2634
|
-
const
|
|
2671
|
+
const current = providerOf(s.modelConfig.model).id;
|
|
2672
|
+
const here = resolveChoice(arg, orderedModelList(s.modelConfig.model), "model");
|
|
2673
|
+
const { providerId, words } = splitModelArg(arg, allProviders(), current, here.kind !== "error");
|
|
2674
|
+
// `/model openrouter` alone: that provider's picker.
|
|
2675
|
+
if (!words)
|
|
2676
|
+
return setOverlay({ kind: "model", providerId });
|
|
2677
|
+
const picked = providerId === current ? here : resolveChoice(words, orderedModelsOf(providerId), "model");
|
|
2635
2678
|
if (picked.kind === "error")
|
|
2636
2679
|
return say(picked.message);
|
|
2637
|
-
|
|
2680
|
+
// Several fit: show exactly those, in the picker, rather than a list to retype from.
|
|
2681
|
+
if (picked.kind === "several")
|
|
2682
|
+
return setOverlay({ kind: "model", providerId, filter: words });
|
|
2683
|
+
await applyModel(picked.index, providerId);
|
|
2638
2684
|
return;
|
|
2639
2685
|
}
|
|
2640
2686
|
setOverlay({ kind: "model" });
|
|
@@ -2663,7 +2709,7 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2663
2709
|
if (name === "/think") {
|
|
2664
2710
|
if (arg) {
|
|
2665
2711
|
const picked = resolveChoice(arg, thinkLevels(s.modelConfig.model), "reasoning level");
|
|
2666
|
-
if (picked.kind
|
|
2712
|
+
if (picked.kind !== "match")
|
|
2667
2713
|
return say(picked.message);
|
|
2668
2714
|
await applyThink(picked.index);
|
|
2669
2715
|
return;
|
|
@@ -3090,9 +3136,10 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
3090
3136
|
}
|
|
3091
3137
|
if (overlay.kind === "model") {
|
|
3092
3138
|
const id = cur?.modelConfig.model ?? DEFAULT_MODEL_CONFIG.model;
|
|
3093
|
-
//
|
|
3094
|
-
//
|
|
3095
|
-
const models = orderedModelList(id);
|
|
3139
|
+
// One provider's models, in display order (default first, rest A→Z): the one in use,
|
|
3140
|
+
// unless `/model <provider> …` named another.
|
|
3141
|
+
const models = overlay.providerId ? orderedModelsOf(overlay.providerId) : orderedModelList(id);
|
|
3142
|
+
const pickerProvider = overlay.providerId ? allProviders().find((p) => p.id === overlay.providerId)?.label ?? "" : providerOf(id).label;
|
|
3096
3143
|
// The two facts that decide the choice and are nowhere else on the screen: how much
|
|
3097
3144
|
// it can hold, and whether it can see an image you attach. They lead the description
|
|
3098
3145
|
// because the row truncates from the RIGHT — put behind the prose they would be the
|
|
@@ -3106,7 +3153,7 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
3106
3153
|
description: m.description ? `${facts} · ${m.description}` : facts,
|
|
3107
3154
|
};
|
|
3108
3155
|
});
|
|
3109
|
-
return (_jsx(Picker, { title: `Choose a ${
|
|
3156
|
+
return (_jsx(Picker, { title: `Choose a ${pickerProvider} model`, items: items, width: width, maxRows: maxRows, initialIndex: Math.max(0, models.findIndex((m) => m.id === id)), initialFilter: overlay.filter, onSelect: onOverlaySelect, onCancel: onOverlayCancel }));
|
|
3110
3157
|
}
|
|
3111
3158
|
if (overlay.kind === "think") {
|
|
3112
3159
|
const model = cur?.modelConfig.model ?? DEFAULT_MODEL_CONFIG.model;
|