@wrongstack/cli 0.1.4 → 0.1.7
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 +98 -0
- package/dist/index.js +712 -86
- package/dist/index.js.map +1 -1
- package/package.json +15 -8
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, DefaultLogger, DefaultModelsRegistry, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI,
|
|
2
|
+
import { color, DefaultLogger, DefaultModelsRegistry, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader, makeAgentSubagentRunner, DefaultMultiAgentCoordinator, decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core';
|
|
3
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
4
|
import * as fs6 from 'fs/promises';
|
|
4
5
|
import { writeFileSync } from 'fs';
|
|
5
6
|
import { createRequire } from 'module';
|
|
@@ -34,6 +35,506 @@ var init_plugin_api_factory = __esm({
|
|
|
34
35
|
"src/plugin-api-factory.ts"() {
|
|
35
36
|
}
|
|
36
37
|
});
|
|
38
|
+
|
|
39
|
+
// src/webui-server.ts
|
|
40
|
+
var webui_server_exports = {};
|
|
41
|
+
__export(webui_server_exports, {
|
|
42
|
+
runWebUI: () => runWebUI
|
|
43
|
+
});
|
|
44
|
+
async function runWebUI(opts) {
|
|
45
|
+
const port = opts.port ?? 3457;
|
|
46
|
+
const clients = /* @__PURE__ */ new Map();
|
|
47
|
+
let abortController = null;
|
|
48
|
+
const wss = new WebSocketServer({ port });
|
|
49
|
+
console.log(`[WebUI] WebSocket server starting on ws://localhost:${port}`);
|
|
50
|
+
const eventUnsubscribers = [];
|
|
51
|
+
function setupEvents() {
|
|
52
|
+
for (const unsub of eventUnsubscribers) unsub();
|
|
53
|
+
eventUnsubscribers.length = 0;
|
|
54
|
+
eventUnsubscribers.push(
|
|
55
|
+
opts.events.on("iteration.started", (e) => {
|
|
56
|
+
broadcast({
|
|
57
|
+
type: "iteration.started",
|
|
58
|
+
payload: { index: e.index }
|
|
59
|
+
});
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
eventUnsubscribers.push(
|
|
63
|
+
opts.events.on("provider.text_delta", (e) => {
|
|
64
|
+
broadcast({
|
|
65
|
+
type: "provider.text_delta",
|
|
66
|
+
payload: { text: e.text, messageId: "current" }
|
|
67
|
+
});
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
eventUnsubscribers.push(
|
|
71
|
+
opts.events.on("tool.started", (e) => {
|
|
72
|
+
broadcast({
|
|
73
|
+
type: "tool.started",
|
|
74
|
+
payload: {
|
|
75
|
+
id: e.id,
|
|
76
|
+
name: e.name,
|
|
77
|
+
input: e.input,
|
|
78
|
+
messageId: `tool_${e.id}`
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
eventUnsubscribers.push(
|
|
84
|
+
opts.events.on("tool.progress", (e) => {
|
|
85
|
+
broadcast({
|
|
86
|
+
type: "tool.progress",
|
|
87
|
+
payload: {
|
|
88
|
+
name: e.name,
|
|
89
|
+
id: e.id,
|
|
90
|
+
event: e.event
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
eventUnsubscribers.push(
|
|
96
|
+
opts.events.on("tool.executed", (e) => {
|
|
97
|
+
broadcast({
|
|
98
|
+
type: "tool.executed",
|
|
99
|
+
payload: {
|
|
100
|
+
// Forward the tool_use id so the WebUI can correlate this with
|
|
101
|
+
// the matching tool.started bubble for parallel tool calls.
|
|
102
|
+
id: e.id,
|
|
103
|
+
name: e.name,
|
|
104
|
+
durationMs: e.durationMs,
|
|
105
|
+
ok: e.ok,
|
|
106
|
+
input: e.input,
|
|
107
|
+
output: e.output
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
eventUnsubscribers.push(
|
|
113
|
+
opts.events.on("provider.response", (e) => {
|
|
114
|
+
broadcast({
|
|
115
|
+
type: "provider.response",
|
|
116
|
+
payload: {
|
|
117
|
+
usage: e.usage,
|
|
118
|
+
stopReason: e.stopReason,
|
|
119
|
+
messageId: "current"
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
eventUnsubscribers.push(
|
|
125
|
+
opts.events.on("error", (e) => {
|
|
126
|
+
broadcast({
|
|
127
|
+
type: "error",
|
|
128
|
+
payload: {
|
|
129
|
+
phase: e.phase,
|
|
130
|
+
message: e.err instanceof Error ? e.err.message : String(e.err)
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return new Promise((resolve3) => {
|
|
137
|
+
wss.on("listening", () => {
|
|
138
|
+
console.log(`[WebUI] WebSocket server running on ws://localhost:${port}`);
|
|
139
|
+
setupEvents();
|
|
140
|
+
});
|
|
141
|
+
wss.on("connection", (ws) => {
|
|
142
|
+
const client = { ws, sessionId: opts.session.id };
|
|
143
|
+
clients.set(ws, client);
|
|
144
|
+
console.log("[WebUI] Client connected");
|
|
145
|
+
ws.on("message", async (data) => {
|
|
146
|
+
try {
|
|
147
|
+
const msg = JSON.parse(data.toString());
|
|
148
|
+
await handleMessage(ws, client, msg);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error("[WebUI] Failed to parse message", err);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
ws.on("close", () => {
|
|
154
|
+
console.log("[WebUI] Client disconnected");
|
|
155
|
+
clients.delete(ws);
|
|
156
|
+
});
|
|
157
|
+
send(ws, {
|
|
158
|
+
type: "session.start",
|
|
159
|
+
payload: {
|
|
160
|
+
sessionId: opts.session.id,
|
|
161
|
+
model: opts.agent.ctx.model,
|
|
162
|
+
provider: opts.agent.ctx.provider.id
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
wss.on("error", (err) => {
|
|
167
|
+
console.error("[WebUI] Server error:", err);
|
|
168
|
+
});
|
|
169
|
+
function shutdown() {
|
|
170
|
+
console.log("[WebUI] Shutting down...");
|
|
171
|
+
for (const unsub of eventUnsubscribers) unsub();
|
|
172
|
+
for (const [ws] of clients) {
|
|
173
|
+
ws.close();
|
|
174
|
+
}
|
|
175
|
+
clients.clear();
|
|
176
|
+
wss.close(() => {
|
|
177
|
+
console.log("[WebUI] Server stopped");
|
|
178
|
+
resolve3();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
process.on("SIGINT", shutdown);
|
|
182
|
+
process.on("SIGTERM", shutdown);
|
|
183
|
+
});
|
|
184
|
+
async function handleMessage(ws, client, msg) {
|
|
185
|
+
switch (msg.type) {
|
|
186
|
+
case "user_message":
|
|
187
|
+
await handleUserMessage(ws, client, msg.payload.content);
|
|
188
|
+
break;
|
|
189
|
+
case "abort":
|
|
190
|
+
abortController?.abort();
|
|
191
|
+
broadcast({
|
|
192
|
+
type: "error",
|
|
193
|
+
payload: { phase: "abort", message: "User aborted" }
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
case "ping":
|
|
197
|
+
send(ws, { type: "pong", payload: {} });
|
|
198
|
+
break;
|
|
199
|
+
case "providers.list":
|
|
200
|
+
await handleProvidersList(ws);
|
|
201
|
+
break;
|
|
202
|
+
case "provider.models":
|
|
203
|
+
await handleProviderModels(ws, msg.payload.providerId);
|
|
204
|
+
break;
|
|
205
|
+
case "providers.saved":
|
|
206
|
+
await handleProvidersSaved(ws);
|
|
207
|
+
break;
|
|
208
|
+
case "key.add":
|
|
209
|
+
case "key.update": {
|
|
210
|
+
const m = msg;
|
|
211
|
+
await handleKeyUpsert(ws, m.payload.providerId, m.payload.label, m.payload.apiKey);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "key.delete": {
|
|
215
|
+
const m = msg;
|
|
216
|
+
await handleKeyDelete(ws, m.payload.providerId, m.payload.label);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "key.set_active": {
|
|
220
|
+
const m = msg;
|
|
221
|
+
await handleKeySetActive(ws, m.payload.providerId, m.payload.label);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "provider.add": {
|
|
225
|
+
const m = msg;
|
|
226
|
+
await handleProviderAdd(ws, m.payload);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
case "provider.remove": {
|
|
230
|
+
const m = msg;
|
|
231
|
+
await handleProviderRemove(ws, m.payload.providerId);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function handleUserMessage(ws, client, content) {
|
|
237
|
+
abortController?.abort();
|
|
238
|
+
abortController = new AbortController();
|
|
239
|
+
try {
|
|
240
|
+
const result = await opts.agent.run(content, {
|
|
241
|
+
signal: abortController.signal
|
|
242
|
+
});
|
|
243
|
+
send(ws, {
|
|
244
|
+
type: "run.result",
|
|
245
|
+
payload: {
|
|
246
|
+
status: result.status,
|
|
247
|
+
iterations: result.iterations,
|
|
248
|
+
finalText: result.finalText,
|
|
249
|
+
error: result.error ? {
|
|
250
|
+
code: result.error.code,
|
|
251
|
+
message: result.error.message,
|
|
252
|
+
recoverable: result.error.recoverable
|
|
253
|
+
} : void 0
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
send(ws, {
|
|
258
|
+
type: "error",
|
|
259
|
+
payload: {
|
|
260
|
+
phase: "agent.run",
|
|
261
|
+
message: err instanceof Error ? err.message : String(err)
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
} finally {
|
|
265
|
+
abortController = null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function send(ws, msg) {
|
|
269
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
270
|
+
ws.send(JSON.stringify(msg));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function broadcast(msg) {
|
|
274
|
+
const data = JSON.stringify(msg);
|
|
275
|
+
for (const [ws] of clients) {
|
|
276
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
277
|
+
ws.send(data);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function handleProvidersList(ws) {
|
|
282
|
+
if (!opts.modelsRegistry) {
|
|
283
|
+
sendResult(ws, false, "Models registry not available");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const providers = await opts.modelsRegistry.listProviders();
|
|
288
|
+
const savedProviders = await loadSavedProviders();
|
|
289
|
+
const savedIds = new Set(Object.keys(savedProviders));
|
|
290
|
+
send(ws, {
|
|
291
|
+
type: "provider.catalog",
|
|
292
|
+
payload: {
|
|
293
|
+
providers: providers.map((p) => ({
|
|
294
|
+
id: p.id,
|
|
295
|
+
name: p.name,
|
|
296
|
+
family: p.family,
|
|
297
|
+
apiBase: p.apiBase,
|
|
298
|
+
envVars: p.envVars,
|
|
299
|
+
modelCount: p.models.length,
|
|
300
|
+
hasApiKey: savedIds.has(p.id) || p.envVars.some((v) => !!process.env[v])
|
|
301
|
+
}))
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
} catch (err) {
|
|
305
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function handleProviderModels(ws, providerId) {
|
|
309
|
+
if (!opts.modelsRegistry) {
|
|
310
|
+
sendResult(ws, false, "Models registry not available");
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const provider = await opts.modelsRegistry.getProvider(providerId);
|
|
315
|
+
if (!provider) {
|
|
316
|
+
sendResult(ws, false, `Provider "${providerId}" not found in catalog`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
send(ws, {
|
|
320
|
+
type: "provider.models",
|
|
321
|
+
payload: {
|
|
322
|
+
provider: providerId,
|
|
323
|
+
models: provider.models.map((m) => ({
|
|
324
|
+
id: m.id,
|
|
325
|
+
name: m.name,
|
|
326
|
+
releaseDate: m.release_date,
|
|
327
|
+
contextWindow: m.limit?.context,
|
|
328
|
+
inputCost: m.cost?.input,
|
|
329
|
+
outputCost: m.cost?.output,
|
|
330
|
+
capabilities: [
|
|
331
|
+
...m.tool_call ? ["tools"] : [],
|
|
332
|
+
...m.reasoning ? ["reasoning"] : [],
|
|
333
|
+
...m.modalities?.input?.includes("image") ? ["vision"] : [],
|
|
334
|
+
...m.open_weights ? ["open_weights"] : []
|
|
335
|
+
]
|
|
336
|
+
}))
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
} catch (err) {
|
|
340
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function handleProvidersSaved(ws) {
|
|
344
|
+
try {
|
|
345
|
+
const providers = await loadSavedProviders();
|
|
346
|
+
send(ws, {
|
|
347
|
+
type: "providers.saved",
|
|
348
|
+
payload: {
|
|
349
|
+
providers: Object.entries(providers).map(([id, cfg]) => ({
|
|
350
|
+
id,
|
|
351
|
+
family: cfg.family,
|
|
352
|
+
baseUrl: cfg.baseUrl,
|
|
353
|
+
apiKeys: normalizeKeys2(cfg).map((k) => ({
|
|
354
|
+
label: k.label,
|
|
355
|
+
maskedKey: maskedKey2(k.apiKey),
|
|
356
|
+
isActive: k.label === cfg.activeKey,
|
|
357
|
+
createdAt: k.createdAt
|
|
358
|
+
}))
|
|
359
|
+
}))
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
} catch (err) {
|
|
363
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function handleKeyUpsert(ws, providerId, label, apiKey) {
|
|
367
|
+
try {
|
|
368
|
+
const providers = await loadSavedProviders();
|
|
369
|
+
const existing = providers[providerId] ?? { type: providerId };
|
|
370
|
+
const keys = normalizeKeys2(existing);
|
|
371
|
+
const existingIdx = keys.findIndex((k) => k.label === label);
|
|
372
|
+
if (existingIdx >= 0) {
|
|
373
|
+
keys[existingIdx] = { ...keys[existingIdx], apiKey, createdAt: nowIso2() };
|
|
374
|
+
} else {
|
|
375
|
+
keys.push({ label, apiKey, createdAt: nowIso2() });
|
|
376
|
+
}
|
|
377
|
+
writeKeysBack2(existing, keys);
|
|
378
|
+
if (!existing.activeKey) existing.activeKey = label;
|
|
379
|
+
providers[providerId] = existing;
|
|
380
|
+
await saveProviders(providers);
|
|
381
|
+
sendResult(ws, true, `Key "${label}" saved for ${providerId}`);
|
|
382
|
+
} catch (err) {
|
|
383
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function handleKeyDelete(ws, providerId, label) {
|
|
387
|
+
try {
|
|
388
|
+
const providers = await loadSavedProviders();
|
|
389
|
+
const existing = providers[providerId];
|
|
390
|
+
if (!existing) {
|
|
391
|
+
sendResult(ws, false, `Provider "${providerId}" not found`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const keys = normalizeKeys2(existing).filter((k) => k.label !== label);
|
|
395
|
+
if (keys.length === 0) {
|
|
396
|
+
delete providers[providerId];
|
|
397
|
+
} else {
|
|
398
|
+
writeKeysBack2(existing, keys);
|
|
399
|
+
if (existing.activeKey === label) {
|
|
400
|
+
existing.activeKey = keys[0].label;
|
|
401
|
+
}
|
|
402
|
+
providers[providerId] = existing;
|
|
403
|
+
}
|
|
404
|
+
await saveProviders(providers);
|
|
405
|
+
sendResult(ws, true, `Key "${label}" deleted from ${providerId}`);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async function handleKeySetActive(ws, providerId, label) {
|
|
411
|
+
try {
|
|
412
|
+
const providers = await loadSavedProviders();
|
|
413
|
+
const existing = providers[providerId];
|
|
414
|
+
if (!existing) {
|
|
415
|
+
sendResult(ws, false, `Provider "${providerId}" not found`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
existing.activeKey = label;
|
|
419
|
+
writeKeysBack2(existing, normalizeKeys2(existing));
|
|
420
|
+
providers[providerId] = existing;
|
|
421
|
+
await saveProviders(providers);
|
|
422
|
+
sendResult(ws, true, `Active key for ${providerId} set to "${label}"`);
|
|
423
|
+
} catch (err) {
|
|
424
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function handleProviderAdd(ws, payload) {
|
|
428
|
+
try {
|
|
429
|
+
const providers = await loadSavedProviders();
|
|
430
|
+
if (providers[payload.id]) {
|
|
431
|
+
sendResult(ws, false, `Provider "${payload.id}" already exists. Use key.add to add a key.`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const newProv = {
|
|
435
|
+
type: payload.id,
|
|
436
|
+
family: payload.family,
|
|
437
|
+
baseUrl: payload.baseUrl
|
|
438
|
+
};
|
|
439
|
+
if (payload.apiKey) {
|
|
440
|
+
newProv.apiKeys = [{ label: "default", apiKey: payload.apiKey, createdAt: nowIso2() }];
|
|
441
|
+
newProv.activeKey = "default";
|
|
442
|
+
}
|
|
443
|
+
providers[payload.id] = newProv;
|
|
444
|
+
await saveProviders(providers);
|
|
445
|
+
sendResult(ws, true, `Provider "${payload.id}" added`);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async function handleProviderRemove(ws, providerId) {
|
|
451
|
+
try {
|
|
452
|
+
const providers = await loadSavedProviders();
|
|
453
|
+
if (!providers[providerId]) {
|
|
454
|
+
sendResult(ws, false, `Provider "${providerId}" not found`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
delete providers[providerId];
|
|
458
|
+
await saveProviders(providers);
|
|
459
|
+
sendResult(ws, true, `Provider "${providerId}" removed`);
|
|
460
|
+
} catch (err) {
|
|
461
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async function loadSavedProviders() {
|
|
465
|
+
if (!opts.globalConfigPath) return {};
|
|
466
|
+
let raw;
|
|
467
|
+
try {
|
|
468
|
+
raw = await fs6.readFile(opts.globalConfigPath, "utf8");
|
|
469
|
+
} catch {
|
|
470
|
+
return {};
|
|
471
|
+
}
|
|
472
|
+
let parsed = {};
|
|
473
|
+
try {
|
|
474
|
+
parsed = JSON.parse(raw);
|
|
475
|
+
} catch {
|
|
476
|
+
return {};
|
|
477
|
+
}
|
|
478
|
+
return parsed.providers ?? {};
|
|
479
|
+
}
|
|
480
|
+
async function saveProviders(providers) {
|
|
481
|
+
if (!opts.globalConfigPath) return;
|
|
482
|
+
let raw;
|
|
483
|
+
try {
|
|
484
|
+
raw = await fs6.readFile(opts.globalConfigPath, "utf8");
|
|
485
|
+
} catch {
|
|
486
|
+
raw = "{}";
|
|
487
|
+
}
|
|
488
|
+
let parsed;
|
|
489
|
+
try {
|
|
490
|
+
parsed = JSON.parse(raw);
|
|
491
|
+
} catch {
|
|
492
|
+
parsed = {};
|
|
493
|
+
}
|
|
494
|
+
parsed.providers = providers;
|
|
495
|
+
await atomicWrite(opts.globalConfigPath, JSON.stringify(parsed, null, 2), { mode: 384 });
|
|
496
|
+
}
|
|
497
|
+
function normalizeKeys2(cfg) {
|
|
498
|
+
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
499
|
+
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
500
|
+
}
|
|
501
|
+
if (typeof cfg.apiKey === "string" && cfg.apiKey.length > 0) {
|
|
502
|
+
return [{ label: "default", apiKey: cfg.apiKey, createdAt: "" }];
|
|
503
|
+
}
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
function writeKeysBack2(cfg, keys) {
|
|
507
|
+
if (keys.length === 0) {
|
|
508
|
+
delete cfg.apiKeys;
|
|
509
|
+
delete cfg.apiKey;
|
|
510
|
+
delete cfg.activeKey;
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
cfg.apiKeys = keys;
|
|
514
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
515
|
+
cfg.apiKey = active.apiKey;
|
|
516
|
+
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
517
|
+
cfg.activeKey = active.label;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function maskedKey2(key) {
|
|
521
|
+
if (!key) return "\u2014";
|
|
522
|
+
if (key.length <= 8) return "\u2022".repeat(key.length);
|
|
523
|
+
const head = key.slice(0, 4);
|
|
524
|
+
const tail = key.slice(-4);
|
|
525
|
+
return `${head}\u2026${tail}`;
|
|
526
|
+
}
|
|
527
|
+
function nowIso2() {
|
|
528
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
529
|
+
}
|
|
530
|
+
function sendResult(ws, success, message) {
|
|
531
|
+
send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
var init_webui_server = __esm({
|
|
535
|
+
"src/webui-server.ts"() {
|
|
536
|
+
}
|
|
537
|
+
});
|
|
37
538
|
var ReadlineInputReader = class {
|
|
38
539
|
rl;
|
|
39
540
|
historyFile;
|
|
@@ -516,17 +1017,17 @@ async function resolveModelSelection(answer, models, provider, _registry, render
|
|
|
516
1017
|
var theme2 = { primary: color.amber };
|
|
517
1018
|
async function saveToGlobalConfig(configPath, provider, model) {
|
|
518
1019
|
try {
|
|
519
|
-
const { atomicWrite:
|
|
520
|
-
const
|
|
1020
|
+
const { atomicWrite: atomicWrite4 } = await import('@wrongstack/core');
|
|
1021
|
+
const fs9 = await import('fs/promises');
|
|
521
1022
|
let existing = {};
|
|
522
1023
|
try {
|
|
523
|
-
const raw = await
|
|
1024
|
+
const raw = await fs9.readFile(configPath, "utf8");
|
|
524
1025
|
existing = JSON.parse(raw);
|
|
525
1026
|
} catch {
|
|
526
1027
|
}
|
|
527
1028
|
existing.provider = provider;
|
|
528
1029
|
existing.model = model;
|
|
529
|
-
await
|
|
1030
|
+
await atomicWrite4(configPath, JSON.stringify(existing, null, 2));
|
|
530
1031
|
return true;
|
|
531
1032
|
} catch {
|
|
532
1033
|
return false;
|
|
@@ -548,6 +1049,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
548
1049
|
metricsCommand(opts),
|
|
549
1050
|
healthCommand(opts),
|
|
550
1051
|
memoryCommand(opts),
|
|
1052
|
+
todosCommand(opts),
|
|
551
1053
|
saveCommand(opts),
|
|
552
1054
|
loadCommand(opts),
|
|
553
1055
|
exitCommand(opts)
|
|
@@ -597,6 +1099,68 @@ function memoryCommand(opts) {
|
|
|
597
1099
|
}
|
|
598
1100
|
};
|
|
599
1101
|
}
|
|
1102
|
+
function todosCommand(opts) {
|
|
1103
|
+
return {
|
|
1104
|
+
name: "todos",
|
|
1105
|
+
description: "Inspect or edit the live todo list: /todos [show|clear|add <text>|done <id|index>]",
|
|
1106
|
+
async run(args) {
|
|
1107
|
+
const ctx = opts.context;
|
|
1108
|
+
if (!ctx) return { message: "No active context." };
|
|
1109
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
1110
|
+
const restJoined = rest.join(" ").trim();
|
|
1111
|
+
switch (verb) {
|
|
1112
|
+
case "":
|
|
1113
|
+
case "show":
|
|
1114
|
+
case "list": {
|
|
1115
|
+
const todos = ctx.todos;
|
|
1116
|
+
if (todos.length === 0) {
|
|
1117
|
+
return { message: "No todos. The agent will add some when it plans work." };
|
|
1118
|
+
}
|
|
1119
|
+
const lines = [];
|
|
1120
|
+
const done = todos.filter((t) => t.status === "completed").length;
|
|
1121
|
+
lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
|
|
1122
|
+
todos.forEach((t, i) => {
|
|
1123
|
+
const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
|
|
1124
|
+
const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
|
|
1125
|
+
const label = t.status === "completed" ? color.dim(text) : text;
|
|
1126
|
+
lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
|
|
1127
|
+
});
|
|
1128
|
+
return { message: lines.join("\n") };
|
|
1129
|
+
}
|
|
1130
|
+
case "clear": {
|
|
1131
|
+
const n = ctx.todos.length;
|
|
1132
|
+
ctx.todos.length = 0;
|
|
1133
|
+
return { message: n === 0 ? "Todos were already empty." : `Cleared ${n} todo${n === 1 ? "" : "s"}.` };
|
|
1134
|
+
}
|
|
1135
|
+
case "add": {
|
|
1136
|
+
if (!restJoined) return { message: "Usage: /todos add <text>" };
|
|
1137
|
+
ctx.todos.push({
|
|
1138
|
+
id: `todo_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
|
1139
|
+
content: restJoined,
|
|
1140
|
+
status: "pending"
|
|
1141
|
+
});
|
|
1142
|
+
return { message: `Added: ${restJoined}` };
|
|
1143
|
+
}
|
|
1144
|
+
case "done":
|
|
1145
|
+
case "complete": {
|
|
1146
|
+
if (!restJoined) return { message: "Usage: /todos done <id|index>" };
|
|
1147
|
+
const asIndex = Number.parseInt(restJoined, 10);
|
|
1148
|
+
let target = !Number.isNaN(asIndex) ? ctx.todos[asIndex - 1] : ctx.todos.find((t) => t.id === restJoined);
|
|
1149
|
+
if (!target) {
|
|
1150
|
+
target = ctx.todos.find((t) => t.content.toLowerCase().includes(restJoined.toLowerCase()));
|
|
1151
|
+
}
|
|
1152
|
+
if (!target) return { message: `No todo matched "${restJoined}".` };
|
|
1153
|
+
target.status = "completed";
|
|
1154
|
+
return { message: `Marked done: ${target.content}` };
|
|
1155
|
+
}
|
|
1156
|
+
default:
|
|
1157
|
+
return {
|
|
1158
|
+
message: `Unknown subcommand "${verb}". Try: show | clear | add <text> | done <id|index>`
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
600
1164
|
function initCommand(opts) {
|
|
601
1165
|
return {
|
|
602
1166
|
name: "init",
|
|
@@ -810,11 +1374,11 @@ function clearCommand(opts) {
|
|
|
810
1374
|
].join("\n"),
|
|
811
1375
|
async run(_args, ctx) {
|
|
812
1376
|
if (ctx) {
|
|
813
|
-
ctx.
|
|
814
|
-
ctx.
|
|
1377
|
+
ctx.state.replaceMessages([]);
|
|
1378
|
+
ctx.state.replaceTodos([]);
|
|
815
1379
|
ctx.readFiles.clear();
|
|
816
1380
|
ctx.fileMtimes.clear();
|
|
817
|
-
ctx.meta
|
|
1381
|
+
for (const key of Object.keys(ctx.meta)) ctx.state.deleteMeta(key);
|
|
818
1382
|
}
|
|
819
1383
|
await opts.memoryStore?.clear();
|
|
820
1384
|
opts.onClear?.();
|
|
@@ -960,21 +1524,25 @@ ${lines.join("\n")}
|
|
|
960
1524
|
function skillCommand(opts) {
|
|
961
1525
|
return {
|
|
962
1526
|
name: "skill",
|
|
963
|
-
description: "Show
|
|
1527
|
+
description: "Show skill details or list available skills.",
|
|
964
1528
|
async run(args) {
|
|
965
1529
|
if (!opts.skillLoader) {
|
|
966
1530
|
const msg = "No skill loader configured.";
|
|
967
1531
|
return { message: msg };
|
|
968
1532
|
}
|
|
969
1533
|
if (!args.trim()) {
|
|
970
|
-
const
|
|
971
|
-
if (
|
|
1534
|
+
const entries = await opts.skillLoader.listEntries();
|
|
1535
|
+
if (entries.length === 0) {
|
|
972
1536
|
const msg2 = "No skills found.";
|
|
973
1537
|
return { message: msg2 };
|
|
974
1538
|
}
|
|
975
|
-
const lines =
|
|
976
|
-
|
|
977
|
-
${
|
|
1539
|
+
const lines = entries.map((e) => {
|
|
1540
|
+
const scopeTag = e.scope.length > 0 ? ` ${color.dim(`(${e.scope.slice(0, 3).join(", ")})`)}` : "";
|
|
1541
|
+
return ` ${color.bold(e.name)}${scopeTag}
|
|
1542
|
+
Use when: ${e.trigger}`;
|
|
1543
|
+
});
|
|
1544
|
+
const msg = `Available skills:
|
|
1545
|
+
${lines.join("\n\n")}
|
|
978
1546
|
`;
|
|
979
1547
|
return { message: msg };
|
|
980
1548
|
}
|
|
@@ -1498,7 +2066,7 @@ function patchConfig(base, patch) {
|
|
|
1498
2066
|
|
|
1499
2067
|
// src/repl.ts
|
|
1500
2068
|
async function runRepl(opts) {
|
|
1501
|
-
if (opts.banner !== false) printBanner(opts.renderer);
|
|
2069
|
+
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
1502
2070
|
let activeCtrl;
|
|
1503
2071
|
let interrupts = 0;
|
|
1504
2072
|
const onSigint = () => {
|
|
@@ -1516,78 +2084,82 @@ async function runRepl(opts) {
|
|
|
1516
2084
|
};
|
|
1517
2085
|
process.on("SIGINT", onSigint);
|
|
1518
2086
|
const builder = new InputBuilder({ store: opts.attachments });
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
raw = await readPossiblyMultiline(opts);
|
|
1523
|
-
} catch {
|
|
1524
|
-
break;
|
|
1525
|
-
}
|
|
1526
|
-
const trimmed = raw.trim();
|
|
1527
|
-
if (!trimmed) {
|
|
1528
|
-
interrupts = 0;
|
|
1529
|
-
continue;
|
|
1530
|
-
}
|
|
1531
|
-
interrupts = 0;
|
|
1532
|
-
if (trimmed.startsWith("/")) {
|
|
2087
|
+
try {
|
|
2088
|
+
for (; ; ) {
|
|
2089
|
+
let raw;
|
|
1533
2090
|
try {
|
|
1534
|
-
|
|
1535
|
-
|
|
2091
|
+
raw = await readPossiblyMultiline(opts);
|
|
2092
|
+
} catch {
|
|
2093
|
+
break;
|
|
2094
|
+
}
|
|
2095
|
+
const trimmed = raw.trim();
|
|
2096
|
+
if (!trimmed) {
|
|
2097
|
+
interrupts = 0;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
interrupts = 0;
|
|
2101
|
+
if (trimmed.startsWith("/")) {
|
|
2102
|
+
try {
|
|
2103
|
+
const res = await opts.slashRegistry.dispatch(trimmed, opts.agent.ctx);
|
|
2104
|
+
if (res?.message) opts.renderer.write(`${res.message}
|
|
1536
2105
|
`);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
2106
|
+
if (res?.exit) break;
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
2109
|
+
}
|
|
2110
|
+
continue;
|
|
1540
2111
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
const lineCount = raw.split("\n").length;
|
|
1546
|
-
opts.renderer.write(color.dim(` \u21B3 ${ph} (${lineCount} lines)
|
|
2112
|
+
const ph = await builder.appendPaste(raw);
|
|
2113
|
+
if (ph) {
|
|
2114
|
+
const lineCount = raw.split("\n").length;
|
|
2115
|
+
opts.renderer.write(color.dim(` \u21B3 ${ph} (${lineCount} lines)
|
|
1547
2116
|
`));
|
|
1548
|
-
}
|
|
1549
|
-
const blocks = await builder.submit();
|
|
1550
|
-
const runCtrl = new AbortController();
|
|
1551
|
-
activeCtrl = runCtrl;
|
|
1552
|
-
try {
|
|
1553
|
-
const startedAt = Date.now();
|
|
1554
|
-
const before = opts.tokenCounter?.total();
|
|
1555
|
-
const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
|
|
1556
|
-
const result = await opts.agent.run(blocks, { signal: runCtrl.signal });
|
|
1557
|
-
if (result.status === "aborted") {
|
|
1558
|
-
opts.renderer.writeWarning("Aborted.");
|
|
1559
|
-
} else if (result.status === "failed") {
|
|
1560
|
-
const err = result.error;
|
|
1561
|
-
if (err) {
|
|
1562
|
-
const tag = err.recoverable ? " (recoverable)" : "";
|
|
1563
|
-
opts.renderer.writeError(`Failed [${err.severity}]${tag}: ${err.describe()}`);
|
|
1564
|
-
} else {
|
|
1565
|
-
opts.renderer.writeError("Failed.");
|
|
1566
|
-
}
|
|
1567
|
-
} else if (result.status === "max_iterations") {
|
|
1568
|
-
opts.renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
1569
2117
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
2118
|
+
const blocks = await builder.submit();
|
|
2119
|
+
const runCtrl = new AbortController();
|
|
2120
|
+
activeCtrl = runCtrl;
|
|
2121
|
+
try {
|
|
2122
|
+
const startedAt = Date.now();
|
|
2123
|
+
const before = opts.tokenCounter?.total();
|
|
2124
|
+
const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
|
|
2125
|
+
const result = await opts.agent.run(blocks, { signal: runCtrl.signal });
|
|
2126
|
+
if (result.status === "aborted") {
|
|
2127
|
+
opts.renderer.writeWarning("Aborted.");
|
|
2128
|
+
} else if (result.status === "failed") {
|
|
2129
|
+
const err = result.error;
|
|
2130
|
+
if (err) {
|
|
2131
|
+
const tag = err.recoverable ? " (recoverable)" : "";
|
|
2132
|
+
opts.renderer.writeError(`Failed [${err.severity}]${tag}: ${err.describe()}`);
|
|
2133
|
+
} else {
|
|
2134
|
+
opts.renderer.writeError("Failed.");
|
|
2135
|
+
}
|
|
2136
|
+
} else if (result.status === "max_iterations") {
|
|
2137
|
+
opts.renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
2138
|
+
}
|
|
2139
|
+
if (opts.tokenCounter && before) {
|
|
2140
|
+
const after = opts.tokenCounter.total();
|
|
2141
|
+
const costAfter = opts.tokenCounter.estimateCost().total;
|
|
2142
|
+
const ctxChip = opts.effectiveMaxContext && opts.effectiveMaxContext > 0 ? ` ctx: ${renderContextChip(after.input, opts.effectiveMaxContext)}` : "";
|
|
2143
|
+
opts.renderer.write(
|
|
2144
|
+
`
|
|
1576
2145
|
${color.dim(
|
|
1577
|
-
|
|
1578
|
-
|
|
2146
|
+
`[in: ${fmtTok(after.input - before.input)} out: ${fmtTok(after.output - before.output)} iters: ${result.iterations} cost: ${(costAfter - costBefore).toFixed(4)} ${((Date.now() - startedAt) / 1e3).toFixed(1)}s]${ctxChip}`
|
|
2147
|
+
)}
|
|
1579
2148
|
`
|
|
1580
|
-
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
2153
|
+
} finally {
|
|
2154
|
+
activeCtrl = void 0;
|
|
1581
2155
|
}
|
|
1582
|
-
} catch (err) {
|
|
1583
|
-
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
1584
|
-
} finally {
|
|
1585
|
-
activeCtrl = void 0;
|
|
1586
2156
|
}
|
|
2157
|
+
return 0;
|
|
2158
|
+
} finally {
|
|
2159
|
+
process.off("SIGINT", onSigint);
|
|
2160
|
+
await opts.reader.close().catch(() => {
|
|
2161
|
+
});
|
|
1587
2162
|
}
|
|
1588
|
-
process.off("SIGINT", onSigint);
|
|
1589
|
-
await opts.reader.close();
|
|
1590
|
-
return 0;
|
|
1591
2163
|
}
|
|
1592
2164
|
async function readPossiblyMultiline(opts) {
|
|
1593
2165
|
const firstPrompt = theme.primary("\u203A ");
|
|
@@ -1624,13 +2196,15 @@ function renderProgress(ratio, width) {
|
|
|
1624
2196
|
const capped = Math.min(width, filled);
|
|
1625
2197
|
return FILLED.repeat(capped) + EMPTY.repeat(width - capped);
|
|
1626
2198
|
}
|
|
1627
|
-
function printBanner(renderer) {
|
|
2199
|
+
function printBanner(renderer, projectName) {
|
|
1628
2200
|
const lines = [
|
|
1629
2201
|
theme.primary(theme.bold("WrongStack")) + color.dim(` v${CLI_VERSION}`),
|
|
1630
|
-
color.dim("Built on the wrong stack. Shipped anyway.")
|
|
1631
|
-
color.dim("Type /help for commands, /exit to quit."),
|
|
1632
|
-
""
|
|
2202
|
+
color.dim("Built on the wrong stack. Shipped anyway.")
|
|
1633
2203
|
];
|
|
2204
|
+
if (projectName && projectName.length > 0) {
|
|
2205
|
+
lines.push(color.dim("Project: ") + theme.bold(projectName));
|
|
2206
|
+
}
|
|
2207
|
+
lines.push(color.dim("Type /help for commands, /exit to quit."), "");
|
|
1634
2208
|
renderer.write(`${lines.join("\n")}
|
|
1635
2209
|
`);
|
|
1636
2210
|
}
|
|
@@ -1929,6 +2503,11 @@ var MultiAgentHost = class {
|
|
|
1929
2503
|
systemPrompt: baseSystem,
|
|
1930
2504
|
provider,
|
|
1931
2505
|
session: subSession,
|
|
2506
|
+
// Placeholder — Agent.run() overwrites ctx.signal with the live
|
|
2507
|
+
// per-run signal (see core/agent.ts run()). Tools/middleware that
|
|
2508
|
+
// read ctx.signal after construction will see the runtime signal,
|
|
2509
|
+
// not this one. Kept as `new AbortController().signal` so the
|
|
2510
|
+
// initial value is non-null/non-aborted.
|
|
1932
2511
|
signal: new AbortController().signal,
|
|
1933
2512
|
tokenCounter: this.deps.tokenCounter,
|
|
1934
2513
|
cwd: this.deps.cwd,
|
|
@@ -3418,7 +3997,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
3418
3997
|
"alt-screen",
|
|
3419
3998
|
"output-json",
|
|
3420
3999
|
"prompt",
|
|
3421
|
-
"metrics"
|
|
4000
|
+
"metrics",
|
|
4001
|
+
"webui"
|
|
3422
4002
|
]);
|
|
3423
4003
|
function parseArgs(argv) {
|
|
3424
4004
|
const flags = {};
|
|
@@ -3618,6 +4198,8 @@ async function main(argv) {
|
|
|
3618
4198
|
TOKENS.TokenCounter,
|
|
3619
4199
|
() => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider })
|
|
3620
4200
|
);
|
|
4201
|
+
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
4202
|
+
container.bind(TOKENS.ModeStore, () => modeStore);
|
|
3621
4203
|
container.bind(
|
|
3622
4204
|
TOKENS.SessionStore,
|
|
3623
4205
|
() => new DefaultSessionStore({ dir: wpaths.projectSessions })
|
|
@@ -3629,11 +4211,25 @@ async function main(argv) {
|
|
|
3629
4211
|
bundledDir: config.features.skills ? resolveBundledSkillsDir() : void 0
|
|
3630
4212
|
});
|
|
3631
4213
|
container.bind(TOKENS.SkillLoader, () => skillLoader);
|
|
4214
|
+
const activeMode = await modeStore.getActiveMode();
|
|
4215
|
+
const modeId = activeMode?.id ?? "default";
|
|
4216
|
+
const modePrompt = activeMode?.prompt ?? "";
|
|
4217
|
+
const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
|
|
4218
|
+
const modelCapabilities = resolvedModel?.capabilities ? {
|
|
4219
|
+
maxContextTokens: resolvedModel.capabilities.maxContext,
|
|
4220
|
+
supportsTools: resolvedModel.capabilities.tools,
|
|
4221
|
+
supportsVision: resolvedModel.capabilities.vision,
|
|
4222
|
+
supportsReasoning: resolvedModel.capabilities.reasoning
|
|
4223
|
+
} : void 0;
|
|
3632
4224
|
container.bind(
|
|
3633
4225
|
TOKENS.SystemPromptBuilder,
|
|
3634
4226
|
() => new DefaultSystemPromptBuilder({
|
|
3635
4227
|
memoryStore,
|
|
3636
|
-
skillLoader: config.features.skills ? skillLoader : void 0
|
|
4228
|
+
skillLoader: config.features.skills ? skillLoader : void 0,
|
|
4229
|
+
modeStore,
|
|
4230
|
+
modeId,
|
|
4231
|
+
modePrompt,
|
|
4232
|
+
modelCapabilities
|
|
3637
4233
|
})
|
|
3638
4234
|
);
|
|
3639
4235
|
container.bind(TOKENS.Renderer, () => renderer);
|
|
@@ -3897,7 +4493,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
3897
4493
|
model: config.model
|
|
3898
4494
|
});
|
|
3899
4495
|
if (restoredMessages.length > 0) {
|
|
3900
|
-
context.
|
|
4496
|
+
context.state.replaceMessages(restoredMessages);
|
|
3901
4497
|
}
|
|
3902
4498
|
const pipelines = createDefaultPipelines();
|
|
3903
4499
|
const installBoundary = (p) => {
|
|
@@ -3957,7 +4553,11 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
3957
4553
|
soft: config.context.softThreshold,
|
|
3958
4554
|
hard: config.context.hardThreshold
|
|
3959
4555
|
},
|
|
3960
|
-
|
|
4556
|
+
{
|
|
4557
|
+
aggressiveOn: "soft",
|
|
4558
|
+
failureMode: "throw_on_hard",
|
|
4559
|
+
events
|
|
4560
|
+
}
|
|
3961
4561
|
);
|
|
3962
4562
|
pipelines.contextWindow.use({
|
|
3963
4563
|
name: "AutoCompaction",
|
|
@@ -4255,11 +4855,36 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4255
4855
|
process.stdout.write(
|
|
4256
4856
|
color.dim(`Session saved: ${session.id} \u2014 resume with `) + color.cyan(`wstack resume ${session.id}`) + "\n"
|
|
4257
4857
|
);
|
|
4858
|
+
},
|
|
4859
|
+
onClearHistory: (dispatch) => {
|
|
4860
|
+
dispatch({ type: "clearHistory" });
|
|
4861
|
+
dispatch({ type: "resetContextChip" });
|
|
4258
4862
|
}
|
|
4259
4863
|
});
|
|
4260
4864
|
} finally {
|
|
4261
4865
|
renderer.setSilent(false);
|
|
4262
4866
|
}
|
|
4867
|
+
} else if (flags.webui) {
|
|
4868
|
+
const { runWebUI: runWebUI2 } = await Promise.resolve().then(() => (init_webui_server(), webui_server_exports));
|
|
4869
|
+
const webuiPromise = runWebUI2({
|
|
4870
|
+
agent,
|
|
4871
|
+
events,
|
|
4872
|
+
session,
|
|
4873
|
+
port: Number.parseInt(String(flags.port ?? "3457"), 10),
|
|
4874
|
+
modelsRegistry,
|
|
4875
|
+
globalConfigPath: wpaths.globalConfig
|
|
4876
|
+
});
|
|
4877
|
+
code = await runRepl({
|
|
4878
|
+
agent,
|
|
4879
|
+
renderer,
|
|
4880
|
+
reader,
|
|
4881
|
+
slashRegistry,
|
|
4882
|
+
tokenCounter,
|
|
4883
|
+
attachments,
|
|
4884
|
+
effectiveMaxContext,
|
|
4885
|
+
projectName: path5.basename(projectRoot) || void 0
|
|
4886
|
+
});
|
|
4887
|
+
await webuiPromise;
|
|
4263
4888
|
} else {
|
|
4264
4889
|
code = await runRepl({
|
|
4265
4890
|
agent,
|
|
@@ -4268,7 +4893,8 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4268
4893
|
slashRegistry,
|
|
4269
4894
|
tokenCounter,
|
|
4270
4895
|
attachments,
|
|
4271
|
-
effectiveMaxContext
|
|
4896
|
+
effectiveMaxContext,
|
|
4897
|
+
projectName: path5.basename(projectRoot) || void 0
|
|
4272
4898
|
});
|
|
4273
4899
|
}
|
|
4274
4900
|
} finally {
|