@falkenslab/agent-kit 0.18.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 +30 -16
- 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/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/messages/de.js +11 -32
- package/dist/core/messages/en.d.ts +13 -16
- package/dist/core/messages/en.js +11 -32
- package/dist/core/messages/es.js +11 -32
- package/dist/core/messages/fr.js +11 -32
- 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 +30 -5
- package/dist/core/session.js +143 -94
- 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/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/extensions/knowledge/index.d.ts +8 -0
- package/dist/extensions/knowledge/index.js +35 -0
- package/dist/{core → extensions/knowledge}/knowledgeStore.d.ts +1 -0
- package/dist/extensions/knowledge/labels.d.ts +3 -0
- package/dist/extensions/knowledge/labels.js +119 -0
- package/dist/{core/knowledge.d.ts → extensions/knowledge/prompt.d.ts} +1 -1
- package/dist/{core/knowledge.js → extensions/knowledge/prompt.js} +7 -4
- package/dist/{core/tools/knowledgeTools.d.ts → extensions/knowledge/tools.d.ts} +1 -1
- package/dist/{core/tools/knowledgeTools.js → extensions/knowledge/tools.js} +2 -2
- 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/tools/saveToSources.d.ts → extensions/sources/tools.d.ts} +1 -1
- package/dist/{core/tools/saveToSources.js → extensions/sources/tools.js} +5 -5
- 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/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/dist/core/agentHelp.d.ts +0 -30
- package/dist/core/agentHelp.js +0 -55
- /package/dist/{core → extensions/knowledge}/fileKnowledgeStore.d.ts +0 -0
- /package/dist/{core → extensions/knowledge}/fileKnowledgeStore.js +0 -0
- /package/dist/{core → extensions/knowledge}/knowledgeStore.js +0 -0
- /package/dist/{core → extensions/sources}/extractText.d.ts +0 -0
- /package/dist/{core → extensions/sources}/extractText.js +0 -0
- /package/dist/{core → extensions/sources}/sources.d.ts +0 -0
- /package/dist/{core → extensions/sources}/sources.js +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/commands/ingest.md +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/commands/lint.md +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/commands/query.md +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-ingest/SKILL.md +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-lint/SKILL.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,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",
|
package/dist/core/agentHelp.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { AgentIdentity, Mode } from "./agentSpec.js";
|
|
2
|
-
/**
|
|
3
|
-
* The agent's self-knowledge: who it is (a section of the system prompt) and how it is used
|
|
4
|
-
* (the `agent-help` skill). The chat's part of the skill is the kit's, the same for every
|
|
5
|
-
* agent and kept next to the chat's code (`assets/agent-help/SKILL.md`, checked against the
|
|
6
|
-
* chat's commands and keys by a test); the agent's part is its own guide (`AgentSpec.helpGuide`).
|
|
7
|
-
*/
|
|
8
|
-
/** The plugin the skill is loaded from, as the SDK names its skills ("agent-kit:agent-help"). */
|
|
9
|
-
export declare const AGENT_HELP_SKILL = "agent-kit:agent-help";
|
|
10
|
-
/** Absolute path of the kit's part of the skill, next to `dist/` (or `src/` under tsx). */
|
|
11
|
-
export declare function agentHelpSkillPath(): string;
|
|
12
|
-
/** The "Who you are" section appended to the system prompt: name, version, what it is, the kit's version. */
|
|
13
|
-
export declare function identityPromptSection(identity: AgentIdentity): string;
|
|
14
|
-
/** What the generated skill says about this session. */
|
|
15
|
-
export interface AgentHelpSession {
|
|
16
|
-
mode: Mode;
|
|
17
|
-
/** The modes Shift+Tab goes through (`ModeControl.switchable`). */
|
|
18
|
-
switchable: readonly Mode[];
|
|
19
|
-
/** The built-in knowledge base is on: its slash commands exist. */
|
|
20
|
-
knowledgeBase: boolean;
|
|
21
|
-
/** The sources folder, as shown to the model (e.g. "sources/"). */
|
|
22
|
-
sourcesFolder?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Writes the `agent-help` plugin into `dir` (a folder of this run's), with the kit's part of
|
|
26
|
-
* the skill, this session's facts and the agent's own guide, and returns its root. Generated
|
|
27
|
-
* rather than shipped as it is: the guide goes inside the skill, so the agent needs no file
|
|
28
|
-
* tools to read it, and nothing about it reaches the context until the skill is loaded.
|
|
29
|
-
*/
|
|
30
|
-
export declare function writeAgentHelpPlugin(dir: string, session: AgentHelpSession, helpGuide?: string): Promise<string>;
|
package/dist/core/agentHelp.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { agentKitVersion } from "./version.js";
|
|
5
|
-
/**
|
|
6
|
-
* The agent's self-knowledge: who it is (a section of the system prompt) and how it is used
|
|
7
|
-
* (the `agent-help` skill). The chat's part of the skill is the kit's, the same for every
|
|
8
|
-
* agent and kept next to the chat's code (`assets/agent-help/SKILL.md`, checked against the
|
|
9
|
-
* chat's commands and keys by a test); the agent's part is its own guide (`AgentSpec.helpGuide`).
|
|
10
|
-
*/
|
|
11
|
-
/** The plugin the skill is loaded from, as the SDK names its skills ("agent-kit:agent-help"). */
|
|
12
|
-
export const AGENT_HELP_SKILL = "agent-kit:agent-help";
|
|
13
|
-
/** Absolute path of the kit's part of the skill, next to `dist/` (or `src/` under tsx). */
|
|
14
|
-
export function agentHelpSkillPath() {
|
|
15
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "agent-help", "SKILL.md");
|
|
16
|
-
}
|
|
17
|
-
/** The "Who you are" section appended to the system prompt: name, version, what it is, the kit's version. */
|
|
18
|
-
export function identityPromptSection(identity) {
|
|
19
|
-
const who = `${identity.name}${identity.version ? ` ${identity.version}` : ""}${identity.description ? `: ${identity.description}` : ""}`;
|
|
20
|
-
return `## Who you are
|
|
21
|
-
You are ${who}. You are built on agent-kit ${agentKitVersion()}, which provides your chat (its modes, keys and slash commands) and your built-in tools.
|
|
22
|
-
When the person asks how to use you (your modes, keys or commands, resuming an earlier conversation, your configuration, what you can do), load the \`agent-help\` skill and answer from it; don't invent options it doesn't list.`;
|
|
23
|
-
}
|
|
24
|
-
/** The skill's "This session" section. */
|
|
25
|
-
function sessionSection(session) {
|
|
26
|
-
const lines = [
|
|
27
|
-
`- This session started in **${session.mode}** mode.`,
|
|
28
|
-
session.switchable.length > 1
|
|
29
|
-
? `- \`Shift+Tab\` goes through: ${session.switchable.join(", ")}.`
|
|
30
|
-
: "- The mode can't be switched during this session.",
|
|
31
|
-
];
|
|
32
|
-
if (session.knowledgeBase)
|
|
33
|
-
lines.push("- You keep a knowledge base (your memory across sessions). Its commands: `/knowledge:ingest` (learn the new or changed files of the sources folder), `/knowledge:query <question>` (answer from it), `/knowledge:lint` (check it and fix what's mechanical).");
|
|
34
|
-
if (session.sourcesFolder)
|
|
35
|
-
lines.push(`- Originals go in \`${session.sourcesFolder}\`: the person drops files there (or you ask for one, or download it), and you never change them.`);
|
|
36
|
-
return `## This session\n\n${lines.join("\n")}`;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Writes the `agent-help` plugin into `dir` (a folder of this run's), with the kit's part of
|
|
40
|
-
* the skill, this session's facts and the agent's own guide, and returns its root. Generated
|
|
41
|
-
* rather than shipped as it is: the guide goes inside the skill, so the agent needs no file
|
|
42
|
-
* tools to read it, and nothing about it reaches the context until the skill is loaded.
|
|
43
|
-
*/
|
|
44
|
-
export async function writeAgentHelpPlugin(dir, session, helpGuide) {
|
|
45
|
-
const kitPart = (await readFile(agentHelpSkillPath(), "utf8")).trimEnd();
|
|
46
|
-
const guide = helpGuide ? (await readFile(helpGuide, "utf8")).trim() : "";
|
|
47
|
-
const agentPart = guide
|
|
48
|
-
? `## This agent\n\nThe agent's own guide, for everything about its domain (its commands, configuration, folders):\n\n${guide}`
|
|
49
|
-
: "## This agent\n\nThis agent has no guide of its own: for its domain, say only what your instructions tell you, and that the rest isn't documented.";
|
|
50
|
-
await mkdir(path.join(dir, ".claude-plugin"), { recursive: true });
|
|
51
|
-
await mkdir(path.join(dir, "skills", "agent-help"), { recursive: true });
|
|
52
|
-
await writeFile(path.join(dir, ".claude-plugin", "plugin.json"), `${JSON.stringify({ name: "agent-kit", description: "How to use this agent: the kit's chat and the agent's own guide." }, null, 2)}\n`);
|
|
53
|
-
await writeFile(path.join(dir, "skills", "agent-help", "SKILL.md"), `${kitPart}\n\n${sessionSection(session)}\n\n${agentPart}\n`);
|
|
54
|
-
return dir;
|
|
55
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|