@stjbrown/agent-knowledge 0.1.0-beta.1 → 0.1.0-beta.10
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/NOTICE +5 -4
- package/OBSERVABILITY.md +163 -0
- package/README.md +74 -8
- package/dist/chunk-JZ3GYPHM.js +3993 -0
- package/dist/chunk-JZ3GYPHM.js.map +1 -0
- package/dist/{chunk-J4MELCGD.js → chunk-X6R3XLAQ.js} +19 -6
- package/dist/chunk-X6R3XLAQ.js.map +1 -0
- package/dist/headless.js +2 -2
- package/dist/index.js +2 -2
- package/dist/main.js +7 -18
- package/dist/main.js.map +1 -1
- package/dist/tui-74FJW5Y4.js +1519 -0
- package/dist/tui-74FJW5Y4.js.map +1 -0
- package/package.json +27 -15
- package/skills/kb/SKILL.md +5 -0
- package/skills/kb/example-bundle/spec/types.md +3 -2
- package/skills/kb-ingest/SKILL.md +27 -6
- package/skills/kb-init/SKILL.md +23 -5
- package/skills/kb-lint/SKILL.md +6 -1
- package/dist/chunk-3XSOMUQQ.js +0 -131
- package/dist/chunk-3XSOMUQQ.js.map +0 -1
- package/dist/chunk-J4MELCGD.js.map +0 -1
- package/dist/chunk-YIAVFL7A.js +0 -1732
- package/dist/chunk-YIAVFL7A.js.map +0 -1
- package/dist/tui-VZVO7UHV.js +0 -521
- package/dist/tui-VZVO7UHV.js.map +0 -1
|
@@ -0,0 +1,1519 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __janetCreateRequire } from "node:module";
|
|
3
|
+
const require = __janetCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
GREETING,
|
|
6
|
+
NATIVE_PROVIDER_DEFINITIONS,
|
|
7
|
+
availableModels,
|
|
8
|
+
bootJanet,
|
|
9
|
+
completeOnboarding,
|
|
10
|
+
discoverAvailableModels,
|
|
11
|
+
formatObservabilityStatus,
|
|
12
|
+
getAuthStorage,
|
|
13
|
+
groupModelsByProvider,
|
|
14
|
+
loadSettings,
|
|
15
|
+
messageText,
|
|
16
|
+
normalizeModelSelection,
|
|
17
|
+
rememberModel,
|
|
18
|
+
rememberObservability,
|
|
19
|
+
resolveObservabilityConfig,
|
|
20
|
+
safeObservabilityEndpoint
|
|
21
|
+
} from "./chunk-JZ3GYPHM.js";
|
|
22
|
+
|
|
23
|
+
// src/tui/index.ts
|
|
24
|
+
import {
|
|
25
|
+
Container,
|
|
26
|
+
Editor,
|
|
27
|
+
Loader,
|
|
28
|
+
Markdown,
|
|
29
|
+
ProcessTerminal,
|
|
30
|
+
SelectList,
|
|
31
|
+
Spacer,
|
|
32
|
+
TUI,
|
|
33
|
+
Text,
|
|
34
|
+
matchesKey as matchesKey2
|
|
35
|
+
} from "@earendil-works/pi-tui";
|
|
36
|
+
|
|
37
|
+
// src/memory/compact.ts
|
|
38
|
+
async function compactConversation({
|
|
39
|
+
memory,
|
|
40
|
+
agent,
|
|
41
|
+
threadId,
|
|
42
|
+
resourceId,
|
|
43
|
+
requestContext
|
|
44
|
+
}) {
|
|
45
|
+
const om = await memory.omEngine;
|
|
46
|
+
if (!om) {
|
|
47
|
+
throw new Error("Observational Memory is unavailable for this storage.");
|
|
48
|
+
}
|
|
49
|
+
await om.waitForBuffering(threadId, resourceId);
|
|
50
|
+
const before = await om.getStatus({ threadId, resourceId });
|
|
51
|
+
let buffered = false;
|
|
52
|
+
if (before.pendingTokens > 0) {
|
|
53
|
+
const result = await om.buffer({
|
|
54
|
+
threadId,
|
|
55
|
+
resourceId,
|
|
56
|
+
requestContext,
|
|
57
|
+
agent,
|
|
58
|
+
pendingTokens: before.pendingTokens,
|
|
59
|
+
record: before.record,
|
|
60
|
+
skipMinimumTokenCheck: true
|
|
61
|
+
});
|
|
62
|
+
buffered = result.buffered;
|
|
63
|
+
}
|
|
64
|
+
await om.waitForBuffering(threadId, resourceId);
|
|
65
|
+
const activation = await om.activate({
|
|
66
|
+
threadId,
|
|
67
|
+
resourceId,
|
|
68
|
+
checkThreshold: false
|
|
69
|
+
});
|
|
70
|
+
const afterActivation = await om.getStatus({ threadId, resourceId });
|
|
71
|
+
let reflected = false;
|
|
72
|
+
let record = activation.record;
|
|
73
|
+
if (afterActivation.shouldReflect) {
|
|
74
|
+
const reflection = await om.reflect(
|
|
75
|
+
threadId,
|
|
76
|
+
resourceId,
|
|
77
|
+
void 0,
|
|
78
|
+
requestContext
|
|
79
|
+
);
|
|
80
|
+
reflected = reflection.reflected;
|
|
81
|
+
record = reflection.record;
|
|
82
|
+
}
|
|
83
|
+
const after = await om.getStatus({ threadId, resourceId });
|
|
84
|
+
return {
|
|
85
|
+
pendingTokensBefore: before.pendingTokens,
|
|
86
|
+
pendingTokensAfter: after.pendingTokens,
|
|
87
|
+
observationTokens: record.observationTokenCount,
|
|
88
|
+
buffered,
|
|
89
|
+
activated: activation.activated,
|
|
90
|
+
reflected
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/tui/activity.ts
|
|
95
|
+
var WORKSPACE_READ = /* @__PURE__ */ new Set([
|
|
96
|
+
"mastra_workspace_file_stat",
|
|
97
|
+
"mastra_workspace_grep",
|
|
98
|
+
"mastra_workspace_lsp_inspect",
|
|
99
|
+
"mastra_workspace_list_files",
|
|
100
|
+
"mastra_workspace_read_file",
|
|
101
|
+
"mastra_workspace_search"
|
|
102
|
+
]);
|
|
103
|
+
var WORKSPACE_WRITE = /* @__PURE__ */ new Set([
|
|
104
|
+
"mastra_workspace_ast_edit",
|
|
105
|
+
"mastra_workspace_delete",
|
|
106
|
+
"mastra_workspace_edit_file",
|
|
107
|
+
"mastra_workspace_index",
|
|
108
|
+
"mastra_workspace_mkdir",
|
|
109
|
+
"mastra_workspace_write_file"
|
|
110
|
+
]);
|
|
111
|
+
var WORKSPACE_EXECUTE = /* @__PURE__ */ new Set([
|
|
112
|
+
"mastra_workspace_execute_command",
|
|
113
|
+
"mastra_workspace_get_process_output",
|
|
114
|
+
"mastra_workspace_kill_process"
|
|
115
|
+
]);
|
|
116
|
+
var PDF_READ = /* @__PURE__ */ new Set(["janet_read_pdf", "janet_read_pdf_chunk"]);
|
|
117
|
+
var WEB_READ = /* @__PURE__ */ new Set(["janet_web_fetch", "janet_web_fetch_chunk"]);
|
|
118
|
+
function toolActivityLabel(toolName) {
|
|
119
|
+
if (toolName === "skill" || toolName === "skill_read" || toolName === "skill_search") {
|
|
120
|
+
return "Janet is reading the playbook\u2026";
|
|
121
|
+
}
|
|
122
|
+
if (PDF_READ.has(toolName)) return "Janet is reading the document\u2026";
|
|
123
|
+
if (WEB_READ.has(toolName)) return "Janet is reading the page\u2026";
|
|
124
|
+
if (WORKSPACE_READ.has(toolName)) return "Janet is checking the workspace\u2026";
|
|
125
|
+
if (WORKSPACE_WRITE.has(toolName)) return "Janet is updating the bundle\u2026";
|
|
126
|
+
if (WORKSPACE_EXECUTE.has(toolName) || toolName.includes("shell")) {
|
|
127
|
+
return "Janet is running a check\u2026";
|
|
128
|
+
}
|
|
129
|
+
return "Janet is working\u2026";
|
|
130
|
+
}
|
|
131
|
+
function toolErrorLabel(result) {
|
|
132
|
+
const detail = String(result);
|
|
133
|
+
const readRequired = detail.match(
|
|
134
|
+
/File "([^"]+)" (?:has not been read|was modified since last read)/
|
|
135
|
+
);
|
|
136
|
+
if (readRequired) {
|
|
137
|
+
return `Update paused: Janet needs to re-read "${readRequired[1]}" first.`;
|
|
138
|
+
}
|
|
139
|
+
return `Tool error: ${detail.slice(0, 140)}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/tui/interrupt.ts
|
|
143
|
+
function createInterruptController(actions, options = {}) {
|
|
144
|
+
const doublePressMs = options.doublePressMs ?? 800;
|
|
145
|
+
const now = options.now ?? Date.now;
|
|
146
|
+
let lastCtrlC;
|
|
147
|
+
const cancelRun = () => {
|
|
148
|
+
if (!actions.isRunning()) return "ignored";
|
|
149
|
+
actions.abortRun();
|
|
150
|
+
actions.notify("cancelled");
|
|
151
|
+
return "cancelled";
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
handleCtrlC() {
|
|
155
|
+
const pressedAt = now();
|
|
156
|
+
if (lastCtrlC !== void 0 && pressedAt - lastCtrlC < doublePressMs) {
|
|
157
|
+
actions.notify("exit");
|
|
158
|
+
actions.exit();
|
|
159
|
+
return "exit";
|
|
160
|
+
}
|
|
161
|
+
lastCtrlC = pressedAt;
|
|
162
|
+
const cancelled = cancelRun();
|
|
163
|
+
if (cancelled !== "ignored") return cancelled;
|
|
164
|
+
if (actions.hasInput()) {
|
|
165
|
+
actions.clearInput();
|
|
166
|
+
actions.notify("cleared");
|
|
167
|
+
return "cleared";
|
|
168
|
+
}
|
|
169
|
+
actions.notify("exit-hint");
|
|
170
|
+
return "exit-hint";
|
|
171
|
+
},
|
|
172
|
+
handleEscape() {
|
|
173
|
+
return cancelRun();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/tui/multi-select.ts
|
|
179
|
+
import {
|
|
180
|
+
getKeybindings,
|
|
181
|
+
matchesKey,
|
|
182
|
+
truncateToWidth,
|
|
183
|
+
visibleWidth
|
|
184
|
+
} from "@earendil-works/pi-tui";
|
|
185
|
+
var MultiSelectList = class {
|
|
186
|
+
constructor(items, maxVisible, theme, initiallySelected = []) {
|
|
187
|
+
this.items = items;
|
|
188
|
+
this.maxVisible = maxVisible;
|
|
189
|
+
this.theme = theme;
|
|
190
|
+
this.selectedValues = new Set(initiallySelected);
|
|
191
|
+
}
|
|
192
|
+
items;
|
|
193
|
+
maxVisible;
|
|
194
|
+
theme;
|
|
195
|
+
selectedIndex = 0;
|
|
196
|
+
selectedValues;
|
|
197
|
+
onConfirm;
|
|
198
|
+
onCancel;
|
|
199
|
+
setSelectedIndex(index) {
|
|
200
|
+
this.selectedIndex = Math.max(0, Math.min(index, this.items.length - 1));
|
|
201
|
+
}
|
|
202
|
+
getSelectedItems() {
|
|
203
|
+
return this.items.filter((item) => this.selectedValues.has(item.value));
|
|
204
|
+
}
|
|
205
|
+
invalidate() {
|
|
206
|
+
}
|
|
207
|
+
render(width) {
|
|
208
|
+
if (!this.items.length) {
|
|
209
|
+
return [this.theme.noMatch(" No options")];
|
|
210
|
+
}
|
|
211
|
+
const startIndex = Math.max(
|
|
212
|
+
0,
|
|
213
|
+
Math.min(
|
|
214
|
+
this.selectedIndex - Math.floor(this.maxVisible / 2),
|
|
215
|
+
this.items.length - this.maxVisible
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
const endIndex = Math.min(
|
|
219
|
+
startIndex + Math.max(1, this.maxVisible),
|
|
220
|
+
this.items.length
|
|
221
|
+
);
|
|
222
|
+
const lines = this.items.slice(startIndex, endIndex).map(
|
|
223
|
+
(item, offset) => this.renderItem(item, startIndex + offset === this.selectedIndex, width)
|
|
224
|
+
);
|
|
225
|
+
if (startIndex > 0 || endIndex < this.items.length) {
|
|
226
|
+
lines.push(
|
|
227
|
+
this.theme.scrollInfo(
|
|
228
|
+
truncateToWidth(
|
|
229
|
+
` (${this.selectedIndex + 1}/${this.items.length})`,
|
|
230
|
+
Math.max(1, width - 2),
|
|
231
|
+
""
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return lines;
|
|
237
|
+
}
|
|
238
|
+
handleInput(keyData) {
|
|
239
|
+
const keybindings = getKeybindings();
|
|
240
|
+
if (!this.items.length) {
|
|
241
|
+
if (keybindings.matches(keyData, "tui.select.cancel")) this.onCancel?.();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (keybindings.matches(keyData, "tui.select.up")) {
|
|
245
|
+
this.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;
|
|
246
|
+
} else if (keybindings.matches(keyData, "tui.select.down")) {
|
|
247
|
+
this.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;
|
|
248
|
+
} else if (keybindings.matches(keyData, "tui.select.pageUp")) {
|
|
249
|
+
this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);
|
|
250
|
+
} else if (keybindings.matches(keyData, "tui.select.pageDown")) {
|
|
251
|
+
this.selectedIndex = Math.min(
|
|
252
|
+
this.items.length - 1,
|
|
253
|
+
this.selectedIndex + this.maxVisible
|
|
254
|
+
);
|
|
255
|
+
} else if (matchesKey(keyData, "space")) {
|
|
256
|
+
const item = this.items[this.selectedIndex];
|
|
257
|
+
if (this.selectedValues.has(item.value)) {
|
|
258
|
+
this.selectedValues.delete(item.value);
|
|
259
|
+
} else {
|
|
260
|
+
this.selectedValues.add(item.value);
|
|
261
|
+
}
|
|
262
|
+
} else if (keybindings.matches(keyData, "tui.select.confirm")) {
|
|
263
|
+
this.onConfirm?.(this.getSelectedItems());
|
|
264
|
+
} else if (keybindings.matches(keyData, "tui.select.cancel")) {
|
|
265
|
+
this.onCancel?.();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
renderItem(item, isActive, width) {
|
|
269
|
+
const checked = this.selectedValues.has(item.value);
|
|
270
|
+
const prefix = isActive ? "\u2192 " : " ";
|
|
271
|
+
const checkbox = checked ? "[x]" : "[ ]";
|
|
272
|
+
const maxWidth = Math.max(1, width - 2);
|
|
273
|
+
const primary = truncateToWidth(
|
|
274
|
+
`${prefix}${checkbox} ${item.label || item.value}`,
|
|
275
|
+
maxWidth,
|
|
276
|
+
""
|
|
277
|
+
);
|
|
278
|
+
let plain = primary;
|
|
279
|
+
if (item.description && width > 40) {
|
|
280
|
+
const remaining = maxWidth - visibleWidth(primary) - 2;
|
|
281
|
+
if (remaining > 10) {
|
|
282
|
+
plain += ` ${truncateToWidth(
|
|
283
|
+
item.description.replace(/[\r\n]+/g, " ").trim(),
|
|
284
|
+
remaining,
|
|
285
|
+
""
|
|
286
|
+
)}`;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (isActive) return this.theme.selectedText(plain);
|
|
290
|
+
if (!checked) return plain;
|
|
291
|
+
const markStart = prefix.length;
|
|
292
|
+
return plain.slice(0, markStart) + this.theme.selectedPrefix(checkbox) + plain.slice(markStart + checkbox.length);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// src/tui/thread.ts
|
|
297
|
+
async function clearConversation(thread) {
|
|
298
|
+
const previousThreadId = thread.getId();
|
|
299
|
+
const created = await thread.create({ title: "Janet conversation" });
|
|
300
|
+
return { previousThreadId, threadId: created.id };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/tui/traces.ts
|
|
304
|
+
function duration(span) {
|
|
305
|
+
if (!span.endedAt) return "running";
|
|
306
|
+
const elapsed = Math.max(0, span.endedAt.getTime() - span.startedAt.getTime());
|
|
307
|
+
return elapsed >= 1e3 ? `${(elapsed / 1e3).toFixed(1)}s` : `${elapsed}ms`;
|
|
308
|
+
}
|
|
309
|
+
function traceStatus(span) {
|
|
310
|
+
if (span.error) return "error";
|
|
311
|
+
return span.endedAt ? "ok" : "running";
|
|
312
|
+
}
|
|
313
|
+
function formatTraceTree(spans) {
|
|
314
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
315
|
+
const ids = new Set(spans.map((span) => span.spanId));
|
|
316
|
+
for (const span of spans) {
|
|
317
|
+
const parent = span.parentSpanId && ids.has(span.parentSpanId) ? span.parentSpanId : null;
|
|
318
|
+
const children = byParent.get(parent) ?? [];
|
|
319
|
+
children.push(span);
|
|
320
|
+
byParent.set(parent, children);
|
|
321
|
+
}
|
|
322
|
+
for (const children of byParent.values()) {
|
|
323
|
+
children.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
|
324
|
+
}
|
|
325
|
+
const lines = [];
|
|
326
|
+
const visited = /* @__PURE__ */ new Set();
|
|
327
|
+
const visit = (span, depth) => {
|
|
328
|
+
if (visited.has(span.spanId)) return;
|
|
329
|
+
visited.add(span.spanId);
|
|
330
|
+
const status = traceStatus(span);
|
|
331
|
+
const marker = status === "error" ? "\u2717" : status === "running" ? "\u2026" : "\u2713";
|
|
332
|
+
lines.push(
|
|
333
|
+
`${" ".repeat(depth)}${marker} ${span.name} \xB7 ${span.spanType} \xB7 ${duration(span)}`
|
|
334
|
+
);
|
|
335
|
+
for (const child of byParent.get(span.spanId) ?? []) visit(child, depth + 1);
|
|
336
|
+
};
|
|
337
|
+
for (const root of byParent.get(null) ?? []) visit(root, 0);
|
|
338
|
+
return lines;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/tui/theme.ts
|
|
342
|
+
import chalk from "chalk";
|
|
343
|
+
var c = {
|
|
344
|
+
accent: chalk.cyan,
|
|
345
|
+
accentBold: chalk.cyan.bold,
|
|
346
|
+
dim: chalk.dim,
|
|
347
|
+
user: chalk.green,
|
|
348
|
+
error: chalk.red,
|
|
349
|
+
warn: chalk.yellow,
|
|
350
|
+
bold: chalk.bold,
|
|
351
|
+
italic: chalk.italic
|
|
352
|
+
};
|
|
353
|
+
var editorTheme = {
|
|
354
|
+
borderColor: (s) => chalk.cyan(s),
|
|
355
|
+
selectList: {
|
|
356
|
+
selectedPrefix: (s) => chalk.cyan(s),
|
|
357
|
+
selectedText: (s) => chalk.cyan.bold(s),
|
|
358
|
+
description: (s) => chalk.dim(s),
|
|
359
|
+
scrollInfo: (s) => chalk.dim(s),
|
|
360
|
+
noMatch: (s) => chalk.dim(s)
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
var markdownTheme = {
|
|
364
|
+
heading: (s) => chalk.cyan.bold(s),
|
|
365
|
+
link: (s) => chalk.cyan.underline(s),
|
|
366
|
+
linkUrl: (s) => chalk.dim(s),
|
|
367
|
+
code: (s) => chalk.yellow(s),
|
|
368
|
+
codeBlock: (s) => chalk.yellow(s),
|
|
369
|
+
codeBlockBorder: (s) => chalk.dim(s),
|
|
370
|
+
quote: (s) => chalk.italic(s),
|
|
371
|
+
quoteBorder: (s) => chalk.dim(s),
|
|
372
|
+
hr: (s) => chalk.dim(s),
|
|
373
|
+
listBullet: (s) => chalk.cyan(s),
|
|
374
|
+
bold: (s) => chalk.bold(s),
|
|
375
|
+
italic: (s) => chalk.italic(s),
|
|
376
|
+
strikethrough: (s) => chalk.strikethrough(s),
|
|
377
|
+
underline: (s) => chalk.underline(s)
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/tui/index.ts
|
|
381
|
+
var OAUTH_PROVIDERS = ["anthropic", "openai-codex"];
|
|
382
|
+
var HELP_TEXT = `Commands:
|
|
383
|
+
/models Pick one or more providers, then a model
|
|
384
|
+
/model [provider/id] Open the picker, or switch directly by id
|
|
385
|
+
/providers Browse provider status and setup
|
|
386
|
+
/login <provider> [mode]
|
|
387
|
+
Log in; OpenAI mode is browser or device
|
|
388
|
+
/logout <provider> Remove stored credentials for a provider
|
|
389
|
+
/auth Show which providers are authenticated
|
|
390
|
+
/observability Configure opt-in tracing
|
|
391
|
+
/traces Browse recent local traces
|
|
392
|
+
/compact Flush this conversation into Observational Memory
|
|
393
|
+
/clear Start a blank conversation (keeps the old thread)
|
|
394
|
+
/cancel Cancel the active run
|
|
395
|
+
/help This help
|
|
396
|
+
/quit Exit (double Ctrl+C also works)
|
|
397
|
+
|
|
398
|
+
While Janet is working, Esc or Ctrl+C cancels the active run.
|
|
399
|
+
Anything else is a message to Janet.`;
|
|
400
|
+
function shortTokens(tokens) {
|
|
401
|
+
if (tokens < 1e3) return String(Math.max(0, Math.round(tokens)));
|
|
402
|
+
const thousands = tokens / 1e3;
|
|
403
|
+
return `${thousands >= 10 ? Math.round(thousands) : thousands.toFixed(1)}k`;
|
|
404
|
+
}
|
|
405
|
+
function resolveAnswer(q, text) {
|
|
406
|
+
if (!q.options?.length) return text;
|
|
407
|
+
const opts = q.options;
|
|
408
|
+
const pick = (token) => {
|
|
409
|
+
const t = token.trim();
|
|
410
|
+
if (!t) return void 0;
|
|
411
|
+
const n = Number(t);
|
|
412
|
+
if (Number.isInteger(n) && n >= 1 && n <= opts.length) return opts[n - 1].label;
|
|
413
|
+
const exact = opts.find((o) => o.label.toLowerCase() === t.toLowerCase());
|
|
414
|
+
if (exact) return exact.label;
|
|
415
|
+
return opts.find((o) => o.label.toLowerCase().startsWith(t.toLowerCase()))?.label;
|
|
416
|
+
};
|
|
417
|
+
if (q.multi) {
|
|
418
|
+
const picks = text.split(",").map(pick);
|
|
419
|
+
return picks.some((p) => p === void 0) ? void 0 : picks;
|
|
420
|
+
}
|
|
421
|
+
return pick(text);
|
|
422
|
+
}
|
|
423
|
+
async function runTui(opts) {
|
|
424
|
+
const { controller, session, paths, herdrDetach, observability } = await bootJanet({
|
|
425
|
+
...opts,
|
|
426
|
+
interactive: true
|
|
427
|
+
});
|
|
428
|
+
const persistedModel = process.env["JANET_MODEL"] || loadSettings().defaultModelId;
|
|
429
|
+
const presetModel = persistedModel ? normalizeModelSelection(persistedModel, availableModels()) : void 0;
|
|
430
|
+
if (!session.model.hasSelection() && presetModel) {
|
|
431
|
+
await session.model.switch({ modelId: presetModel });
|
|
432
|
+
}
|
|
433
|
+
const terminal = new ProcessTerminal();
|
|
434
|
+
const ui = new TUI(terminal);
|
|
435
|
+
const chat = new Container();
|
|
436
|
+
const status = new Text("", 1, 0);
|
|
437
|
+
const editor = new Editor(ui, editorTheme);
|
|
438
|
+
const loader = new Loader(ui, c.accent, c.dim, "Janet is thinking\u2026");
|
|
439
|
+
ui.addChild(chat);
|
|
440
|
+
ui.addChild(new Spacer(1));
|
|
441
|
+
ui.addChild(editor);
|
|
442
|
+
ui.addChild(status);
|
|
443
|
+
let running = false;
|
|
444
|
+
let loaderMounted = false;
|
|
445
|
+
let pendingApproval = null;
|
|
446
|
+
let pendingQuestion = null;
|
|
447
|
+
let pendingInput = null;
|
|
448
|
+
let activeSelect = null;
|
|
449
|
+
let active = null;
|
|
450
|
+
let cancelRequested = false;
|
|
451
|
+
let modelPickerLoading = false;
|
|
452
|
+
let compacting = false;
|
|
453
|
+
let omWindows = null;
|
|
454
|
+
let omActivity = null;
|
|
455
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
456
|
+
const updateStatus = () => {
|
|
457
|
+
const model = session.model.hasSelection() ? session.model.get() : "no model \u2014 /model <id>";
|
|
458
|
+
const tracing = observability.status.enabled ? c.dim(` \xB7 trace:${observability.status.capture}`) : "";
|
|
459
|
+
const memory = omWindows ? c.dim(
|
|
460
|
+
` \xB7 mem:${shortTokens(
|
|
461
|
+
omWindows.active.messages.tokens + omWindows.active.observations.tokens
|
|
462
|
+
)}/${shortTokens(
|
|
463
|
+
omWindows.active.messages.threshold + omWindows.active.observations.threshold
|
|
464
|
+
)}`
|
|
465
|
+
) : "";
|
|
466
|
+
const state = pendingInput ? "enter the requested value" : compacting ? "compacting memory" : omActivity ? `${omActivity} memory` : pendingQuestion || activeSelect ? "answer Janet's question" : pendingApproval ? "awaiting approval" : cancelRequested ? "cancelling" : running ? "working \xB7 Esc/Ctrl+C cancels" : "idle";
|
|
467
|
+
status.setText(
|
|
468
|
+
c.dim(`${paths.projectPath} \xB7 `) + c.accent(model) + c.dim(` \xB7 ${state}`) + memory + tracing
|
|
469
|
+
);
|
|
470
|
+
ui.requestRender();
|
|
471
|
+
};
|
|
472
|
+
const appendToChat = (comp) => {
|
|
473
|
+
if (loaderMounted) chat.removeChild(loader);
|
|
474
|
+
if (activeSelect) chat.removeChild(activeSelect);
|
|
475
|
+
chat.addChild(comp);
|
|
476
|
+
if (activeSelect) chat.addChild(activeSelect);
|
|
477
|
+
if (loaderMounted) chat.addChild(loader);
|
|
478
|
+
ui.requestRender();
|
|
479
|
+
};
|
|
480
|
+
const addLine = (text) => appendToChat(new Text(text, 1, 0));
|
|
481
|
+
const setLoader = (on) => {
|
|
482
|
+
if (on && !loaderMounted) {
|
|
483
|
+
chat.addChild(loader);
|
|
484
|
+
loader.start();
|
|
485
|
+
loaderMounted = true;
|
|
486
|
+
} else if (!on && loaderMounted) {
|
|
487
|
+
loader.stop();
|
|
488
|
+
chat.removeChild(loader);
|
|
489
|
+
loaderMounted = false;
|
|
490
|
+
}
|
|
491
|
+
ui.requestRender();
|
|
492
|
+
};
|
|
493
|
+
const closeSegment = () => {
|
|
494
|
+
if (active) {
|
|
495
|
+
active.committedLen = active.lastText.length;
|
|
496
|
+
active.comp = null;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
const answerQuestion = (resumeData, echo) => {
|
|
500
|
+
if (activeSelect) {
|
|
501
|
+
chat.removeChild(activeSelect);
|
|
502
|
+
activeSelect = null;
|
|
503
|
+
}
|
|
504
|
+
const q = pendingQuestion;
|
|
505
|
+
pendingQuestion = null;
|
|
506
|
+
ui.setFocus(editor);
|
|
507
|
+
addLine(c.user(`\u276F ${echo}`));
|
|
508
|
+
setLoader(true);
|
|
509
|
+
updateStatus();
|
|
510
|
+
if (q) void session.respondToToolSuspension({ toolCallId: q.toolCallId, resumeData });
|
|
511
|
+
};
|
|
512
|
+
const onEvent = (event) => {
|
|
513
|
+
switch (event.type) {
|
|
514
|
+
case "agent_start":
|
|
515
|
+
running = true;
|
|
516
|
+
cancelRequested = false;
|
|
517
|
+
active = null;
|
|
518
|
+
activeTools.clear();
|
|
519
|
+
loader.setMessage("Janet is thinking\u2026");
|
|
520
|
+
setLoader(true);
|
|
521
|
+
updateStatus();
|
|
522
|
+
break;
|
|
523
|
+
case "message_update":
|
|
524
|
+
case "message_end": {
|
|
525
|
+
if (event.message.role !== "assistant") break;
|
|
526
|
+
const text = messageText(event.message);
|
|
527
|
+
if (!text) break;
|
|
528
|
+
if (!active || active.id !== event.message.id) {
|
|
529
|
+
active = { id: event.message.id, committedLen: 0, comp: null, lastText: "" };
|
|
530
|
+
}
|
|
531
|
+
active.lastText = text;
|
|
532
|
+
const tail = text.slice(active.committedLen);
|
|
533
|
+
if (!tail) break;
|
|
534
|
+
if (!active.comp) {
|
|
535
|
+
active.comp = new Markdown(tail, 1, 0, markdownTheme);
|
|
536
|
+
appendToChat(active.comp);
|
|
537
|
+
} else {
|
|
538
|
+
active.comp.setText(tail);
|
|
539
|
+
ui.requestRender();
|
|
540
|
+
}
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case "tool_start":
|
|
544
|
+
closeSegment();
|
|
545
|
+
if (event.toolName !== "ask_user") {
|
|
546
|
+
activeTools.set(event.toolCallId, event.toolName);
|
|
547
|
+
loader.setMessage(toolActivityLabel(event.toolName));
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
case "tool_end":
|
|
551
|
+
activeTools.delete(event.toolCallId);
|
|
552
|
+
loader.setMessage(
|
|
553
|
+
activeTools.size ? toolActivityLabel(Array.from(activeTools.values()).at(-1)) : "Janet is thinking\u2026"
|
|
554
|
+
);
|
|
555
|
+
if (event.isError) {
|
|
556
|
+
closeSegment();
|
|
557
|
+
addLine(c.warn(` ${toolErrorLabel(event.result)}`));
|
|
558
|
+
}
|
|
559
|
+
break;
|
|
560
|
+
case "tool_suspended": {
|
|
561
|
+
closeSegment();
|
|
562
|
+
activeTools.delete(event.toolCallId);
|
|
563
|
+
setLoader(false);
|
|
564
|
+
const payload = event.suspendPayload;
|
|
565
|
+
const question = payload?.question ?? `Janet needs input for ${event.toolName}.`;
|
|
566
|
+
const options = payload?.options;
|
|
567
|
+
const multi = payload?.selectionMode === "multi_select";
|
|
568
|
+
addLine(c.accentBold(` Janet asks: ${question}`));
|
|
569
|
+
if (options?.length && !multi) {
|
|
570
|
+
const items = options.map((o) => ({
|
|
571
|
+
value: o.label,
|
|
572
|
+
label: o.label,
|
|
573
|
+
...o.description ? { description: o.description } : {}
|
|
574
|
+
}));
|
|
575
|
+
const select = new SelectList(items, Math.min(items.length, 8), editorTheme.selectList);
|
|
576
|
+
select.onSelect = (item) => answerQuestion(item.value, item.label);
|
|
577
|
+
select.onCancel = () => {
|
|
578
|
+
closeActiveSelect(select);
|
|
579
|
+
addLine(c.dim(" Picker closed. Type your answer instead."));
|
|
580
|
+
updateStatus();
|
|
581
|
+
};
|
|
582
|
+
activeSelect = select;
|
|
583
|
+
pendingQuestion = { toolCallId: event.toolCallId, options, multi: false };
|
|
584
|
+
chat.addChild(select);
|
|
585
|
+
addLine(c.dim(" Use \u2191/\u2193 and Enter \xB7 Esc to close."));
|
|
586
|
+
ui.setFocus(select);
|
|
587
|
+
} else {
|
|
588
|
+
pendingQuestion = { toolCallId: event.toolCallId, options, multi };
|
|
589
|
+
if (options?.length) {
|
|
590
|
+
options.forEach(
|
|
591
|
+
(o, i) => addLine(c.accent(` ${i + 1}. `) + o.label + (o.description ? c.dim(` \u2014 ${o.description}`) : ""))
|
|
592
|
+
);
|
|
593
|
+
addLine(c.dim(" Reply with numbers or labels, then press Enter."));
|
|
594
|
+
} else {
|
|
595
|
+
addLine(c.dim(" Type your answer, then press Enter."));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
updateStatus();
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case "tool_approval_required":
|
|
602
|
+
closeSegment();
|
|
603
|
+
activeTools.delete(event.toolCallId);
|
|
604
|
+
pendingApproval = { toolCallId: event.toolCallId, toolName: event.toolName };
|
|
605
|
+
addLine(
|
|
606
|
+
c.warn(` Janet wants to run ${c.bold(event.toolName)}.`) + c.dim(" y = yes \xB7 n = no \xB7 a = always allow this kind")
|
|
607
|
+
);
|
|
608
|
+
updateStatus();
|
|
609
|
+
break;
|
|
610
|
+
case "error": {
|
|
611
|
+
closeSegment();
|
|
612
|
+
const err = event.error;
|
|
613
|
+
addLine(
|
|
614
|
+
c.error(` Error: ${err?.message || "unknown"}${err?.responseBody ? ` \u2014 ${err.responseBody.slice(0, 200)}` : ""}`)
|
|
615
|
+
);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
case "model_changed":
|
|
619
|
+
updateStatus();
|
|
620
|
+
break;
|
|
621
|
+
case "om_status":
|
|
622
|
+
omWindows = event.windows;
|
|
623
|
+
updateStatus();
|
|
624
|
+
break;
|
|
625
|
+
case "om_buffering_start":
|
|
626
|
+
omActivity = event.operationType === "reflection" ? "reflecting" : "observing";
|
|
627
|
+
updateStatus();
|
|
628
|
+
break;
|
|
629
|
+
case "om_observation_start":
|
|
630
|
+
case "om_reflection_start":
|
|
631
|
+
omActivity = event.type === "om_reflection_start" || event.operationType === "reflection" ? "reflecting" : "observing";
|
|
632
|
+
updateStatus();
|
|
633
|
+
break;
|
|
634
|
+
case "om_buffering_end":
|
|
635
|
+
case "om_observation_end":
|
|
636
|
+
case "om_reflection_end":
|
|
637
|
+
omActivity = null;
|
|
638
|
+
updateStatus();
|
|
639
|
+
break;
|
|
640
|
+
case "om_buffering_failed":
|
|
641
|
+
case "om_observation_failed":
|
|
642
|
+
case "om_reflection_failed":
|
|
643
|
+
omActivity = null;
|
|
644
|
+
closeSegment();
|
|
645
|
+
addLine(
|
|
646
|
+
c.warn(
|
|
647
|
+
` Memory ${event.type === "om_buffering_failed" ? `${event.operationType} buffering` : event.type === "om_reflection_failed" ? "reflection" : "observation"} failed: ${event.error}`
|
|
648
|
+
)
|
|
649
|
+
);
|
|
650
|
+
updateStatus();
|
|
651
|
+
break;
|
|
652
|
+
case "om_activation":
|
|
653
|
+
omActivity = null;
|
|
654
|
+
closeSegment();
|
|
655
|
+
addLine(
|
|
656
|
+
c.dim(
|
|
657
|
+
` Memory compacted ${shortTokens(event.tokensActivated)} into ${shortTokens(event.observationTokens)} observation tokens.`
|
|
658
|
+
)
|
|
659
|
+
);
|
|
660
|
+
updateStatus();
|
|
661
|
+
break;
|
|
662
|
+
case "agent_end":
|
|
663
|
+
running = false;
|
|
664
|
+
cancelRequested = false;
|
|
665
|
+
activeTools.clear();
|
|
666
|
+
loader.setMessage("Janet is thinking\u2026");
|
|
667
|
+
if (event.reason !== "suspended") pendingQuestion = null;
|
|
668
|
+
setLoader(false);
|
|
669
|
+
updateStatus();
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
const unsubscribe = session.subscribe(onEvent);
|
|
674
|
+
let removeInputListener = () => {
|
|
675
|
+
};
|
|
676
|
+
let sigintHandler;
|
|
677
|
+
const shutdown = async (code) => {
|
|
678
|
+
removeInputListener();
|
|
679
|
+
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
680
|
+
unsubscribe();
|
|
681
|
+
herdrDetach();
|
|
682
|
+
ui.stop();
|
|
683
|
+
await observability.flush().catch(() => {
|
|
684
|
+
});
|
|
685
|
+
await controller.destroy().catch(() => {
|
|
686
|
+
});
|
|
687
|
+
process.exit(code);
|
|
688
|
+
};
|
|
689
|
+
const notifyInterrupt = (result) => {
|
|
690
|
+
switch (result) {
|
|
691
|
+
case "cancelled":
|
|
692
|
+
addLine(c.dim(" Cancelling the active run\u2026"));
|
|
693
|
+
break;
|
|
694
|
+
case "cleared":
|
|
695
|
+
break;
|
|
696
|
+
case "exit":
|
|
697
|
+
break;
|
|
698
|
+
case "exit-hint":
|
|
699
|
+
addLine(c.dim(" Press Ctrl+C again to quit."));
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
updateStatus();
|
|
703
|
+
};
|
|
704
|
+
const abortActiveRun = () => {
|
|
705
|
+
if (cancelRequested) return;
|
|
706
|
+
cancelRequested = true;
|
|
707
|
+
pendingApproval = null;
|
|
708
|
+
pendingQuestion = null;
|
|
709
|
+
activeTools.clear();
|
|
710
|
+
if (activeSelect) {
|
|
711
|
+
chat.removeChild(activeSelect);
|
|
712
|
+
activeSelect = null;
|
|
713
|
+
}
|
|
714
|
+
ui.setFocus(editor);
|
|
715
|
+
loader.setMessage("Cancelling\u2026");
|
|
716
|
+
session.abort();
|
|
717
|
+
};
|
|
718
|
+
const interrupts = createInterruptController({
|
|
719
|
+
isRunning: () => running,
|
|
720
|
+
hasInput: () => editor.getText().length > 0,
|
|
721
|
+
abortRun: abortActiveRun,
|
|
722
|
+
clearInput: () => {
|
|
723
|
+
editor.setText("");
|
|
724
|
+
ui.requestRender();
|
|
725
|
+
},
|
|
726
|
+
exit: () => {
|
|
727
|
+
void shutdown(0);
|
|
728
|
+
},
|
|
729
|
+
notify: notifyInterrupt
|
|
730
|
+
});
|
|
731
|
+
removeInputListener = ui.addInputListener((data) => {
|
|
732
|
+
if (matchesKey2(data, "ctrl+c")) {
|
|
733
|
+
interrupts.handleCtrlC();
|
|
734
|
+
return { consume: true };
|
|
735
|
+
}
|
|
736
|
+
if (matchesKey2(data, "escape") && activeSelect) {
|
|
737
|
+
return void 0;
|
|
738
|
+
}
|
|
739
|
+
if (matchesKey2(data, "escape") && running) {
|
|
740
|
+
interrupts.handleEscape();
|
|
741
|
+
return { consume: true };
|
|
742
|
+
}
|
|
743
|
+
return void 0;
|
|
744
|
+
});
|
|
745
|
+
sigintHandler = () => {
|
|
746
|
+
interrupts.handleCtrlC();
|
|
747
|
+
};
|
|
748
|
+
process.on("SIGINT", sigintHandler);
|
|
749
|
+
const promptInput = (message, placeholder) => {
|
|
750
|
+
addLine(c.accentBold(` ${message}`));
|
|
751
|
+
if (placeholder) addLine(c.dim(` (${placeholder})`));
|
|
752
|
+
return new Promise((resolve) => {
|
|
753
|
+
pendingInput = resolve;
|
|
754
|
+
updateStatus();
|
|
755
|
+
});
|
|
756
|
+
};
|
|
757
|
+
const closeActiveSelect = (select) => {
|
|
758
|
+
chat.removeChild(select);
|
|
759
|
+
if (activeSelect === select) activeSelect = null;
|
|
760
|
+
ui.setFocus(editor);
|
|
761
|
+
};
|
|
762
|
+
const selectModel = async (modelId) => {
|
|
763
|
+
try {
|
|
764
|
+
await session.model.switch({ modelId });
|
|
765
|
+
completeOnboarding(modelId, (/* @__PURE__ */ new Date()).toISOString());
|
|
766
|
+
rememberModel(modelId);
|
|
767
|
+
addLine(c.accentBold(` \u2713 Using ${modelId}.`) + c.dim(" (saved as your default)"));
|
|
768
|
+
} catch (error) {
|
|
769
|
+
addLine(c.error(` Could not select ${modelId}: ${error.message}`));
|
|
770
|
+
} finally {
|
|
771
|
+
updateStatus();
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
const showProviderModels = (groups, allChoices) => {
|
|
775
|
+
const current = session.model.hasSelection() ? session.model.get() : null;
|
|
776
|
+
const available = groups.flatMap(
|
|
777
|
+
(group) => group.models.map((choice) => ({ choice, group }))
|
|
778
|
+
);
|
|
779
|
+
const currentChoice = available.find(({ choice }) => choice.id === current);
|
|
780
|
+
const ordered = currentChoice ? [currentChoice, ...available.filter(({ choice }) => choice.id !== current)] : available;
|
|
781
|
+
const visible = ordered.slice(0, 29);
|
|
782
|
+
const items = visible.map(({ choice, group }) => ({
|
|
783
|
+
value: choice.id,
|
|
784
|
+
label: groups.length > 1 ? `${group.label}: ${choice.label}${choice.id === current ? " (current)" : ""}` : `${choice.label}${choice.id === current ? " (current)" : ""}`,
|
|
785
|
+
description: choice.id
|
|
786
|
+
}));
|
|
787
|
+
items.push({
|
|
788
|
+
value: "__janet_enter_model_id__",
|
|
789
|
+
label: "Enter another model ID\u2026",
|
|
790
|
+
description: ordered.length > visible.length ? `${ordered.length - visible.length} more catalog models; enter the exact id` : groups.length === 1 ? `Use any ${groups[0].id}/model supported by Mastra` : "Use any provider/model supported by Mastra"
|
|
791
|
+
});
|
|
792
|
+
addLine(
|
|
793
|
+
c.accentBold(
|
|
794
|
+
groups.length === 1 ? ` ${groups[0].label} models` : ` Models from ${groups.length} providers`
|
|
795
|
+
)
|
|
796
|
+
);
|
|
797
|
+
addLine(c.dim(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 Esc to go back:"));
|
|
798
|
+
const select = new SelectList(
|
|
799
|
+
items,
|
|
800
|
+
Math.min(items.length, 10),
|
|
801
|
+
editorTheme.selectList
|
|
802
|
+
);
|
|
803
|
+
select.onSelect = (item) => {
|
|
804
|
+
closeActiveSelect(select);
|
|
805
|
+
if (item.value === "__janet_enter_model_id__") {
|
|
806
|
+
void promptInput(
|
|
807
|
+
groups.length === 1 ? `Model id for ${groups[0].label}:` : "Full model id:",
|
|
808
|
+
groups.length === 1 ? `${groups[0].id}/model-name` : "provider/model-name"
|
|
809
|
+
).then((input) => {
|
|
810
|
+
const modelId = groups.length === 1 && !input.includes("/") ? `${groups[0].id}/${input}` : normalizeModelSelection(
|
|
811
|
+
input,
|
|
812
|
+
available.map(({ choice }) => choice)
|
|
813
|
+
);
|
|
814
|
+
void selectModel(modelId);
|
|
815
|
+
});
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
void selectModel(item.value);
|
|
819
|
+
};
|
|
820
|
+
select.onCancel = () => {
|
|
821
|
+
closeActiveSelect(select);
|
|
822
|
+
showProviderPicker(allChoices);
|
|
823
|
+
};
|
|
824
|
+
activeSelect = select;
|
|
825
|
+
chat.addChild(select);
|
|
826
|
+
ui.setFocus(select);
|
|
827
|
+
updateStatus();
|
|
828
|
+
};
|
|
829
|
+
const showProviderPicker = (choices) => {
|
|
830
|
+
const groups = groupModelsByProvider(choices);
|
|
831
|
+
if (!groups.length) {
|
|
832
|
+
addLine(c.dim(" No providers are configured yet. Set one up, then try again:"));
|
|
833
|
+
addLine(c.dim(" \u2022 Vertex AI: gcloud auth application-default login (+ GOOGLE_VERTEX_PROJECT)"));
|
|
834
|
+
addLine(c.dim(" \u2022 Anthropic: set ANTHROPIC_API_KEY, or /login anthropic"));
|
|
835
|
+
addLine(c.dim(" \u2022 OpenAI: set OPENAI_API_KEY, or /login openai-codex"));
|
|
836
|
+
addLine(c.dim(" \u2022 Bedrock: configure AWS credentials"));
|
|
837
|
+
addLine(c.dim(" \u2022 More: /providers lists native Mastra environment variables"));
|
|
838
|
+
updateStatus();
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
addLine(
|
|
842
|
+
c.dim(
|
|
843
|
+
" \u2191/\u2193 to move \xB7 Space to toggle \xB7 Enter to view models \xB7 Esc to close:"
|
|
844
|
+
)
|
|
845
|
+
);
|
|
846
|
+
const select = new MultiSelectList(
|
|
847
|
+
groups.map((group) => ({
|
|
848
|
+
value: group.id,
|
|
849
|
+
label: group.label,
|
|
850
|
+
description: `${group.models.length} model${group.models.length === 1 ? "" : "s"} \xB7 ${group.via}`
|
|
851
|
+
})),
|
|
852
|
+
Math.min(groups.length, 10),
|
|
853
|
+
editorTheme.selectList,
|
|
854
|
+
groups.map((group) => group.id)
|
|
855
|
+
);
|
|
856
|
+
select.onConfirm = (items) => {
|
|
857
|
+
if (!items.length) {
|
|
858
|
+
addLine(c.warn(" Select at least one provider."));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
closeActiveSelect(select);
|
|
862
|
+
const selectedIds = new Set(items.map((item) => item.value));
|
|
863
|
+
showProviderModels(
|
|
864
|
+
groups.filter((group) => selectedIds.has(group.id)),
|
|
865
|
+
choices
|
|
866
|
+
);
|
|
867
|
+
};
|
|
868
|
+
select.onCancel = () => closeActiveSelect(select);
|
|
869
|
+
activeSelect = select;
|
|
870
|
+
chat.addChild(select);
|
|
871
|
+
ui.setFocus(select);
|
|
872
|
+
updateStatus();
|
|
873
|
+
};
|
|
874
|
+
const showModelPicker = (intro) => {
|
|
875
|
+
if (modelPickerLoading) {
|
|
876
|
+
addLine(c.dim(" The provider catalog is already loading."));
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (intro) addLine(c.accentBold(intro));
|
|
880
|
+
addLine(c.dim(" Loading configured providers\u2026"));
|
|
881
|
+
modelPickerLoading = true;
|
|
882
|
+
updateStatus();
|
|
883
|
+
void discoverAvailableModels(() => controller.listAvailableModels()).then(showProviderPicker).catch((error) => {
|
|
884
|
+
addLine(c.error(` Could not load providers: ${error.message}`));
|
|
885
|
+
showProviderPicker(availableModels());
|
|
886
|
+
}).finally(() => {
|
|
887
|
+
modelPickerLoading = false;
|
|
888
|
+
updateStatus();
|
|
889
|
+
});
|
|
890
|
+
};
|
|
891
|
+
const showProviders = () => {
|
|
892
|
+
if (running) {
|
|
893
|
+
addLine(c.dim(" Cancel the active run before opening provider setup."));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
addLine(c.accentBold(" Model providers"));
|
|
897
|
+
addLine(c.dim(" Loading provider status\u2026"));
|
|
898
|
+
void discoverAvailableModels(() => controller.listAvailableModels()).then((choices) => {
|
|
899
|
+
const groups = groupModelsByProvider(choices);
|
|
900
|
+
const groupsById = new Map(groups.map((group) => [group.id, group]));
|
|
901
|
+
const known = [
|
|
902
|
+
{
|
|
903
|
+
id: "vertex",
|
|
904
|
+
label: "Google Vertex AI",
|
|
905
|
+
setup: "Run gcloud auth application-default login and set GOOGLE_VERTEX_PROJECT."
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
id: "amazon-bedrock",
|
|
909
|
+
label: "Amazon Bedrock",
|
|
910
|
+
setup: "Configure an AWS credential chain and region."
|
|
911
|
+
},
|
|
912
|
+
...NATIVE_PROVIDER_DEFINITIONS.map((provider) => ({
|
|
913
|
+
id: provider.id,
|
|
914
|
+
label: provider.label,
|
|
915
|
+
setup: `Set ${provider.envVars.join(" or ")}.`
|
|
916
|
+
}))
|
|
917
|
+
];
|
|
918
|
+
const knownIds = new Set(known.map((provider) => provider.id));
|
|
919
|
+
const providers = [
|
|
920
|
+
...known,
|
|
921
|
+
...groups.filter((group) => !knownIds.has(group.id)).map((group) => ({
|
|
922
|
+
id: group.id,
|
|
923
|
+
label: group.label,
|
|
924
|
+
setup: "This provider was discovered through Mastra."
|
|
925
|
+
}))
|
|
926
|
+
];
|
|
927
|
+
addLine(
|
|
928
|
+
c.dim(
|
|
929
|
+
" \u2191/\u2193 to move \xB7 Space to select providers \xB7 Enter for details \xB7 Esc to close:"
|
|
930
|
+
)
|
|
931
|
+
);
|
|
932
|
+
const select = new MultiSelectList(
|
|
933
|
+
providers.map((provider) => {
|
|
934
|
+
const group = groupsById.get(provider.id);
|
|
935
|
+
return {
|
|
936
|
+
value: provider.id,
|
|
937
|
+
label: provider.label,
|
|
938
|
+
description: group ? `Ready \xB7 ${group.via}` : provider.setup
|
|
939
|
+
};
|
|
940
|
+
}),
|
|
941
|
+
Math.min(providers.length, 12),
|
|
942
|
+
editorTheme.selectList
|
|
943
|
+
);
|
|
944
|
+
select.onConfirm = (items) => {
|
|
945
|
+
if (!items.length) {
|
|
946
|
+
addLine(c.warn(" Select at least one provider, or press Esc to close."));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
closeActiveSelect(select);
|
|
950
|
+
addLine(c.accentBold(" Provider details"));
|
|
951
|
+
for (const item of items) {
|
|
952
|
+
const provider = providers.find((candidate) => candidate.id === item.value);
|
|
953
|
+
const group = groupsById.get(item.value);
|
|
954
|
+
if (!provider) continue;
|
|
955
|
+
if (group) {
|
|
956
|
+
addLine(
|
|
957
|
+
c.accent(` \u2713 ${provider.label}`) + c.dim(` \u2014 ready via ${group.via}`)
|
|
958
|
+
);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
addLine(c.bold(` ${provider.label}`) + c.dim(` \u2014 ${provider.setup}`));
|
|
962
|
+
if (provider.id === "anthropic") {
|
|
963
|
+
addLine(c.dim(" Or use /login anthropic for a Claude subscription."));
|
|
964
|
+
} else if (provider.id === "openai") {
|
|
965
|
+
addLine(c.dim(" Or use /login openai-codex for a ChatGPT subscription."));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
addLine(c.dim(" Reopen /providers at any time; /models shows providers ready now."));
|
|
969
|
+
updateStatus();
|
|
970
|
+
};
|
|
971
|
+
select.onCancel = () => closeActiveSelect(select);
|
|
972
|
+
activeSelect = select;
|
|
973
|
+
chat.addChild(select);
|
|
974
|
+
ui.setFocus(select);
|
|
975
|
+
updateStatus();
|
|
976
|
+
}).catch((error) => {
|
|
977
|
+
addLine(c.error(` Could not load provider status: ${error.message}`));
|
|
978
|
+
updateStatus();
|
|
979
|
+
});
|
|
980
|
+
};
|
|
981
|
+
const savedObservabilitySummary = () => {
|
|
982
|
+
const saved = loadSettings().observability;
|
|
983
|
+
const resolved = resolveObservabilityConfig(saved, {});
|
|
984
|
+
return formatObservabilityStatus({
|
|
985
|
+
enabled: resolved.enabled,
|
|
986
|
+
capture: resolved.capture,
|
|
987
|
+
sampleRate: resolved.sampleRate,
|
|
988
|
+
destinations: [
|
|
989
|
+
...resolved.local.enabled ? ["local"] : [],
|
|
990
|
+
...resolved.remote ? [
|
|
991
|
+
resolved.remote.kind === "phoenix" ? `phoenix (${safeObservabilityEndpoint(resolved.remote.endpoint)})` : `otlp (${safeObservabilityEndpoint(resolved.remote.endpoint)})`
|
|
992
|
+
] : []
|
|
993
|
+
],
|
|
994
|
+
warnings: resolved.warnings
|
|
995
|
+
});
|
|
996
|
+
};
|
|
997
|
+
const persistObservability = (settings) => {
|
|
998
|
+
rememberObservability(settings);
|
|
999
|
+
addLine(c.accentBold(" \u2713 Observability settings saved."));
|
|
1000
|
+
addLine(c.dim(` Saved: ${savedObservabilitySummary()}`));
|
|
1001
|
+
addLine(c.dim(" Restart Janet to apply the new setting."));
|
|
1002
|
+
updateStatus();
|
|
1003
|
+
};
|
|
1004
|
+
const confirmFullCapture = (base) => {
|
|
1005
|
+
addLine(
|
|
1006
|
+
c.warn(
|
|
1007
|
+
" Full capture includes prompts, responses, and tool payloads. Do not use it with sensitive material."
|
|
1008
|
+
)
|
|
1009
|
+
);
|
|
1010
|
+
addLine(c.dim(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 Esc to go back:"));
|
|
1011
|
+
const select = new SelectList(
|
|
1012
|
+
[
|
|
1013
|
+
{
|
|
1014
|
+
value: "no",
|
|
1015
|
+
label: "Keep metadata-only capture",
|
|
1016
|
+
description: "Recommended. Content stays out of traces."
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
value: "yes",
|
|
1020
|
+
label: "Enable full capture",
|
|
1021
|
+
description: "I understand trace content may contain sensitive data."
|
|
1022
|
+
}
|
|
1023
|
+
],
|
|
1024
|
+
2,
|
|
1025
|
+
editorTheme.selectList
|
|
1026
|
+
);
|
|
1027
|
+
select.onSelect = (item) => {
|
|
1028
|
+
closeActiveSelect(select);
|
|
1029
|
+
persistObservability({
|
|
1030
|
+
...base,
|
|
1031
|
+
capture: item.value === "yes" ? "full" : "metadata"
|
|
1032
|
+
});
|
|
1033
|
+
};
|
|
1034
|
+
select.onCancel = () => {
|
|
1035
|
+
closeActiveSelect(select);
|
|
1036
|
+
chooseCaptureMode(base);
|
|
1037
|
+
};
|
|
1038
|
+
activeSelect = select;
|
|
1039
|
+
chat.addChild(select);
|
|
1040
|
+
ui.setFocus(select);
|
|
1041
|
+
updateStatus();
|
|
1042
|
+
};
|
|
1043
|
+
const chooseCaptureMode = (base) => {
|
|
1044
|
+
addLine(c.accentBold(" What may Janet include in traces?"));
|
|
1045
|
+
addLine(c.dim(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 Esc to go back:"));
|
|
1046
|
+
const select = new SelectList(
|
|
1047
|
+
[
|
|
1048
|
+
{
|
|
1049
|
+
value: "metadata",
|
|
1050
|
+
label: "Metadata only",
|
|
1051
|
+
description: "Timing, tool names, model, tokens, status, and errors."
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
value: "full",
|
|
1055
|
+
label: "Full content",
|
|
1056
|
+
description: "Also includes prompts, responses, and tool payloads."
|
|
1057
|
+
}
|
|
1058
|
+
],
|
|
1059
|
+
2,
|
|
1060
|
+
editorTheme.selectList
|
|
1061
|
+
);
|
|
1062
|
+
select.onSelect = (item) => {
|
|
1063
|
+
closeActiveSelect(select);
|
|
1064
|
+
const capture = item.value;
|
|
1065
|
+
if (capture === "full") {
|
|
1066
|
+
confirmFullCapture(base);
|
|
1067
|
+
} else {
|
|
1068
|
+
persistObservability({ ...base, capture });
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
select.onCancel = () => {
|
|
1072
|
+
closeActiveSelect(select);
|
|
1073
|
+
showObservabilityPicker();
|
|
1074
|
+
};
|
|
1075
|
+
activeSelect = select;
|
|
1076
|
+
chat.addChild(select);
|
|
1077
|
+
ui.setFocus(select);
|
|
1078
|
+
updateStatus();
|
|
1079
|
+
};
|
|
1080
|
+
const showObservabilityPicker = () => {
|
|
1081
|
+
if (running) {
|
|
1082
|
+
addLine(c.dim(" Cancel the active run before changing observability settings."));
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
addLine(c.accentBold(" Configure observability"));
|
|
1086
|
+
addLine(c.dim(` Active now: ${formatObservabilityStatus(observability.status)}`));
|
|
1087
|
+
addLine(c.dim(" Tracing is opt-in and changes apply after restart."));
|
|
1088
|
+
addLine(c.dim(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 Esc to close:"));
|
|
1089
|
+
const select = new SelectList(
|
|
1090
|
+
[
|
|
1091
|
+
{
|
|
1092
|
+
value: "off",
|
|
1093
|
+
label: "Off",
|
|
1094
|
+
description: "No spans, trace database, or network export."
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
value: "local",
|
|
1098
|
+
label: "Local trace history",
|
|
1099
|
+
description: "Store traces in ~/.agent-knowledge/observability.db."
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
value: "phoenix",
|
|
1103
|
+
label: "Phoenix",
|
|
1104
|
+
description: "Send OTLP traces to http://localhost:6006."
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
value: "otlp",
|
|
1108
|
+
label: "Custom OTLP",
|
|
1109
|
+
description: "Send OTLP/HTTP protobuf traces to your endpoint."
|
|
1110
|
+
}
|
|
1111
|
+
],
|
|
1112
|
+
4,
|
|
1113
|
+
editorTheme.selectList
|
|
1114
|
+
);
|
|
1115
|
+
select.onSelect = (item) => {
|
|
1116
|
+
closeActiveSelect(select);
|
|
1117
|
+
if (item.value === "off") {
|
|
1118
|
+
persistObservability({
|
|
1119
|
+
capture: "off",
|
|
1120
|
+
sampleRate: 1,
|
|
1121
|
+
local: { enabled: false, retentionDays: 7 }
|
|
1122
|
+
});
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (item.value === "local") {
|
|
1126
|
+
chooseCaptureMode({
|
|
1127
|
+
sampleRate: 1,
|
|
1128
|
+
local: { enabled: true, retentionDays: 7 }
|
|
1129
|
+
});
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (item.value === "phoenix") {
|
|
1133
|
+
chooseCaptureMode({
|
|
1134
|
+
sampleRate: 1,
|
|
1135
|
+
local: { enabled: false, retentionDays: 7 },
|
|
1136
|
+
remote: {
|
|
1137
|
+
kind: "phoenix",
|
|
1138
|
+
endpoint: "http://localhost:6006",
|
|
1139
|
+
projectName: "janet"
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
void promptInput(
|
|
1145
|
+
"OTLP endpoint (for example, http://localhost:4318):"
|
|
1146
|
+
).then((endpoint) => {
|
|
1147
|
+
try {
|
|
1148
|
+
const parsed = new URL(endpoint);
|
|
1149
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error();
|
|
1150
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
1151
|
+
addLine(
|
|
1152
|
+
c.error(
|
|
1153
|
+
" Do not put credentials or query parameters in the saved endpoint. Use OTEL_EXPORTER_OTLP_HEADERS."
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
} catch {
|
|
1159
|
+
addLine(c.error(" Endpoint must be a valid HTTP or HTTPS URL."));
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
chooseCaptureMode({
|
|
1163
|
+
sampleRate: 1,
|
|
1164
|
+
local: { enabled: false, retentionDays: 7 },
|
|
1165
|
+
remote: {
|
|
1166
|
+
kind: "otlp",
|
|
1167
|
+
endpoint
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
});
|
|
1171
|
+
};
|
|
1172
|
+
select.onCancel = () => closeActiveSelect(select);
|
|
1173
|
+
activeSelect = select;
|
|
1174
|
+
chat.addChild(select);
|
|
1175
|
+
ui.setFocus(select);
|
|
1176
|
+
updateStatus();
|
|
1177
|
+
};
|
|
1178
|
+
const showLocalTraces = async () => {
|
|
1179
|
+
if (running) {
|
|
1180
|
+
addLine(c.dim(" Cancel the active run before browsing traces."));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (!observability.config.local.enabled) {
|
|
1184
|
+
addLine(c.dim(" Local trace history is not active. Use /observability to enable it."));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
await observability.flush().catch(() => {
|
|
1188
|
+
});
|
|
1189
|
+
const store = await observability.storage.getStore("observability");
|
|
1190
|
+
if (!store) {
|
|
1191
|
+
addLine(c.error(" Local trace storage is unavailable."));
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const recent = await store.listTraces({
|
|
1195
|
+
pagination: { page: 0, perPage: 10 },
|
|
1196
|
+
orderBy: { field: "startedAt", direction: "DESC" }
|
|
1197
|
+
});
|
|
1198
|
+
if (!recent.spans.length) {
|
|
1199
|
+
addLine(c.dim(" No local traces yet."));
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
addLine(c.accentBold(" Recent local traces"));
|
|
1203
|
+
addLine(c.dim(" \u2191/\u2193 to move \xB7 Enter to open \xB7 Esc to close:"));
|
|
1204
|
+
const select = new SelectList(
|
|
1205
|
+
recent.spans.map((span) => {
|
|
1206
|
+
const state = traceStatus(span);
|
|
1207
|
+
const marker = state === "error" ? "\u2717" : state === "running" ? "\u2026" : "\u2713";
|
|
1208
|
+
return {
|
|
1209
|
+
value: span.traceId,
|
|
1210
|
+
label: `${marker} ${span.name}`,
|
|
1211
|
+
description: `${span.startedAt.toLocaleString()} \xB7 ${span.traceId}`
|
|
1212
|
+
};
|
|
1213
|
+
}),
|
|
1214
|
+
Math.min(recent.spans.length, 10),
|
|
1215
|
+
editorTheme.selectList
|
|
1216
|
+
);
|
|
1217
|
+
select.onSelect = (item) => {
|
|
1218
|
+
closeActiveSelect(select);
|
|
1219
|
+
void store.getTrace({ traceId: item.value }).then((trace) => {
|
|
1220
|
+
if (!trace) {
|
|
1221
|
+
addLine(c.error(` Trace not found: ${item.value}`));
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
addLine(c.accentBold(` Trace ${trace.traceId}`));
|
|
1225
|
+
for (const line of formatTraceTree(trace.spans)) {
|
|
1226
|
+
addLine(c.dim(` ${line}`));
|
|
1227
|
+
}
|
|
1228
|
+
}).catch((error) => {
|
|
1229
|
+
addLine(c.error(` Could not read trace: ${error.message}`));
|
|
1230
|
+
});
|
|
1231
|
+
};
|
|
1232
|
+
select.onCancel = () => closeActiveSelect(select);
|
|
1233
|
+
activeSelect = select;
|
|
1234
|
+
chat.addChild(select);
|
|
1235
|
+
ui.setFocus(select);
|
|
1236
|
+
updateStatus();
|
|
1237
|
+
};
|
|
1238
|
+
const handleCommand = async (text) => {
|
|
1239
|
+
const [cmd, ...rest] = text.slice(1).split(/\s+/);
|
|
1240
|
+
switch (cmd) {
|
|
1241
|
+
case "quit":
|
|
1242
|
+
case "exit":
|
|
1243
|
+
await shutdown(0);
|
|
1244
|
+
break;
|
|
1245
|
+
case "help":
|
|
1246
|
+
addLine(c.dim(HELP_TEXT));
|
|
1247
|
+
break;
|
|
1248
|
+
case "cancel":
|
|
1249
|
+
if (interrupts.handleEscape() === "ignored") {
|
|
1250
|
+
addLine(c.dim(" No active run to cancel."));
|
|
1251
|
+
}
|
|
1252
|
+
break;
|
|
1253
|
+
case "clear":
|
|
1254
|
+
if (running || compacting) {
|
|
1255
|
+
addLine(c.dim(" Wait for the active work to finish before clearing the conversation."));
|
|
1256
|
+
break;
|
|
1257
|
+
}
|
|
1258
|
+
try {
|
|
1259
|
+
await clearConversation(session.thread);
|
|
1260
|
+
active = null;
|
|
1261
|
+
activeTools.clear();
|
|
1262
|
+
omWindows = null;
|
|
1263
|
+
omActivity = null;
|
|
1264
|
+
chat.clear();
|
|
1265
|
+
addLine(c.accentBold(GREETING));
|
|
1266
|
+
addLine(c.accentBold(" \u2713 Conversation cleared."));
|
|
1267
|
+
addLine(c.dim(` Knowledge bundle: ${paths.bundlePath}`));
|
|
1268
|
+
addLine(c.dim(" The previous conversation is still saved as a separate thread."));
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
addLine(c.error(` Could not clear the conversation: ${error.message}`));
|
|
1271
|
+
} finally {
|
|
1272
|
+
updateStatus();
|
|
1273
|
+
}
|
|
1274
|
+
break;
|
|
1275
|
+
case "compact": {
|
|
1276
|
+
if (running || compacting) {
|
|
1277
|
+
addLine(c.dim(" Wait for the active work to finish before compacting memory."));
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
if (!session.model.hasSelection()) {
|
|
1281
|
+
addLine(c.dim(" Pick a model before compacting memory."));
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
const threadId = session.thread.getId();
|
|
1285
|
+
if (!threadId) {
|
|
1286
|
+
addLine(c.dim(" There is no active conversation to compact."));
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
compacting = true;
|
|
1290
|
+
loader.setMessage("Janet is compacting memory\u2026");
|
|
1291
|
+
setLoader(true);
|
|
1292
|
+
updateStatus();
|
|
1293
|
+
try {
|
|
1294
|
+
const requestContext = await session.machinery.buildRequestContext();
|
|
1295
|
+
const agent = controller.getCurrentAgent(session);
|
|
1296
|
+
const memory = await agent.getMemory({ requestContext });
|
|
1297
|
+
if (!memory) throw new Error("Janet memory is unavailable.");
|
|
1298
|
+
const result = await compactConversation({
|
|
1299
|
+
memory,
|
|
1300
|
+
agent,
|
|
1301
|
+
threadId,
|
|
1302
|
+
resourceId: session.identity.getResourceId(),
|
|
1303
|
+
requestContext
|
|
1304
|
+
});
|
|
1305
|
+
if (!result.buffered && !result.activated && !result.reflected) {
|
|
1306
|
+
addLine(c.dim(" Memory is already compact."));
|
|
1307
|
+
} else {
|
|
1308
|
+
const reflected = result.reflected ? " and reflected" : "";
|
|
1309
|
+
addLine(
|
|
1310
|
+
c.accentBold(
|
|
1311
|
+
` \u2713 Compacted ~${result.pendingTokensBefore.toLocaleString()} message tokens into ~${result.observationTokens.toLocaleString()} memory tokens${reflected}.`
|
|
1312
|
+
)
|
|
1313
|
+
);
|
|
1314
|
+
addLine(c.dim(" Raw messages remain available to Janet through memory recall."));
|
|
1315
|
+
}
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
addLine(c.error(` Could not compact memory: ${error.message}`));
|
|
1318
|
+
} finally {
|
|
1319
|
+
compacting = false;
|
|
1320
|
+
loader.setMessage("Janet is thinking\u2026");
|
|
1321
|
+
setLoader(false);
|
|
1322
|
+
updateStatus();
|
|
1323
|
+
}
|
|
1324
|
+
break;
|
|
1325
|
+
}
|
|
1326
|
+
case "observability": {
|
|
1327
|
+
const action = rest[0]?.trim().toLowerCase();
|
|
1328
|
+
if (action === "status") {
|
|
1329
|
+
addLine(c.dim(` Active: ${formatObservabilityStatus(observability.status)}`));
|
|
1330
|
+
addLine(c.dim(` Saved: ${savedObservabilitySummary()}`));
|
|
1331
|
+
} else if (action === "off") {
|
|
1332
|
+
persistObservability({
|
|
1333
|
+
capture: "off",
|
|
1334
|
+
sampleRate: 1,
|
|
1335
|
+
local: { enabled: false, retentionDays: 7 }
|
|
1336
|
+
});
|
|
1337
|
+
} else if (!action) {
|
|
1338
|
+
showObservabilityPicker();
|
|
1339
|
+
} else {
|
|
1340
|
+
addLine(c.dim("Usage: /observability [status | off]"));
|
|
1341
|
+
}
|
|
1342
|
+
break;
|
|
1343
|
+
}
|
|
1344
|
+
case "traces":
|
|
1345
|
+
await showLocalTraces();
|
|
1346
|
+
break;
|
|
1347
|
+
case "login": {
|
|
1348
|
+
const providerId = (rest[0] || "anthropic").trim();
|
|
1349
|
+
if (!OAUTH_PROVIDERS.includes(providerId)) {
|
|
1350
|
+
addLine(c.dim(`Usage: /login <${OAUTH_PROVIDERS.join(" | ")}>`));
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
const authMode = rest[1]?.trim();
|
|
1354
|
+
if (authMode && (providerId !== "openai-codex" || !["browser", "device"].includes(authMode))) {
|
|
1355
|
+
addLine(c.dim("Usage: /login openai-codex [browser | device]"));
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
addLine(c.dim(`Starting ${providerId} login\u2026`));
|
|
1359
|
+
try {
|
|
1360
|
+
await getAuthStorage().login(providerId, {
|
|
1361
|
+
onAuth: (info) => {
|
|
1362
|
+
addLine(c.accent(" Open this URL in your browser to authorize:"));
|
|
1363
|
+
addLine(" " + info.url);
|
|
1364
|
+
if (info.instructions) addLine(c.dim(" " + info.instructions));
|
|
1365
|
+
},
|
|
1366
|
+
onProgress: (m) => addLine(c.dim(" " + m)),
|
|
1367
|
+
onManualCodeInput: () => promptInput("Paste the code shown after you authorize:"),
|
|
1368
|
+
onPrompt: (p) => promptInput(p.message, p.placeholder),
|
|
1369
|
+
...authMode ? { authMode } : {}
|
|
1370
|
+
});
|
|
1371
|
+
addLine(c.accentBold(` \u2713 Logged in to ${providerId}.`));
|
|
1372
|
+
updateStatus();
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
addLine(c.error(` Login failed: ${err.message}`));
|
|
1375
|
+
} finally {
|
|
1376
|
+
pendingInput = null;
|
|
1377
|
+
updateStatus();
|
|
1378
|
+
}
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1381
|
+
case "logout": {
|
|
1382
|
+
const providerId = rest[0]?.trim();
|
|
1383
|
+
if (!providerId) {
|
|
1384
|
+
addLine(c.dim(`Usage: /logout <${OAUTH_PROVIDERS.join(" | ")}>`));
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
const storage = getAuthStorage();
|
|
1388
|
+
storage.logout(providerId);
|
|
1389
|
+
storage.remove(`apikey:${providerId}`);
|
|
1390
|
+
addLine(c.dim(`Logged out of ${providerId}.`));
|
|
1391
|
+
break;
|
|
1392
|
+
}
|
|
1393
|
+
case "auth": {
|
|
1394
|
+
const storage = getAuthStorage();
|
|
1395
|
+
storage.reload();
|
|
1396
|
+
const providers = storage.list();
|
|
1397
|
+
if (!providers.length) {
|
|
1398
|
+
addLine(c.dim("No stored credentials. Use /login <provider>, or set an API key env var"));
|
|
1399
|
+
addLine(c.dim("(ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_VERTEX_PROJECT, AWS_*)."));
|
|
1400
|
+
} else {
|
|
1401
|
+
for (const p of providers) {
|
|
1402
|
+
const cred = storage.get(p);
|
|
1403
|
+
addLine(c.dim(` ${p}: `) + (cred?.type === "oauth" ? c.accent("OAuth (subscription)") : "API key"));
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
case "model": {
|
|
1409
|
+
const inputId = rest.join(" ").trim();
|
|
1410
|
+
if (!inputId) {
|
|
1411
|
+
showModelPicker();
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
const id = normalizeModelSelection(inputId, availableModels());
|
|
1415
|
+
await selectModel(id);
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
case "models":
|
|
1419
|
+
showModelPicker();
|
|
1420
|
+
break;
|
|
1421
|
+
case "providers":
|
|
1422
|
+
showProviders();
|
|
1423
|
+
break;
|
|
1424
|
+
default:
|
|
1425
|
+
addLine(c.dim(`Unknown command /${cmd}. Try /help.`));
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
editor.onSubmit = (raw) => {
|
|
1429
|
+
const text = raw.trim();
|
|
1430
|
+
editor.setText("");
|
|
1431
|
+
if (!text) return;
|
|
1432
|
+
if (!pendingInput && !pendingApproval && !pendingQuestion) {
|
|
1433
|
+
editor.addToHistory(text);
|
|
1434
|
+
}
|
|
1435
|
+
if (pendingInput) {
|
|
1436
|
+
const resolve = pendingInput;
|
|
1437
|
+
pendingInput = null;
|
|
1438
|
+
addLine(c.dim(" \u276F (value entered)"));
|
|
1439
|
+
updateStatus();
|
|
1440
|
+
resolve(text);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
if (pendingQuestion && !activeSelect) {
|
|
1444
|
+
const resumeData = resolveAnswer(pendingQuestion, text);
|
|
1445
|
+
if (resumeData === void 0) {
|
|
1446
|
+
addLine(c.dim(" Didn't match an option \u2014 reply with a number or an exact label."));
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
answerQuestion(resumeData, Array.isArray(resumeData) ? resumeData.join(", ") : resumeData);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (pendingApproval) {
|
|
1453
|
+
const approve = /^y(es)?$/i.test(text);
|
|
1454
|
+
const decline = /^n(o)?$/i.test(text);
|
|
1455
|
+
const always = /^a(lways)?$/i.test(text);
|
|
1456
|
+
if (approve || decline || always) {
|
|
1457
|
+
const { toolCallId } = pendingApproval;
|
|
1458
|
+
pendingApproval = null;
|
|
1459
|
+
addLine(c.dim(always ? " \u2713 always allowed" : approve ? " \u2713 approved" : " \u2717 declined"));
|
|
1460
|
+
updateStatus();
|
|
1461
|
+
void session.respondToToolApproval({
|
|
1462
|
+
decision: always ? "always_allow_category" : approve ? "approve" : "decline",
|
|
1463
|
+
toolCallId
|
|
1464
|
+
});
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
addLine(c.dim(" Answer y (yes), n (no), or a (always allow) first."));
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (compacting) {
|
|
1471
|
+
addLine(c.dim(" Janet is still compacting memory; try again in a moment."));
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
if (text.startsWith("/")) {
|
|
1475
|
+
void handleCommand(text);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
addLine(c.user(`\u276F ${text}`));
|
|
1479
|
+
if (!session.model.hasSelection()) {
|
|
1480
|
+
showModelPicker(" Pick a model first:");
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
void session.sendMessage({
|
|
1484
|
+
content: text,
|
|
1485
|
+
tracingOptions: observability.tracingOptionsForTurn({
|
|
1486
|
+
interactive: true,
|
|
1487
|
+
operation: "chat",
|
|
1488
|
+
resourceId: paths.resourceId,
|
|
1489
|
+
threadId: session.thread.getId() ?? void 0
|
|
1490
|
+
})
|
|
1491
|
+
}).catch((err) => {
|
|
1492
|
+
running = false;
|
|
1493
|
+
setLoader(false);
|
|
1494
|
+
addLine(c.error(` \u2717 ${err.message}`));
|
|
1495
|
+
updateStatus();
|
|
1496
|
+
});
|
|
1497
|
+
};
|
|
1498
|
+
addLine(c.accentBold(GREETING));
|
|
1499
|
+
addLine(
|
|
1500
|
+
c.dim(
|
|
1501
|
+
`Knowledge bundle: ${paths.bundlePath}
|
|
1502
|
+
Ask me anything in the bundle, or say what to ingest. /help for commands.`
|
|
1503
|
+
)
|
|
1504
|
+
);
|
|
1505
|
+
for (const warning of observability.status.warnings) {
|
|
1506
|
+
addLine(c.warn(`Observability: ${warning}`));
|
|
1507
|
+
}
|
|
1508
|
+
updateStatus();
|
|
1509
|
+
ui.start();
|
|
1510
|
+
ui.setFocus(editor);
|
|
1511
|
+
ui.requestRender();
|
|
1512
|
+
if (!session.model.hasSelection()) showModelPicker(" Let's pick a model to get you started.");
|
|
1513
|
+
return await new Promise(() => {
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
export {
|
|
1517
|
+
runTui
|
|
1518
|
+
};
|
|
1519
|
+
//# sourceMappingURL=tui-74FJW5Y4.js.map
|