@falkenslab/agent-kit 0.17.0 → 0.19.0
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/assets/extension-launcher.mjs +24 -0
- package/dist/chat/chatController.d.ts +264 -0
- package/dist/chat/chatController.js +510 -0
- package/dist/chat/extensions.d.ts +14 -0
- package/dist/chat/extensions.js +41 -0
- package/dist/chat/history.d.ts +17 -0
- package/dist/chat/history.js +39 -0
- package/dist/{tui → chat}/runs.d.ts +8 -1
- package/dist/{tui → chat}/runs.js +1 -1
- package/dist/core/agentSpec.d.ts +41 -27
- package/dist/core/extensions.d.ts +119 -0
- package/dist/core/extensions.js +97 -0
- package/dist/core/externalExtensions.d.ts +131 -0
- package/dist/core/externalExtensions.js +340 -0
- package/dist/core/hooks/fileScopeGate.d.ts +17 -2
- package/dist/core/hooks/fileScopeGate.js +45 -10
- package/dist/core/hooks/planGate.d.ts +2 -0
- package/dist/core/hooks/planGate.js +1 -10
- package/dist/core/hooks/stepGate.d.ts +1 -1
- package/dist/core/hooks/stepGate.js +3 -3
- package/dist/core/language.js +1 -1
- package/dist/core/messages/de.js +12 -33
- package/dist/core/messages/en.d.ts +13 -16
- package/dist/core/messages/en.js +12 -33
- package/dist/core/messages/es.js +12 -33
- package/dist/core/messages/fr.js +12 -33
- package/dist/core/messages/index.d.ts +6 -0
- package/dist/core/messages/index.js +12 -1
- package/dist/core/packaged.d.ts +10 -0
- package/dist/core/packaged.js +38 -0
- package/dist/core/pluginAgents.d.ts +20 -0
- package/dist/core/pluginAgents.js +76 -0
- package/dist/core/runner.js +16 -9
- package/dist/core/session.d.ts +42 -5
- package/dist/core/session.js +203 -99
- package/dist/core/sessionFacts.d.ts +80 -0
- package/dist/core/sessionFacts.js +48 -0
- package/dist/core/toolLabels.d.ts +24 -6
- package/dist/core/toolLabels.js +24 -39
- package/dist/core/tools/humanApproval.js +3 -3
- package/dist/extensions/awareness/index.d.ts +13 -0
- package/dist/extensions/awareness/index.js +39 -0
- package/dist/extensions/awareness/labels.d.ts +3 -0
- package/dist/extensions/awareness/labels.js +12 -0
- package/dist/extensions/awareness/tools.d.ts +5 -0
- package/dist/extensions/awareness/tools.js +54 -0
- package/dist/{core → extensions/knowledge}/fileKnowledgeStore.d.ts +1 -3
- package/dist/{core → extensions/knowledge}/fileKnowledgeStore.js +23 -29
- package/dist/extensions/knowledge/index.d.ts +8 -0
- package/dist/extensions/knowledge/index.js +35 -0
- package/dist/{core → extensions/knowledge}/knowledgeStore.d.ts +5 -9
- package/dist/{core → extensions/knowledge}/knowledgeStore.js +16 -2
- package/dist/extensions/knowledge/labels.d.ts +3 -0
- package/dist/extensions/knowledge/labels.js +119 -0
- package/dist/extensions/knowledge/prompt.d.ts +28 -0
- package/dist/extensions/knowledge/prompt.js +59 -0
- package/dist/{core/tools/knowledgeTools.d.ts → extensions/knowledge/tools.d.ts} +7 -7
- package/dist/{core/tools/knowledgeTools.js → extensions/knowledge/tools.js} +20 -55
- package/dist/extensions/memory/index.d.ts +12 -0
- package/dist/extensions/memory/index.js +56 -0
- package/dist/extensions/memory/labels.d.ts +3 -0
- package/dist/extensions/memory/labels.js +58 -0
- package/dist/extensions/memory/memoryStore.d.ts +53 -0
- package/dist/extensions/memory/memoryStore.js +88 -0
- package/dist/extensions/memory/tools.d.ts +9 -0
- package/dist/extensions/memory/tools.js +82 -0
- package/dist/extensions/sources/index.d.ts +9 -0
- package/dist/extensions/sources/index.js +40 -0
- package/dist/extensions/sources/labels.d.ts +3 -0
- package/dist/extensions/sources/labels.js +82 -0
- package/dist/{core → extensions/sources}/sources.d.ts +24 -31
- package/dist/{core → extensions/sources}/sources.js +45 -108
- package/dist/{core/tools/saveToSources.d.ts → extensions/sources/tools.d.ts} +7 -3
- package/dist/{core/tools/saveToSources.js → extensions/sources/tools.js} +22 -17
- package/dist/index.d.ts +14 -9
- package/dist/index.js +11 -7
- package/dist/tui/chatTui.d.ts +11 -20
- package/dist/tui/chatTui.js +75 -230
- package/dist/tui/extensionCommand.d.ts +21 -0
- package/dist/tui/extensionCommand.js +133 -0
- package/dist/tui/ink/runChatInk.d.ts +4 -3
- package/dist/tui/ink/runChatInk.js +101 -217
- package/extensions/awareness/.claude-plugin/plugin.json +10 -0
- package/{assets/agent-help → extensions/awareness/skills/help}/SKILL.md +4 -3
- package/{assets/knowledge-plugin → extensions/knowledge}/.claude-plugin/plugin.json +7 -1
- package/extensions/knowledge/commands/ingest.md +7 -0
- package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-ingest/SKILL.md +8 -3
- package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-lint/SKILL.md +12 -5
- package/extensions/memory/.claude-plugin/plugin.json +10 -0
- package/extensions/memory/commands/forget.md +8 -0
- package/extensions/memory/commands/list.md +5 -0
- package/extensions/sources/.claude-plugin/plugin.json +10 -0
- package/package.json +3 -2
- package/assets/knowledge-plugin/commands/ingest.md +0 -7
- package/assets/knowledge-plugin/commands/query.md +0 -5
- package/assets/knowledge-plugin-files/.claude-plugin/plugin.json +0 -4
- package/assets/knowledge-plugin-files/commands/ingest.md +0 -7
- package/assets/knowledge-plugin-files/commands/lint.md +0 -5
- package/assets/knowledge-plugin-files/skills/knowledge-ingest/SKILL.md +0 -46
- package/assets/knowledge-plugin-files/skills/knowledge-lint/SKILL.md +0 -26
- package/assets/knowledge-plugin-files/skills/knowledge-pages/SKILL.md +0 -137
- package/assets/knowledge-plugin-files/skills/knowledge-query/SKILL.md +0 -14
- package/dist/core/agentHelp.d.ts +0 -30
- package/dist/core/agentHelp.js +0 -55
- package/dist/core/knowledge.d.ts +0 -25
- package/dist/core/knowledge.js +0 -84
- /package/dist/{core → extensions/sources}/extractText.d.ts +0 -0
- /package/dist/{core → extensions/sources}/extractText.js +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/commands/lint.md +0 -0
- /package/{assets/knowledge-plugin-files → extensions/knowledge}/commands/query.md +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-query/SKILL.md +0 -0
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createWriteStream } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import { stdin, stdout } from "node:process";
|
|
5
3
|
import { useSyncExternalStore } from "react";
|
|
6
4
|
import { Box, render, Text, useInput } from "ink";
|
|
7
5
|
import { Select } from "@inkjs/ui";
|
|
8
6
|
import { getInteractionPort, setInteractionPort } from "../../core/interaction.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { listRuns, readConversation } from "../../core/runs.js";
|
|
12
|
-
import { capHistory, loadHistory, runChatTui, saveHistory, slashCommandToken } from "../chatTui.js";
|
|
7
|
+
import { runChatTui } from "../chatTui.js";
|
|
8
|
+
import { createChatController } from "../../chat/chatController.js";
|
|
13
9
|
import * as ui from "../ui.js";
|
|
14
10
|
import { t } from "../../core/messages/index.js";
|
|
15
11
|
import { applyLanguage } from "../language.js";
|
|
16
12
|
import { applyTheme, inkColor } from "../theme.js";
|
|
17
13
|
import { KitTheme } from "./inkTheme.js";
|
|
18
|
-
import {
|
|
14
|
+
import { runDate } from "../../chat/runs.js";
|
|
19
15
|
import { createInkInteraction } from "./inkInteraction.js";
|
|
20
16
|
import { PromptInput } from "./PromptInput.js";
|
|
21
17
|
import { createSessionModel, liveWidth } from "./sessionModel.js";
|
|
22
18
|
import { SessionView } from "./SessionView.js";
|
|
23
|
-
import { fitWidth
|
|
19
|
+
import { fitWidth } from "./lineBuffer.js";
|
|
24
20
|
import { clipboardSequence, enterFullscreen } from "./fullscreen.js";
|
|
25
21
|
import { createCursorController, CursorContext } from "./terminalCursor.js";
|
|
26
22
|
import { createTerminalStatus, focusFromReport } from "./terminalStatus.js";
|
|
27
23
|
import { listProjectFiles } from "./fileMentions.js";
|
|
28
24
|
import { headerLines } from "./header.js";
|
|
29
25
|
const DEFAULT_PROMPT_LABEL = "\n> ";
|
|
30
|
-
/** The chat's own commands, never sent to the model (the
|
|
26
|
+
/** The chat's own commands, never sent to the model (the awareness extension's help skill lists them; a test keeps both in step). */
|
|
31
27
|
export const DEFAULT_EXIT_COMMANDS = ["/exit", "/quit"];
|
|
32
|
-
export const LOCAL_COMMANDS = { copy: "/copy", resume: "/resume", plan: "/plan" };
|
|
28
|
+
export const LOCAL_COMMANDS = { copy: "/copy", resume: "/resume", plan: "/plan", extensions: "/extensions" };
|
|
33
29
|
const DEFAULT_HISTORY_LIMIT = 100;
|
|
34
30
|
// The prompt's frame: a border and one column of padding on each side.
|
|
35
31
|
const PROMPT_FRAME_COLUMNS = 4;
|
|
@@ -120,11 +116,12 @@ function PickerPanel({ picker }) {
|
|
|
120
116
|
const width = liveWidth(columns) - PROMPT_FRAME_COLUMNS;
|
|
121
117
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: inkColor("border"), paddingX: 1, children: [_jsx(Text, { bold: true, children: picker.title }), _jsx(Select, { options: picker.options.map((option) => ({ ...option, label: fitWidth(option.label, Math.max(10, width - 2)) })), visibleOptionCount: Math.min(8, picker.options.length), onChange: (value) => picker.resolve(value) }), _jsx(Text, { dimColor: true, children: t().resumeHint })] }));
|
|
122
118
|
}
|
|
123
|
-
function ChatApp({ model, interaction, input, promptLabel, renderApproval, mode, fullscreen, header, onFocusChange,
|
|
119
|
+
function ChatApp({ model, interaction, input, promptLabel, renderApproval, mode, fullscreen, header, onFocusChange, controller }) {
|
|
124
120
|
const chat = useSyncExternalStore(input.subscribe, input.getSnapshot);
|
|
125
121
|
const session = useSyncExternalStore(model.subscribe, model.getSnapshot);
|
|
126
122
|
const checkpoint = useSyncExternalStore(interaction.subscribe, interaction.getSnapshot);
|
|
127
|
-
const
|
|
123
|
+
const control = useSyncExternalStore(controller.subscribe, controller.getState);
|
|
124
|
+
const onInterrupt = () => controller.interrupt();
|
|
128
125
|
// Ctrl+C stops what is running (a checkpoint, then the turn) and leaves only when idle;
|
|
129
126
|
// Esc interrupts the turn but never answers a checkpoint or leaves.
|
|
130
127
|
useInput((text, key) => {
|
|
@@ -139,13 +136,7 @@ function ChatApp({ model, interaction, input, promptLabel, renderApproval, mode,
|
|
|
139
136
|
}
|
|
140
137
|
if (key.shift && key.tab) {
|
|
141
138
|
// Shift+Tab: the next mode this session allows.
|
|
142
|
-
|
|
143
|
-
model.note(ui.dim(t().modeLocked(modeControl?.mode ?? mode)), "notice");
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const next = modeControl.switchable[(modeControl.switchable.indexOf(modeControl.mode) + 1) % modeControl.switchable.length];
|
|
147
|
-
modeControl.set(next);
|
|
148
|
-
input.setMode(next);
|
|
139
|
+
controller.cycleMode();
|
|
149
140
|
return;
|
|
150
141
|
}
|
|
151
142
|
if (key.ctrl && text === "c" && chat.picker) {
|
|
@@ -164,7 +155,7 @@ function ChatApp({ model, interaction, input, promptLabel, renderApproval, mode,
|
|
|
164
155
|
onInterrupt();
|
|
165
156
|
}
|
|
166
157
|
});
|
|
167
|
-
return (_jsx(SessionView, { model: model, interaction: interaction, renderApproval: renderApproval, mode:
|
|
158
|
+
return (_jsx(SessionView, { model: model, interaction: interaction, renderApproval: renderApproval, mode: control.mode ?? mode, modeSwitchable: control.switchableModes.length > 1, closed: chat.closed, fullscreen: fullscreen, header: header, onCopy: fullscreen ? (text) => void stdout.write(clipboardSequence(text)) : undefined, children: _jsxs(Box, { flexDirection: "column", children: [chat.queued.map((line, index) => (_jsx(Text, { wrap: "truncate-end", children: ui.dim(` ${t().queued(line)}`) }, index))), chat.picker ? (_jsx(PickerPanel, { picker: chat.picker })) : (_jsx(Box, { borderStyle: "round", borderColor: inkColor("border"), paddingX: 1, children: _jsx(PromptInput, { label: promptLabel.replace(/^\n+/, ""), history: chat.history, commands: chat.commands, inset: PROMPT_FRAME_COLUMNS, suggestion: chat.suggestion, onSubmit: (line) => input.submit(line), onRecallQueued: () => input.unqueueLast(), files: chat.files, onExit: () => input.submit(null) }) }))] }) }));
|
|
168
159
|
}
|
|
169
160
|
/**
|
|
170
161
|
* The Ink counterpart of `runChatTui()`, with the same options, session log and history
|
|
@@ -188,166 +179,107 @@ export async function runChatInk(options, tuiOptions = {}) {
|
|
|
188
179
|
const languageWarnings = [];
|
|
189
180
|
applyLanguage(tuiOptions.language, (line) => languageWarnings.push(line));
|
|
190
181
|
applyTheme(tuiOptions.theme);
|
|
191
|
-
const opener = typeof options === "function" ? options : null;
|
|
192
|
-
const runsDir = opener ? tuiOptions.runsDir : undefined;
|
|
193
|
-
if (opener && !runsDir)
|
|
194
|
-
throw new Error("runChatInk(): a session opener needs `runsDir`.");
|
|
195
|
-
const exitCommands = new Set((tuiOptions.exitCommands ?? DEFAULT_EXIT_COMMANDS).map((c) => c.toLowerCase()));
|
|
196
182
|
const promptLabel = tuiOptions.promptLabel ?? DEFAULT_PROMPT_LABEL;
|
|
197
183
|
const userLine = (text) => `${promptLabel.replace(/^\n+/, "")}${text}`;
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const model = createSessionModel({
|
|
184
|
+
const input = createChatInput();
|
|
185
|
+
// Set once the terminal integration is up: /copy needs it.
|
|
186
|
+
let status = null;
|
|
187
|
+
const controller = await createChatController(options, {
|
|
188
|
+
exitCommands: tuiOptions.exitCommands ?? DEFAULT_EXIT_COMMANDS,
|
|
189
|
+
initialPrompt: tuiOptions.initialPrompt,
|
|
190
|
+
sessionLogPath: tuiOptions.sessionLogPath,
|
|
191
|
+
promptLabel,
|
|
192
|
+
agentLabel: tuiOptions.agentLabel,
|
|
193
|
+
historyPath: tuiOptions.historyPath,
|
|
194
|
+
historyLimit: tuiOptions.historyLimit ?? DEFAULT_HISTORY_LIMIT,
|
|
195
|
+
runsDir: tuiOptions.runsDir,
|
|
196
|
+
modeControl: tuiOptions.modeControl,
|
|
197
|
+
toolLabels: tuiOptions.toolLabels,
|
|
213
198
|
formatAction: tuiOptions.formatAction,
|
|
214
199
|
toolPhrase: tuiOptions.toolPhrase,
|
|
200
|
+
promptSuggestions: tuiOptions.promptSuggestions ?? true,
|
|
201
|
+
// The /resume list in place of the prompt.
|
|
202
|
+
pick: (title, choices) => input.pick(title, choices),
|
|
203
|
+
// A local command, never sent to the model: the last reply to the clipboard (OSC 52).
|
|
204
|
+
...(tuiOptions.terminalIntegration === false
|
|
205
|
+
? {}
|
|
206
|
+
: {
|
|
207
|
+
commands: {
|
|
208
|
+
copy: () => {
|
|
209
|
+
const reply = controller.lastReply();
|
|
210
|
+
if (reply)
|
|
211
|
+
stdout.write(clipboardSequence(reply));
|
|
212
|
+
model.writeLine(ui.dim(reply ? t().copiedReply(reply.length) : t().nothingToCopy));
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
// The screen, drawn from the controller's events; the session log is the controller's.
|
|
218
|
+
const model = createSessionModel({
|
|
219
|
+
formatAction: controller.formatAction,
|
|
220
|
+
toolPhrase: controller.toolPhrase,
|
|
215
221
|
toolDetail: tuiOptions.toolDetail,
|
|
216
222
|
formatResult: tuiOptions.formatResult,
|
|
217
223
|
agentLabel: tuiOptions.agentLabel,
|
|
218
|
-
onWrite: mirror,
|
|
219
224
|
width: () => liveWidth(stdout.columns),
|
|
220
225
|
});
|
|
221
226
|
const interaction = createInkInteraction((text) => model.note(text));
|
|
222
|
-
const input = createChatInput();
|
|
223
227
|
if (tuiOptions.firstPromptSuggestion)
|
|
224
228
|
input.setSuggestion(tuiOptions.firstPromptSuggestion);
|
|
225
|
-
void listProjectFiles(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const local = [
|
|
266
|
-
...(tuiOptions.terminalIntegration === false ? [] : ["copy"]),
|
|
267
|
-
...(runsDir ? ["resume"] : []),
|
|
268
|
-
...(modeControl()?.switchable.includes("plan") ? ["plan"] : []),
|
|
269
|
-
];
|
|
270
|
-
input.setCommands([...new Set([...(names ?? []), ...exits, ...local])].sort());
|
|
271
|
-
});
|
|
272
|
-
void (async () => {
|
|
273
|
-
try {
|
|
274
|
-
while (true) {
|
|
275
|
-
const { value, done } = await events.next();
|
|
276
|
-
if (own !== generation || done)
|
|
277
|
-
break;
|
|
278
|
-
if (value.type === "prompt-suggestion") {
|
|
279
|
-
input.setSuggestion(value.suggestion);
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
model.render(value);
|
|
283
|
-
if (value.type === "turn-end")
|
|
284
|
-
finishTurn();
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
catch (error) {
|
|
288
|
-
if (own === generation)
|
|
289
|
-
readError = error;
|
|
290
|
-
}
|
|
291
|
-
if (own === generation)
|
|
292
|
-
finishTurn();
|
|
293
|
-
})();
|
|
294
|
-
}
|
|
295
|
-
connect();
|
|
296
|
-
async function runTurn(line) {
|
|
297
|
-
input.setSuggestion(null);
|
|
298
|
-
status?.turnStarted();
|
|
299
|
-
turnInterrupted = false;
|
|
300
|
-
model.separate();
|
|
301
|
-
model.startTurn();
|
|
302
|
-
const finished = new Promise((resolve) => (turnDone = resolve));
|
|
303
|
-
queue.push(line);
|
|
304
|
-
await finished;
|
|
305
|
-
model.endTurn();
|
|
306
|
-
status?.turnEnded();
|
|
307
|
-
void run.contextUsage().then((usage) => usage && model.setContextPercent(usage.percentage));
|
|
308
|
-
if (readError)
|
|
309
|
-
throw readError;
|
|
310
|
-
}
|
|
311
|
-
/** Draws the run's earlier conversation (when it has one) and says it was resumed. */
|
|
312
|
-
async function showConversation(folder, fromResume) {
|
|
313
|
-
if (!folder.sessionId)
|
|
314
|
-
return;
|
|
315
|
-
const messages = await readConversation(folder.dir);
|
|
316
|
-
const [summary] = (await listRuns(path.dirname(folder.dir))).filter((r) => path.resolve(r.dir) === path.resolve(folder.dir));
|
|
317
|
-
// In full screen the history is redrawn from the start; inline, the terminal's
|
|
318
|
-
// scrollback can't be taken back, so the conversation follows what's already there.
|
|
319
|
-
if (fromResume && tuiOptions.fullscreen)
|
|
320
|
-
model.reset();
|
|
321
|
-
model.replay(messages, userLine);
|
|
322
|
-
model.writeLine(ui.dim(t().resumed(runDate(summary?.updatedAt ?? new Date()))));
|
|
323
|
-
}
|
|
324
|
-
/** /resume: picks one of the runs and switches the session to it. */
|
|
325
|
-
async function resume() {
|
|
326
|
-
if (!opener || !runsDir)
|
|
327
|
-
return;
|
|
328
|
-
const runs = await listRuns(runsDir);
|
|
329
|
-
if (runs.length === 0) {
|
|
330
|
-
model.writeLine(ui.dim(t().noEarlierRuns));
|
|
331
|
-
return;
|
|
229
|
+
const refreshFiles = () => void listProjectFiles(controller.cwd()).then((files) => input.setFiles(files));
|
|
230
|
+
refreshFiles();
|
|
231
|
+
const toned = (text, tone) => (tone === "plain" ? text : tone === "dim" ? ui.dim(text) : tone === "warn" ? ui.warn(text) : ui.error(text));
|
|
232
|
+
controller.onEvent((event) => {
|
|
233
|
+
switch (event.type) {
|
|
234
|
+
case "agent":
|
|
235
|
+
if (event.event.type === "prompt-suggestion")
|
|
236
|
+
input.setSuggestion(event.event.suggestion);
|
|
237
|
+
else
|
|
238
|
+
model.render(event.event);
|
|
239
|
+
return;
|
|
240
|
+
case "user":
|
|
241
|
+
model.note(userLine(event.text), "user");
|
|
242
|
+
return;
|
|
243
|
+
case "notice":
|
|
244
|
+
model.writeLine(toned(event.text, event.tone));
|
|
245
|
+
return;
|
|
246
|
+
case "turn-start":
|
|
247
|
+
input.setSuggestion(null);
|
|
248
|
+
status?.turnStarted();
|
|
249
|
+
model.separate();
|
|
250
|
+
model.startTurn();
|
|
251
|
+
return;
|
|
252
|
+
case "turn-end":
|
|
253
|
+
model.endTurn();
|
|
254
|
+
status?.turnEnded();
|
|
255
|
+
return;
|
|
256
|
+
case "conversation":
|
|
257
|
+
// In full screen the history is redrawn from the start; inline, the terminal's
|
|
258
|
+
// scrollback can't be taken back, so the conversation follows what's already there.
|
|
259
|
+
if (event.fromResume && tuiOptions.fullscreen)
|
|
260
|
+
model.reset();
|
|
261
|
+
model.replay(event.messages, userLine);
|
|
262
|
+
model.writeLine(ui.dim(t().resumed(runDate(event.updatedAt))));
|
|
263
|
+
return;
|
|
264
|
+
case "session":
|
|
265
|
+
refreshFiles();
|
|
266
|
+
return;
|
|
267
|
+
case "mode":
|
|
268
|
+
return; // the status bar follows the state
|
|
332
269
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
current = await openSession(opener, await runFolderOf(summary));
|
|
340
|
-
openLog();
|
|
341
|
-
connect();
|
|
342
|
-
await showConversation(current.run, true);
|
|
343
|
-
}
|
|
270
|
+
});
|
|
271
|
+
controller.subscribe((state) => {
|
|
272
|
+
input.setCommands(state.commands);
|
|
273
|
+
if (state.contextPercent !== model.getSnapshot().contextPercent)
|
|
274
|
+
model.setContextPercent(state.contextPercent);
|
|
275
|
+
});
|
|
344
276
|
const previousPort = getInteractionPort();
|
|
345
277
|
setInteractionPort(interaction.port);
|
|
346
278
|
const restoreTerminal = tuiOptions.fullscreen ? enterFullscreen(stdout) : null;
|
|
347
279
|
const cursor = tuiOptions.fullscreen ? createCursorController(stdout) : null;
|
|
348
|
-
|
|
280
|
+
status = tuiOptions.terminalIntegration === false ? null : createTerminalStatus((text) => void stdout.write(text), tuiOptions.header?.title ?? "agent");
|
|
349
281
|
status?.start();
|
|
350
|
-
const app = render(_jsx(CursorContext.Provider, { value: cursor, children: _jsx(KitTheme, { children: _jsx(ChatApp, { model: model, interaction: interaction, input: input, promptLabel: promptLabel, renderApproval: tuiOptions.renderApproval, mode: tuiOptions.mode, fullscreen: tuiOptions.fullscreen, header: tuiOptions.header && tuiOptions.fullscreen ? headerLines(tuiOptions.header) : undefined, onFocusChange: status ? (focused) => status
|
|
282
|
+
const app = render(_jsx(CursorContext.Provider, { value: cursor, children: _jsx(KitTheme, { children: _jsx(ChatApp, { model: model, interaction: interaction, input: input, promptLabel: promptLabel, renderApproval: tuiOptions.renderApproval, mode: tuiOptions.mode, fullscreen: tuiOptions.fullscreen, header: tuiOptions.header && tuiOptions.fullscreen ? headerLines(tuiOptions.header) : undefined, onFocusChange: status ? (focused) => status?.setFocused(focused) : undefined, controller: controller }) }) }),
|
|
351
283
|
// In full screen Ink writes through the cursor controller, which places the terminal's cursor.
|
|
352
284
|
{ exitOnCtrlC: false, ...(cursor ? { stdout: cursor.stream } : {}) });
|
|
353
285
|
try {
|
|
@@ -355,71 +287,24 @@ export async function runChatInk(options, tuiOptions = {}) {
|
|
|
355
287
|
if (tuiOptions.header && !tuiOptions.fullscreen)
|
|
356
288
|
model.note(headerLines(tuiOptions.header).join("\n"));
|
|
357
289
|
for (const warning of languageWarnings)
|
|
358
|
-
|
|
290
|
+
controller.notice(warning, "warn");
|
|
359
291
|
if (tuiOptions.welcomeMessage)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
await showConversation(current.run, false);
|
|
363
|
-
if (tuiOptions.initialPrompt && !current.run?.sessionId) {
|
|
364
|
-
// Logged but not shown, like runChatTui(): it is the agent's cue, not something typed.
|
|
365
|
-
mirror(`${promptLabel}${tuiOptions.initialPrompt}\n`);
|
|
366
|
-
await runTurn(tuiOptions.initialPrompt);
|
|
367
|
-
}
|
|
292
|
+
controller.notice(tuiOptions.welcomeMessage, "plain");
|
|
293
|
+
await controller.start();
|
|
368
294
|
while (true) {
|
|
369
295
|
// The blank line before the prompt is in place while typing, so Enter doesn't push
|
|
370
296
|
// everything down a line: promptLabel's own leading newlines only reach the log.
|
|
371
297
|
model.separate();
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
298
|
+
const line = await input.next(controller.getState().history);
|
|
299
|
+
if (line === null)
|
|
374
300
|
break;
|
|
375
|
-
|
|
376
|
-
if (!line)
|
|
377
|
-
continue;
|
|
378
|
-
mirror(`${promptLabel}${line}\n`);
|
|
379
|
-
model.note(userLine(line), "user");
|
|
380
|
-
if (tuiOptions.historyPath) {
|
|
381
|
-
historyEntries = capHistory([...historyEntries, { text: line, timestamp: new Date().toISOString() }], historyLimit);
|
|
382
|
-
await saveHistory(tuiOptions.historyPath, historyEntries);
|
|
383
|
-
}
|
|
384
|
-
if (exitCommands.has(line.toLowerCase()))
|
|
301
|
+
if ((await controller.send(line)) === "exit")
|
|
385
302
|
break;
|
|
386
|
-
// A local command, never sent to the model: the last reply to the clipboard (OSC 52).
|
|
387
|
-
if (status && line.toLowerCase() === LOCAL_COMMANDS.copy) {
|
|
388
|
-
const reply = model.lastReply();
|
|
389
|
-
if (reply)
|
|
390
|
-
stdout.write(clipboardSequence(reply));
|
|
391
|
-
model.writeLine(ui.dim(reply ? t().copiedReply(reply.length) : t().nothingToCopy));
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
if (runsDir && line.toLowerCase() === LOCAL_COMMANDS.resume) {
|
|
395
|
-
await resume();
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
// A local command too: into plan mode, or back to the mode it was entered from.
|
|
399
|
-
if (line.toLowerCase() === LOCAL_COMMANDS.plan) {
|
|
400
|
-
const control = modeControl();
|
|
401
|
-
const next = control ? togglePlanMode(control) : null;
|
|
402
|
-
if (next)
|
|
403
|
-
input.setMode(next);
|
|
404
|
-
else
|
|
405
|
-
model.writeLine(ui.dim(t().modeLocked(control?.mode ?? tuiOptions.mode)));
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
const commandToken = slashCommandToken(line);
|
|
409
|
-
if (commandToken) {
|
|
410
|
-
const names = await knownCommands;
|
|
411
|
-
if (names && !names.has(commandToken)) {
|
|
412
|
-
model.writeLine(ui.warn(t().unknownCommand(commandToken)));
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
await runTurn(line);
|
|
417
303
|
}
|
|
418
304
|
}
|
|
419
305
|
finally {
|
|
420
306
|
setInteractionPort(previousPort);
|
|
421
|
-
|
|
422
|
-
run.close();
|
|
307
|
+
controller.close();
|
|
423
308
|
input.close();
|
|
424
309
|
// One more frame with only the history, so the prompt and status bar don't stay behind.
|
|
425
310
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -427,6 +312,5 @@ export async function runChatInk(options, tuiOptions = {}) {
|
|
|
427
312
|
await app.waitUntilExit();
|
|
428
313
|
status?.stop();
|
|
429
314
|
restoreTerminal?.();
|
|
430
|
-
sessionLog?.end();
|
|
431
315
|
}
|
|
432
316
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "awareness",
|
|
3
|
+
"description": "You know who you are and what you are right now (your mode, extensions, tools, subagents, commands, context) through about_me, and how your chat is used through the help skill.",
|
|
4
|
+
"agent-kit": {
|
|
5
|
+
"provides": [
|
|
6
|
+
"self-awareness"
|
|
7
|
+
],
|
|
8
|
+
"requires": []
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: How to use this agent - its
|
|
2
|
+
name: help
|
|
3
|
+
description: How to use this agent's chat - its modes, keys and slash commands, resuming an earlier conversation, approvals and questions, the command line. Load it whenever the person asks how to do something with you, or what a key or command does.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Helping the person use you
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This page is the chat's part, the same for every agent built on agent-kit. What you are right now (your mode, the commands this session has, your extensions) comes from `about_me`, and your own domain from `about_me` with `part: "guide"`. If something is in none of them, say you don't know rather than inventing a key, command or option. Answer in the language you reply in, briefly, with the exact key or command; keys and commands are typed as written here, whatever the language.
|
|
9
9
|
|
|
10
10
|
## Modes
|
|
11
11
|
|
|
@@ -45,6 +45,7 @@ Messages typed while the agent works are queued and sent when it finishes.
|
|
|
45
45
|
- `/exit` or `/quit`: leave the chat (an agent can name its own exit commands instead).
|
|
46
46
|
- `/copy`: copy the last reply to the clipboard.
|
|
47
47
|
- `/resume`: pick an earlier conversation to go on with (`↑`/`↓`, `Enter`, `Esc` to cancel), where the agent keeps its conversations.
|
|
48
|
+
- `/extensions`: what the session runs with, what's off and why, and the extensions installed; `/extensions enable <name>` or `/extensions disable <name>` turns an installed one on or off and reopens the session, keeping the conversation.
|
|
48
49
|
- `/plan`: plan mode on or off (see Modes).
|
|
49
50
|
|
|
50
51
|
Typing `/` and `Tab` lists every command, the agent's own included (named `/plugin:command`). An unknown `/command` isn't sent.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knowledge",
|
|
3
|
-
"description": "Built-in knowledge base workflows (an LLM wiki) over the kit's knowledge_* tools: ingesting sources, querying the knowledge base and health-checking it."
|
|
3
|
+
"description": "Built-in knowledge base workflows (an LLM wiki) over the kit's knowledge_* tools: ingesting sources, querying the knowledge base and health-checking it.",
|
|
4
|
+
"agent-kit": {
|
|
5
|
+
"provides": [
|
|
6
|
+
"knowledge-base"
|
|
7
|
+
],
|
|
8
|
+
"requires": []
|
|
9
|
+
}
|
|
4
10
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Ingest material into the knowledge base right now - the files or topic given, or everything in sources/ not ingested yet
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Apply the `knowledge-ingest` skill right now to: $ARGUMENTS
|
|
6
|
+
|
|
7
|
+
If nothing is given above, ingest every original in `list_sources` that no summary is about, and update the summaries whose original's `changedAt` is after their `ingested` (both ISO 8601 in UTC: the later one sorts after as text). When you're done, summarize which pages you created and updated, and anything you couldn't read.
|
|
@@ -9,7 +9,12 @@ A source is ingested once, and from then on the knowledge base holds what it tau
|
|
|
9
9
|
|
|
10
10
|
## 1. Check it isn't already ingested
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The sources and the knowledge base each keep their own data; you match them:
|
|
13
|
+
|
|
14
|
+
- `list_sources` gives each original with `changedAt`, when its content last changed.
|
|
15
|
+
- `knowledge_index` gives each summary with `file`, the original it's about, and `ingested`, when it was written.
|
|
16
|
+
|
|
17
|
+
An original with no summary needs ingesting. One whose `changedAt` is after its summary's `ingested` changed since: update its summary (`knowledge_rewrite`, which sets `ingested` again). Both dates are ISO 8601 in UTC, so the later one sorts after as text; use `date_math` if in doubt. For a document from elsewhere, `knowledge_search` its title or URL.
|
|
13
18
|
|
|
14
19
|
## 2. Read it for real
|
|
15
20
|
|
|
@@ -17,7 +22,7 @@ Read the whole source, not its first page (a long PDF in parts: `list_sources` g
|
|
|
17
22
|
|
|
18
23
|
## 3. Write the summary page
|
|
19
24
|
|
|
20
|
-
`knowledge_create` with type `summary` (call it once without content to get the template), and the field `file` (the original,
|
|
25
|
+
`knowledge_create` with type `summary` (call it once without content to get the template), and the field `file` (the original, exactly as `list_sources` names it) or `url`; `ingested` is set for you. Key points quote literally where exactness matters. Create the new concept and entity pages it feeds in the same call (`also`): they can link to each other.
|
|
21
26
|
|
|
22
27
|
## 4. Update concepts and entities
|
|
23
28
|
|
|
@@ -33,4 +38,4 @@ If the source contradicts a page, record both versions with attribution in both
|
|
|
33
38
|
|
|
34
39
|
## An original that was wrong or replaced
|
|
35
40
|
|
|
36
|
-
`retire_source` (the person
|
|
41
|
+
`retire_source` (it asks the person itself) moves it aside; `list_sources` shows it as retired, with why and what replaced it. The knowledge base is then yours to update: find its summaries (`knowledge_index`, by `file`) and the passages that cite it (`knowledge_search` its file name). After a wrong one, `knowledge_retire` its summary (it asks the person itself: don't ask them first) and correct or remove what it taught (`knowledge_edit`, or `knowledge_retire` for a page that was only about it). After a replaced one, ingest the new original, `knowledge_supersede` the old summary by the new one, and re-ground those passages on it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: knowledge-lint
|
|
3
|
-
description: Health-check the knowledge base - broken links, orphan pages, links to retired pages,
|
|
3
|
+
description: Health-check the knowledge base - broken links, orphan pages, links to retired pages, summaries behind their originals, duplicated concepts, mentioned-but-missing concepts, contradictions, gaps - fix what's mechanical and report the rest.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Linting the knowledge base
|
|
@@ -9,14 +9,21 @@ A knowledge base maintained across sessions drifts. Lint it when asked and after
|
|
|
9
9
|
|
|
10
10
|
## 1. The mechanical checks, in one call
|
|
11
11
|
|
|
12
|
-
`knowledge_check` returns broken links, orphan pages
|
|
12
|
+
`knowledge_check` returns broken links, orphan pages and links to retired pages. The index and backlinks are kept by the store: there's nothing to fix there.
|
|
13
13
|
|
|
14
14
|
- **Broken links**: fix them with `knowledge_edit` when the right target is obvious (a typo, a page under a close name: `knowledge_search`); otherwise report them.
|
|
15
15
|
- **Orphans**: link them from the pages where they belong, or report why they don't belong anywhere.
|
|
16
|
-
- **Links to retired pages
|
|
17
|
-
- **New or changed originals**: report them as what to ingest or update next (or do it, if asked).
|
|
16
|
+
- **Links to retired pages**: correct or re-ground them.
|
|
18
17
|
|
|
19
|
-
## 2. The
|
|
18
|
+
## 2. The knowledge base against its originals
|
|
19
|
+
|
|
20
|
+
When there's a sources folder, match `list_sources` with `knowledge_index`:
|
|
21
|
+
|
|
22
|
+
- **Not ingested**: present originals no summary is about. Report them as what to ingest next (or do it, if asked).
|
|
23
|
+
- **Behind**: summaries whose original's `changedAt` is after their `ingested`, (ISO 8601 in UTC: the later one sorts after as text). Report them as what to update.
|
|
24
|
+
- **Retired or missing originals**: summaries still active whose original is retired (`knowledge_retire` them if it was wrong, which asks the person itself, or `knowledge_supersede` them if it was replaced) or missing (report them); and the passages that cite them (`knowledge_search` the file name).
|
|
25
|
+
|
|
26
|
+
## 3. The checks that need judgement
|
|
20
27
|
|
|
21
28
|
Use `knowledge_index` and `knowledge_search`; read only the pages a check needs.
|
|
22
29
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "memory",
|
|
3
|
+
"description": "Your memory of the person you work for, across all their projects: who they are and how they want things done, kept only from what they tell you, through its tools: recall, remember and forget.",
|
|
4
|
+
"agent-kit": {
|
|
5
|
+
"provides": [
|
|
6
|
+
"person-memory"
|
|
7
|
+
],
|
|
8
|
+
"requires": []
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Forget an entry of what the agent remembers about you
|
|
3
|
+
argument-hint: <entry>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The person wants you to forget this from your memory of them: $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
Find the entry with `recall` (by name, or the one whose description matches) and `forget` it, then say which one you forgot. If nothing is given above, or several entries match, show them and ask which. Stop applying what it said from now on.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show what the agent remembers about you, across all your projects
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Call `recall`, without a name, right now and show the person everything you remember about them, grouped by type (who they are, how they want things done), one short line per entry with its name. If it's empty, say so. End by telling them `/memory:forget <entry>` forgets an entry, or that they can just tell you what's wrong.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falkenslab/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Library for building an agent on top of the Claude Agent SDK",
|
|
5
5
|
"author": "Francisco Vargas Ruiz",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
-
"assets"
|
|
35
|
+
"assets",
|
|
36
|
+
"extensions"
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
39
|
"build": "tsc",
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Ingest material into the knowledge base right now - the files or topic given, or everything in sources/ not ingested yet
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
Apply the `knowledge-ingest` skill right now to: $ARGUMENTS
|
|
6
|
-
|
|
7
|
-
If nothing is given above, ingest every original `list_sources` reports as new, and update the summaries of the ones it reports as changed. When you're done, summarize which pages you created and updated, and anything you couldn't read.
|