@falkenslab/agent-kit 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/assets/knowledge-plugin/.claude-plugin/plugin.json +1 -1
- package/assets/knowledge-plugin/commands/ingest.md +1 -1
- package/assets/knowledge-plugin/commands/lint.md +1 -1
- package/assets/knowledge-plugin/skills/knowledge-ingest/SKILL.md +13 -19
- package/assets/knowledge-plugin/skills/knowledge-lint/SKILL.md +18 -14
- package/assets/knowledge-plugin/skills/knowledge-query/SKILL.md +5 -5
- package/assets/knowledge-plugin-files/.claude-plugin/plugin.json +4 -0
- package/assets/knowledge-plugin-files/commands/ingest.md +7 -0
- package/assets/knowledge-plugin-files/commands/lint.md +5 -0
- package/assets/knowledge-plugin-files/commands/query.md +5 -0
- package/assets/knowledge-plugin-files/skills/knowledge-ingest/SKILL.md +46 -0
- package/assets/knowledge-plugin-files/skills/knowledge-lint/SKILL.md +26 -0
- package/assets/knowledge-plugin-files/skills/knowledge-query/SKILL.md +14 -0
- package/dist/core/agentSpec.d.ts +27 -2
- package/dist/core/extractText.d.ts +13 -0
- package/dist/core/extractText.js +147 -0
- package/dist/core/fileKnowledgeStore.d.ts +10 -0
- package/dist/core/fileKnowledgeStore.js +395 -0
- package/dist/core/hooks/fileScopeGate.d.ts +8 -0
- package/dist/core/hooks/fileScopeGate.js +20 -0
- package/dist/core/hooks/humanInput.d.ts +18 -0
- package/dist/core/hooks/humanInput.js +30 -2
- package/dist/core/hooks/planGate.js +14 -5
- package/dist/core/hooks/stepGate.js +6 -0
- package/dist/core/interaction.d.ts +20 -0
- package/dist/core/knowledge.d.ts +14 -4
- package/dist/core/knowledge.js +36 -6
- package/dist/core/knowledgeStore.d.ts +139 -0
- package/dist/core/knowledgeStore.js +84 -0
- package/dist/core/messages/de.js +65 -1
- package/dist/core/messages/en.d.ts +39 -0
- package/dist/core/messages/en.js +65 -1
- package/dist/core/messages/es.js +65 -1
- package/dist/core/messages/fr.js +65 -1
- package/dist/core/modeControl.d.ts +36 -0
- package/dist/core/modeControl.js +61 -0
- package/dist/core/session.d.ts +6 -31
- package/dist/core/session.js +81 -71
- package/dist/core/sources.d.ts +122 -0
- package/dist/core/sources.js +312 -0
- package/dist/core/todos.d.ts +23 -0
- package/dist/core/todos.js +33 -0
- package/dist/core/toolLabels.d.ts +1 -1
- package/dist/core/toolLabels.js +42 -2
- package/dist/core/tools/humanApproval.d.ts +9 -5
- package/dist/core/tools/humanApproval.js +39 -7
- package/dist/core/tools/knowledgeTools.d.ts +16 -0
- package/dist/core/tools/knowledgeTools.js +168 -0
- package/dist/core/tools/saveToSources.d.ts +27 -8
- package/dist/core/tools/saveToSources.js +217 -55
- package/dist/core/tools/time.d.ts +29 -0
- package/dist/core/tools/time.js +129 -0
- package/dist/core/version.d.ts +5 -0
- package/dist/core/version.js +12 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -1
- package/dist/tui/chatTui.js +14 -1
- package/dist/tui/consoleRenderer.js +16 -2
- package/dist/tui/ink/SessionView.js +67 -17
- package/dist/tui/ink/inkInteraction.d.ts +4 -2
- package/dist/tui/ink/inkInteraction.js +20 -2
- package/dist/tui/ink/runChatInk.js +5 -0
- package/dist/tui/ink/sessionModel.d.ts +3 -0
- package/dist/tui/ink/sessionModel.js +14 -1
- package/dist/tui/terminalInteraction.js +2 -0
- package/package.json +31 -1
- /package/assets/{knowledge-plugin → knowledge-plugin-files}/skills/knowledge-pages/SKILL.md +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* The date and time, and date arithmetic, for every agent: the kit puts no date in the
|
|
5
|
+
* prompt and only subagents have Bash, so without these an agent can't know what day it
|
|
6
|
+
* is, and models get "days until" and "three weeks from" wrong often enough to matter
|
|
7
|
+
* (deadlines, a course's calendar). Only reads: the plan gate lets the server through.
|
|
8
|
+
*/
|
|
9
|
+
const DAY_MS = 86_400_000;
|
|
10
|
+
const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
11
|
+
/** "now" in `timeZone`, as calendar parts. */
|
|
12
|
+
export function nowIn(timeZone, now = new Date()) {
|
|
13
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-CA", {
|
|
14
|
+
timeZone,
|
|
15
|
+
year: "numeric",
|
|
16
|
+
month: "2-digit",
|
|
17
|
+
day: "2-digit",
|
|
18
|
+
hour: "2-digit",
|
|
19
|
+
minute: "2-digit",
|
|
20
|
+
second: "2-digit",
|
|
21
|
+
hourCycle: "h23",
|
|
22
|
+
timeZoneName: "longOffset",
|
|
23
|
+
})
|
|
24
|
+
.formatToParts(now)
|
|
25
|
+
.map((part) => [part.type, part.value]));
|
|
26
|
+
const date = `${parts.year}-${parts.month}-${parts.day}`;
|
|
27
|
+
return {
|
|
28
|
+
date,
|
|
29
|
+
time: `${parts.hour}:${parts.minute}:${parts.second}`,
|
|
30
|
+
weekday: WEEKDAYS[toDay(date).getUTCDay()],
|
|
31
|
+
timeZone,
|
|
32
|
+
utcOffset: parts.timeZoneName.replace("GMT", "") || "+00:00",
|
|
33
|
+
utc: now.toISOString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** A YYYY-MM-DD calendar date as midnight UTC, so arithmetic never meets daylight saving. */
|
|
37
|
+
function toDay(date) {
|
|
38
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
39
|
+
if (!match)
|
|
40
|
+
throw new Error(`"${date}" isn't a date in YYYY-MM-DD form.`);
|
|
41
|
+
const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
|
|
42
|
+
if (day.toISOString().slice(0, 10) !== date)
|
|
43
|
+
throw new Error(`"${date}" isn't a real date.`);
|
|
44
|
+
return day;
|
|
45
|
+
}
|
|
46
|
+
const fromDay = (day) => day.toISOString().slice(0, 10);
|
|
47
|
+
const isWorkingDay = (day) => day.getUTCDay() !== 0 && day.getUTCDay() !== 6;
|
|
48
|
+
/** Days, weeks and working days (Monday to Friday) from `from` to `to`; negative when `to` is earlier. */
|
|
49
|
+
export function dateDifference(from, to) {
|
|
50
|
+
const start = toDay(from);
|
|
51
|
+
const end = toDay(to);
|
|
52
|
+
const days = Math.round((end.getTime() - start.getTime()) / DAY_MS);
|
|
53
|
+
// Working days counted from the day after the earlier date up to the later one included, as
|
|
54
|
+
// "in N working days"; the same count, negative, when `to` is earlier.
|
|
55
|
+
const [first, last] = days < 0 ? [end, start] : [start, end];
|
|
56
|
+
let workingDays = 0;
|
|
57
|
+
for (let day = first.getTime() + DAY_MS; day <= last.getTime(); day += DAY_MS)
|
|
58
|
+
if (isWorkingDay(new Date(day)))
|
|
59
|
+
workingDays++;
|
|
60
|
+
return { from, to, days, weeks: Math.trunc(days / 7), remainderDays: days % 7, workingDays: days < 0 ? -workingDays : workingDays, toWeekday: WEEKDAYS[end.getUTCDay()] };
|
|
61
|
+
}
|
|
62
|
+
/** `date` plus `amount` (negative to go back) `unit`s; months keep the day, or the month's last one. */
|
|
63
|
+
export function dateAdd(date, amount, unit) {
|
|
64
|
+
if (!Number.isInteger(amount))
|
|
65
|
+
throw new Error("The amount must be a whole number.");
|
|
66
|
+
const start = toDay(date);
|
|
67
|
+
let result;
|
|
68
|
+
if (unit === "days" || unit === "weeks") {
|
|
69
|
+
result = new Date(start.getTime() + amount * (unit === "weeks" ? 7 : 1) * DAY_MS);
|
|
70
|
+
}
|
|
71
|
+
else if (unit === "months") {
|
|
72
|
+
const target = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + amount, 1));
|
|
73
|
+
const lastDay = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
|
|
74
|
+
result = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth(), Math.min(start.getUTCDate(), lastDay)));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
result = start;
|
|
78
|
+
const step = Math.sign(amount);
|
|
79
|
+
for (let left = Math.abs(amount); left > 0;) {
|
|
80
|
+
result = new Date(result.getTime() + step * DAY_MS);
|
|
81
|
+
if (isWorkingDay(result))
|
|
82
|
+
left--;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { date: fromDay(result), weekday: WEEKDAYS[result.getUTCDay()] };
|
|
86
|
+
}
|
|
87
|
+
const text = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
88
|
+
const failure = (error) => ({ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true });
|
|
89
|
+
/** The time zone the tools answer in: `timeZone` if valid, otherwise the system's. */
|
|
90
|
+
export function resolveTimeZone(timeZone) {
|
|
91
|
+
const system = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
92
|
+
if (!timeZone)
|
|
93
|
+
return system;
|
|
94
|
+
try {
|
|
95
|
+
new Intl.DateTimeFormat("en", { timeZone });
|
|
96
|
+
return timeZone;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error(`Unknown time zone "${timeZone}" (an IANA name, e.g. "Europe/Madrid").`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** `current_time` and `date_math`, in `timeZone` (an IANA name; the system's if not given). */
|
|
103
|
+
export function createTimeServer(timeZone) {
|
|
104
|
+
const zone = resolveTimeZone(timeZone);
|
|
105
|
+
const currentTime = tool("current_time", "The current date and time: the local date (YYYY-MM-DD), time, weekday, time zone and UTC offset, and the instant in UTC. Use it whenever you need today's date or the time; don't guess them.", {}, async () => text(nowIn(zone)), { annotations: { readOnlyHint: true } });
|
|
106
|
+
const dateMath = tool("date_math", 'Exact date arithmetic on calendar dates (YYYY-MM-DD, local). "difference": days, weeks and working days (Monday to Friday) from `from` to `to`. "add": `from` plus `amount` (negative to go back) days, weeks, months or working days. `from` defaults to today. Use it instead of counting dates yourself.', {
|
|
107
|
+
operation: z.enum(["difference", "add"]),
|
|
108
|
+
from: z.string().optional().describe("Start date, YYYY-MM-DD; today if omitted"),
|
|
109
|
+
to: z.string().optional().describe('End date, YYYY-MM-DD (for "difference")'),
|
|
110
|
+
amount: z.number().int().optional().describe('How many units to add, negative to subtract (for "add")'),
|
|
111
|
+
unit: z.enum(["days", "weeks", "months", "working_days"]).optional().describe('The unit of `amount` (for "add"); days if omitted'),
|
|
112
|
+
}, async (args) => {
|
|
113
|
+
try {
|
|
114
|
+
const from = args.from ?? nowIn(zone).date;
|
|
115
|
+
if (args.operation === "difference") {
|
|
116
|
+
if (!args.to)
|
|
117
|
+
throw new Error('"difference" needs `to`.');
|
|
118
|
+
return text(dateDifference(from, args.to));
|
|
119
|
+
}
|
|
120
|
+
if (args.amount === undefined)
|
|
121
|
+
throw new Error('"add" needs `amount`.');
|
|
122
|
+
return text({ from, ...dateAdd(from, args.amount, args.unit ?? "days") });
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return failure(error);
|
|
126
|
+
}
|
|
127
|
+
}, { annotations: { readOnlyHint: true } });
|
|
128
|
+
return createSdkMcpServer({ name: "time", version: "1.0.0", tools: [currentTime, dateMath] });
|
|
129
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
let cached;
|
|
5
|
+
/**
|
|
6
|
+
* The version of agent-kit in use, read from its own `package.json` (next to `dist/`, or
|
|
7
|
+
* `src/` under tsx), e.g. to show it in an agent's header or log.
|
|
8
|
+
*/
|
|
9
|
+
export function agentKitVersion() {
|
|
10
|
+
cached ??= JSON.parse(readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json"), "utf8")).version;
|
|
11
|
+
return cached;
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export type { Mode, BaseSessionConfig, AgentSpec, PlanModeSpec } from "./core/ag
|
|
|
10
10
|
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode, type ModeControl } from "./core/session.js";
|
|
11
11
|
export { runQuery, type AgentEvent, type AgentRun, type SessionUsage, type ContextUsage } from "./core/runner.js";
|
|
12
12
|
export { knowledgePluginRoot, knowledgePromptSection } from "./core/knowledge.js";
|
|
13
|
+
export { agentKitVersion } from "./core/version.js";
|
|
14
|
+
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, type KnowledgeStore, type PageType, type KnowledgePage, type PageInfo, type SearchHit, type CheckReport, type FieldChanges, type NewPage, } from "./core/knowledgeStore.js";
|
|
15
|
+
export { createFileKnowledgeStore, type FileKnowledgeStoreOptions } from "./core/fileKnowledgeStore.js";
|
|
16
|
+
export { createKnowledgeServer, type KnowledgeToolsOptions } from "./core/tools/knowledgeTools.js";
|
|
13
17
|
export { resolveLanguage, detectLanguage, SUPPORTED_LANGUAGES, type Language, type LanguageSources, type ResolvedLanguage, } from "./core/language.js";
|
|
14
18
|
export { setLanguage, getLanguage, messagesFor, type Messages } from "./core/messages/index.js";
|
|
15
19
|
export { createRunStore, createRunFolder, listRuns, readConversation, type RunFolder, type RunSummary, type ConversationMessage, } from "./core/runs.js";
|
|
@@ -22,12 +26,12 @@ export { createSubagentTypeGate } from "./core/hooks/subagentTypeGate.js";
|
|
|
22
26
|
export { createSubagentForegroundGate } from "./core/hooks/subagentForegroundGate.js";
|
|
23
27
|
export { createFileScopeGate, checkFileScope, type FileScope } from "./core/hooks/fileScopeGate.js";
|
|
24
28
|
export { createPlanGate, checkPlanScope, type PlanScope } from "./core/hooks/planGate.js";
|
|
25
|
-
export { askForDecision, askForManualIntervention } from "./core/hooks/humanInput.js";
|
|
26
|
-
export { setInteractionPort, getInteractionPort, type InteractionPort, type ApprovalPrompt } from "./core/interaction.js";
|
|
29
|
+
export { askForDecision, askForManualIntervention, askForChoice, askForText, type ChoiceAnswer } from "./core/hooks/humanInput.js";
|
|
30
|
+
export { setInteractionPort, getInteractionPort, type InteractionPort, type ApprovalPrompt, type ChoiceSettings } from "./core/interaction.js";
|
|
27
31
|
export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
|
|
28
32
|
export { createHumanApprovalServer, DEFAULT_HUMAN_APPROVAL_TEXTS, type HumanApprovalTexts } from "./core/tools/humanApproval.js";
|
|
29
33
|
export { createManualLoginServer, type ManualInterventionTexts } from "./core/tools/manualLogin.js";
|
|
30
|
-
export { createSaveToSourcesServer } from "./core/tools/saveToSources.js";
|
|
34
|
+
export { createSaveToSourcesServer, type SourceToolsOptions } from "./core/tools/saveToSources.js";
|
|
31
35
|
export { runChatTui, type ChatTuiOptions } from "./tui/chatTui.js";
|
|
32
36
|
export type { SessionOpener } from "./tui/runs.js";
|
|
33
37
|
export { createConsoleRenderer, type ConsoleRenderer, type ConsoleRendererOptions } from "./tui/consoleRenderer.js";
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,10 @@ export { createFriendlyToolLabel, truncate, truncatePath, } from "./core/toolLab
|
|
|
11
11
|
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode } from "./core/session.js";
|
|
12
12
|
export { runQuery } from "./core/runner.js";
|
|
13
13
|
export { knowledgePluginRoot, knowledgePromptSection } from "./core/knowledge.js";
|
|
14
|
+
export { agentKitVersion } from "./core/version.js";
|
|
15
|
+
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, } from "./core/knowledgeStore.js";
|
|
16
|
+
export { createFileKnowledgeStore } from "./core/fileKnowledgeStore.js";
|
|
17
|
+
export { createKnowledgeServer } from "./core/tools/knowledgeTools.js";
|
|
14
18
|
export { resolveLanguage, detectLanguage, SUPPORTED_LANGUAGES, } from "./core/language.js";
|
|
15
19
|
export { setLanguage, getLanguage, messagesFor } from "./core/messages/index.js";
|
|
16
20
|
export { createRunStore, createRunFolder, listRuns, readConversation, } from "./core/runs.js";
|
|
@@ -24,7 +28,7 @@ export { createSubagentTypeGate } from "./core/hooks/subagentTypeGate.js";
|
|
|
24
28
|
export { createSubagentForegroundGate } from "./core/hooks/subagentForegroundGate.js";
|
|
25
29
|
export { createFileScopeGate, checkFileScope } from "./core/hooks/fileScopeGate.js";
|
|
26
30
|
export { createPlanGate, checkPlanScope } from "./core/hooks/planGate.js";
|
|
27
|
-
export { askForDecision, askForManualIntervention } from "./core/hooks/humanInput.js";
|
|
31
|
+
export { askForDecision, askForManualIntervention, askForChoice, askForText } from "./core/hooks/humanInput.js";
|
|
28
32
|
export { setInteractionPort, getInteractionPort } from "./core/interaction.js";
|
|
29
33
|
export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
|
|
30
34
|
// The checkpoints answer on the terminal by default, as they always have; a non-terminal
|
package/dist/tui/chatTui.js
CHANGED
|
@@ -188,6 +188,14 @@ export async function runChatTui(options, tuiOptions = {}) {
|
|
|
188
188
|
},
|
|
189
189
|
});
|
|
190
190
|
const { writeLine } = renderer;
|
|
191
|
+
// The mode can change from inside the session (the agent leaving plan mode with
|
|
192
|
+
// present_plan): say so, as /plan does.
|
|
193
|
+
let unsubscribeMode;
|
|
194
|
+
const watchMode = () => {
|
|
195
|
+
unsubscribeMode?.();
|
|
196
|
+
unsubscribeMode = modeControl()?.subscribe?.((mode) => writeLine(ui.dim(`⏵⏵ ${t().mode(mode)}`)));
|
|
197
|
+
};
|
|
198
|
+
watchMode();
|
|
191
199
|
// Cached lazily (not fetched until the first "/..." line, and only once — the SDK docs
|
|
192
200
|
// don't promise this list changes mid-session, and re-fetching per line would add a
|
|
193
201
|
// round-trip to the CLI subprocess for every turn): every registered command name/alias,
|
|
@@ -261,6 +269,7 @@ export async function runChatTui(options, tuiOptions = {}) {
|
|
|
261
269
|
queue = createInputQueue({ modeControl: modeControl() });
|
|
262
270
|
run = runQuery(queue.iterable, current.options);
|
|
263
271
|
events = run.events[Symbol.asyncIterator]();
|
|
272
|
+
watchMode();
|
|
264
273
|
knownCommandTokens = null;
|
|
265
274
|
await showConversation(current.run);
|
|
266
275
|
}
|
|
@@ -304,7 +313,11 @@ export async function runChatTui(options, tuiOptions = {}) {
|
|
|
304
313
|
if (line.toLowerCase() === "/plan") {
|
|
305
314
|
const control = modeControl();
|
|
306
315
|
const next = control ? togglePlanMode(control) : null;
|
|
307
|
-
|
|
316
|
+
// The new mode's line comes from watchMode(); a control without subscribe() says it here.
|
|
317
|
+
if (!next)
|
|
318
|
+
writeLine(ui.dim(t().modeLocked(control?.mode)));
|
|
319
|
+
else if (!control?.subscribe)
|
|
320
|
+
writeLine(ui.dim(`⏵⏵ ${t().mode(next)}`));
|
|
308
321
|
continue;
|
|
309
322
|
}
|
|
310
323
|
// A line that *looks* like a slash command but matches nothing registered (a typo,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stdout } from "node:process";
|
|
2
2
|
import { createFriendlyToolLabel } from "../core/toolLabels.js";
|
|
3
|
+
import { parseTodos, todoChanges, TODO_TOOL } from "../core/todos.js";
|
|
3
4
|
import * as ui from "./ui.js";
|
|
4
5
|
import { t } from "../core/messages/index.js";
|
|
5
6
|
/**
|
|
@@ -14,6 +15,8 @@ export function createConsoleRenderer(options = {}) {
|
|
|
14
15
|
const output = options.output ?? ((text) => void stdout.write(text));
|
|
15
16
|
let atLineStart = true;
|
|
16
17
|
let labelPrinted = false;
|
|
18
|
+
// The main agent's task list, to print only what changed in each TodoWrite call.
|
|
19
|
+
let todos = [];
|
|
17
20
|
function write(text) {
|
|
18
21
|
if (text.length === 0)
|
|
19
22
|
return;
|
|
@@ -39,9 +42,20 @@ export function createConsoleRenderer(options = {}) {
|
|
|
39
42
|
}
|
|
40
43
|
write(ui.agent(event.text));
|
|
41
44
|
return;
|
|
42
|
-
case "action":
|
|
43
|
-
|
|
45
|
+
case "action": {
|
|
46
|
+
const next = event.toolName === TODO_TOOL ? parseTodos(event.input) : null;
|
|
47
|
+
if (!next) {
|
|
48
|
+
writeLine(ui.action(`[action] ${formatAction(event.toolName, event.input)}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const { started, completed } = todoChanges(todos, next);
|
|
52
|
+
todos = next;
|
|
53
|
+
for (const todo of completed)
|
|
54
|
+
writeLine(ui.action(`[task] ☑ ${todo.content}`));
|
|
55
|
+
for (const todo of started)
|
|
56
|
+
writeLine(ui.action(`[task] ◼ ${todo.activeForm}`));
|
|
44
57
|
return;
|
|
58
|
+
}
|
|
45
59
|
case "subagent-action":
|
|
46
60
|
case "tool-result":
|
|
47
61
|
case "prompt-suggestion":
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import wrapAnsi from "wrap-ansi";
|
|
3
3
|
import { useEffect, useReducer, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { Box, measureElement, Static, Text, useInput, useStdout } from "ink";
|
|
5
|
-
import { Select } from "@inkjs/ui";
|
|
5
|
+
import { MultiSelect, Select, TextInput } from "@inkjs/ui";
|
|
6
6
|
import { liveWidth } from "./sessionModel.js";
|
|
7
7
|
import { createRowCache, highlightRow, isMouseReport, mouseEvents, rowsBelow, scrollBy, selectedText, visibleRows, userBarRows, } from "./fullscreen.js";
|
|
8
8
|
import { framePosition } from "./terminalCursor.js";
|
|
@@ -11,6 +11,7 @@ import { fitWidth, stripAnsi } from "./lineBuffer.js";
|
|
|
11
11
|
import * as ui from "../ui.js";
|
|
12
12
|
import { t } from "../../core/messages/index.js";
|
|
13
13
|
import { inkColor } from "../theme.js";
|
|
14
|
+
import { renderMarkdown } from "./markdown.js";
|
|
14
15
|
// Numbered as in the Claude Code CLI's permission prompt; the digits and y/n/q answer too.
|
|
15
16
|
const decisionOptions = () => [
|
|
16
17
|
{ label: `1. ${t().yes}`, value: "y" },
|
|
@@ -44,16 +45,42 @@ export function previewLines(lines, terminalRows, reservedRows = PANEL_RESERVED_
|
|
|
44
45
|
}
|
|
45
46
|
function CheckpointPanel({ checkpoint, renderApproval, reservedRows, width, }) {
|
|
46
47
|
const decision = checkpoint.kind === "decision";
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const text = checkpoint.kind === "text";
|
|
49
|
+
const choice = checkpoint.kind === "choice";
|
|
50
|
+
useInput((input, key) => {
|
|
51
|
+
if (text || choice) {
|
|
52
|
+
if (key.escape)
|
|
53
|
+
checkpoint.answer("");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const lower = input.toLowerCase();
|
|
57
|
+
if (decision && DECISION_KEYS[lower])
|
|
58
|
+
checkpoint.answer(DECISION_KEYS[lower]);
|
|
59
|
+
else if (!decision && lower === "1")
|
|
52
60
|
checkpoint.answer("");
|
|
53
61
|
});
|
|
54
62
|
const { prompt } = checkpoint;
|
|
55
63
|
const { stdout } = useStdout();
|
|
56
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: inkColor("accent"), paddingX: 1, children: [renderApproval ? (renderApproval(prompt)) : (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: prompt.title }), previewLines(prompt.lines, stdout.rows, reservedRows, width - PANEL_FRAME_COLUMNS).map((line, index) => (_jsx(Text, { children: line }, index))), !decision && prompt.question ? _jsx(Text, { dimColor: true, children: prompt.question }) : null] })), decision ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: t().proceed }) })) : null, _jsx(Box, { marginTop: decision ? 0 : 1, children: _jsx(Select, { options: decision ? decisionOptions() : manualOptions(), onChange: (value) => checkpoint.answer(decision ? value : "") }, checkpoint.id) })] }));
|
|
64
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: inkColor("accent"), paddingX: 1, children: [renderApproval ? (renderApproval(prompt)) : (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: prompt.title }), previewLines(prompt.markdown ? renderMarkdown(prompt.lines.join("\n"), width - PANEL_FRAME_COLUMNS) : prompt.lines, stdout.rows, reservedRows, width - PANEL_FRAME_COLUMNS).map((line, index) => (_jsx(Text, { children: line }, index))), !decision && prompt.question ? _jsx(Text, { dimColor: true, children: prompt.question }) : null] })), decision ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: t().proceed }) })) : null, choice && checkpoint.choice ? (_jsx(ChoiceInput, { choice: checkpoint.choice, onAnswer: (value) => checkpoint.answer(value) }, checkpoint.id)) : text ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { children: "> " }), _jsx(TextInput, { placeholder: t().textAnswerHint, onSubmit: (value) => checkpoint.answer(value.trim()) }, checkpoint.id)] }), _jsx(Text, { dimColor: true, children: t().textAnswerKeys })] })) : (_jsx(Box, { marginTop: decision ? 0 : 1, children: _jsx(Select, { options: decision ? decisionOptions() : manualOptions(), onChange: (value) => checkpoint.answer(decision ? value : "") }, checkpoint.id) }))] }));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A choice's options (one with Select, several with MultiSelect), plus "Other" to type an
|
|
68
|
+
* answer of one's own; answers as `InteractionPort.askChoice()` says: the numbers, then the
|
|
69
|
+
* typed answer on the next line.
|
|
70
|
+
*/
|
|
71
|
+
function ChoiceInput({ choice, onAnswer }) {
|
|
72
|
+
const [typing, setTyping] = useState(null);
|
|
73
|
+
const options = [...choice.options.map((label, i) => ({ label: `${i + 1}. ${label}`, value: String(i + 1) })), { label: t().otherOption, value: "other" }];
|
|
74
|
+
if (typing !== null) {
|
|
75
|
+
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { children: "> " }), _jsx(TextInput, { placeholder: t().textAnswerHint, onSubmit: (value) => onAnswer([typing, value.trim()].filter(Boolean).join("\n")) })] }), _jsx(Text, { dimColor: true, children: t().textAnswerKeys })] }));
|
|
76
|
+
}
|
|
77
|
+
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [choice.multiple ? (_jsx(MultiSelect, { options: options, onSubmit: (values) => {
|
|
78
|
+
const picked = values.filter((value) => value !== "other").join(",");
|
|
79
|
+
if (values.includes("other"))
|
|
80
|
+
setTyping(picked);
|
|
81
|
+
else
|
|
82
|
+
onAnswer(picked);
|
|
83
|
+
} })) : (_jsx(Select, { options: options, onChange: (value) => (value === "other" ? setTyping("") : onAnswer(value)) })), _jsx(Text, { dimColor: true, children: t().choiceKeys(choice.multiple) })] }));
|
|
57
84
|
}
|
|
58
85
|
function formatTokens(count) {
|
|
59
86
|
return count >= 1000 ? `${(count / 1000).toFixed(1)}k` : String(count);
|
|
@@ -89,7 +116,21 @@ function LiveArea({ session, checkpoint, renderApproval, mode, width, statusExtr
|
|
|
89
116
|
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, marginTop: session.liveGap ? 1 : 0, children: [stripAnsi(session.live) ? _jsx(Text, { children: session.live }) : null, checkpoint ? (_jsx(CheckpointPanel, { checkpoint: checkpoint, renderApproval: renderApproval, reservedRows: reservedRows, width: width })) : session.busy ? (
|
|
90
117
|
// Always one blank line above the spinner: from this margin, or from the live area's
|
|
91
118
|
// own when a separator is pending and nothing is written above the spinner yet.
|
|
92
|
-
_jsxs(Box, { flexDirection: "column", marginTop: stripAnsi(session.live) || !session.liveGap ? 1 : 0, children: [_jsx(Working, { label: session.activity ?? t().thinking, startedAt: session.turnStartedAt, width: width }), session.subagentActivity ? _jsx(Text, { dimColor: true, children: fitWidth(` ↳ ${session.subagentActivity}`, width) }) : null] })) : null, checkpoint ? null : children, _jsx(Text, { children: fitWidth(status, width) })] }));
|
|
119
|
+
_jsxs(Box, { flexDirection: "column", marginTop: stripAnsi(session.live) || !session.liveGap ? 1 : 0, children: [_jsx(Working, { label: session.activity ?? t().thinking, startedAt: session.turnStartedAt, width: width }), session.subagentActivity ? _jsx(Text, { dimColor: true, children: fitWidth(` ↳ ${session.subagentActivity}`, width) }) : null] })) : null, checkpoint || !session.todos ? null : _jsx(TodoList, { todos: session.todos, width: width }), checkpoint ? null : children, _jsx(Text, { children: fitWidth(status, width) })] }));
|
|
120
|
+
}
|
|
121
|
+
// At most this many tasks show; the rest are counted (the one in progress always shows).
|
|
122
|
+
const MAX_TODOS = 8;
|
|
123
|
+
/** The agent's task list (TodoWrite), as in the Claude Code CLI: `☐` pending, `◼` in progress, `☑` done. */
|
|
124
|
+
function TodoList({ todos, width }) {
|
|
125
|
+
const current = todos.findIndex((todo) => todo.status === "in_progress");
|
|
126
|
+
const first = Math.max(0, Math.min(current - 2, todos.length - MAX_TODOS));
|
|
127
|
+
const shown = todos.slice(first, first + MAX_TODOS);
|
|
128
|
+
const hidden = todos.length - shown.length;
|
|
129
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [shown.map((todo, i) => {
|
|
130
|
+
const line = ` ${i === 0 ? "⎿ " : " "}${todo.status === "completed" ? "☑" : todo.status === "in_progress" ? "◼" : "☐"} ${todo.content}`;
|
|
131
|
+
const fitted = fitWidth(line, width);
|
|
132
|
+
return (_jsx(Text, { children: todo.status === "completed" ? ui.dim(ui.strike(fitted)) : todo.status === "in_progress" ? ui.bold(fitted) : fitted }, first + i));
|
|
133
|
+
}), hidden > 0 ? _jsx(Text, { children: ui.dim(fitWidth(` ${t().moreTodos(hidden)}`, width)) }) : null] }));
|
|
93
134
|
}
|
|
94
135
|
/** A history line for the inline view: the human's line as a bar, a tool group unfolded with Ctrl+O. */
|
|
95
136
|
function inlineItem(item, expanded, width) {
|
|
@@ -133,6 +174,16 @@ function FullscreenSession({ session, checkpoint, renderApproval, mode, header,
|
|
|
133
174
|
if (height !== historyHeight)
|
|
134
175
|
setHistoryHeight(height);
|
|
135
176
|
});
|
|
177
|
+
// The history's height now: `historyHeight` is set by the effect above after the layout,
|
|
178
|
+
// so a key or a click on the first frames would still find the terminal's height.
|
|
179
|
+
function heightNow() {
|
|
180
|
+
if (!historyRef.current)
|
|
181
|
+
return historyHeight;
|
|
182
|
+
const { height } = measureElement(historyRef.current);
|
|
183
|
+
if (height !== historyHeight)
|
|
184
|
+
setHistoryHeight(height);
|
|
185
|
+
return height;
|
|
186
|
+
}
|
|
136
187
|
const [anchor, setAnchor] = useState(null);
|
|
137
188
|
// Mirrored in a ref: mouse reports can arrive faster than React re-renders.
|
|
138
189
|
const [selection, setSelectionState] = useState(null);
|
|
@@ -161,9 +212,7 @@ function FullscreenSession({ session, checkpoint, renderApproval, mode, header,
|
|
|
161
212
|
if (!historyRef.current || end === start)
|
|
162
213
|
return null;
|
|
163
214
|
const box = framePosition(historyRef.current);
|
|
164
|
-
|
|
165
|
-
// a click on the first frames would find the rows where they were before it.
|
|
166
|
-
const height = measureElement(historyRef.current).height;
|
|
215
|
+
const height = heightNow();
|
|
167
216
|
const firstY = box.y + height - (end - start); // the rows are bottom-aligned
|
|
168
217
|
let offset = y - firstY;
|
|
169
218
|
// Rows that don't fit are clipped above the box, not clickable.
|
|
@@ -179,7 +228,8 @@ function FullscreenSession({ session, checkpoint, renderApproval, mode, header,
|
|
|
179
228
|
for (const event of mouseEvents(text)) {
|
|
180
229
|
if (event.kind === "wheel-up" || event.kind === "wheel-down") {
|
|
181
230
|
const steps = event.kind === "wheel-up" ? -WHEEL_ROWS : WHEEL_ROWS;
|
|
182
|
-
|
|
231
|
+
const height = heightNow();
|
|
232
|
+
setAnchor((a) => scrollBy(a, steps, total, height));
|
|
183
233
|
}
|
|
184
234
|
else if (event.kind === "press") {
|
|
185
235
|
const cell = cellAt(event.x, event.y, false);
|
|
@@ -210,15 +260,15 @@ function FullscreenSession({ session, checkpoint, renderApproval, mode, header,
|
|
|
210
260
|
}
|
|
211
261
|
}
|
|
212
262
|
useInput((text, key) => {
|
|
213
|
-
const page = Math.max(1, historyHeight - 1);
|
|
214
263
|
if (isMouseReport(text))
|
|
215
264
|
handleMouse(text);
|
|
216
265
|
else if (isFocusReport(text))
|
|
217
266
|
return;
|
|
218
|
-
else if (key.pageUp)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
setAnchor((a) => scrollBy(a, page, total,
|
|
267
|
+
else if (key.pageUp || key.pageDown) {
|
|
268
|
+
const height = heightNow();
|
|
269
|
+
const page = Math.max(1, height - 1);
|
|
270
|
+
setAnchor((a) => scrollBy(a, key.pageUp ? -page : page, total, height));
|
|
271
|
+
}
|
|
222
272
|
else if (key.ctrl && key.end)
|
|
223
273
|
setAnchor(null);
|
|
224
274
|
else if (text && !key.ctrl && !key.meta && !key.escape)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ApprovalPrompt, InteractionPort } from "../../core/interaction.js";
|
|
2
|
-
export type CheckpointKind = "decision" | "manual-intervention";
|
|
1
|
+
import type { ApprovalPrompt, ChoiceSettings, InteractionPort } from "../../core/interaction.js";
|
|
2
|
+
export type CheckpointKind = "decision" | "manual-intervention" | "text" | "choice";
|
|
3
3
|
export interface Checkpoint {
|
|
4
4
|
id: number;
|
|
5
5
|
kind: CheckpointKind;
|
|
6
6
|
prompt: ApprovalPrompt;
|
|
7
|
+
/** For a choice: the options and whether several can be picked. */
|
|
8
|
+
choice?: ChoiceSettings;
|
|
7
9
|
/** Settles the checkpoint with the raw answer ("y", "n", "q" for a decision). */
|
|
8
10
|
answer(value: string): void;
|
|
9
11
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { parseChoice } from "../../core/hooks/humanInput.js";
|
|
1
2
|
import * as ui from "../ui.js";
|
|
3
|
+
import { renderMarkdown } from "./markdown.js";
|
|
2
4
|
import { t } from "../../core/messages/index.js";
|
|
3
5
|
const outcome = (answer) => ({ y: ui.success(t().approved), n: ui.warn(t().rejected), q: ui.error(t().stopped) })[answer];
|
|
4
6
|
/**
|
|
@@ -25,16 +27,30 @@ export function createInkInteraction(note) {
|
|
|
25
27
|
function record(prompt, outcome) {
|
|
26
28
|
note([ui.heading(`=== ${prompt.title} ===`), ...prompt.lines, outcome].join("\n"));
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
/** How a settled checkpoint is written to the history. */
|
|
31
|
+
function settledAs(kind, value, choice) {
|
|
32
|
+
if (kind === "decision")
|
|
33
|
+
return outcome(value) ?? value;
|
|
34
|
+
if (kind === "text")
|
|
35
|
+
return value ? ui.success(`✔ ${value}`) : ui.dim(t().noAnswer);
|
|
36
|
+
if (kind === "choice" && choice) {
|
|
37
|
+
const answer = parseChoice(value, choice.options, choice.multiple);
|
|
38
|
+
const words = [...answer.chosen, ...(answer.other ? [answer.other] : [])];
|
|
39
|
+
return words.length ? ui.success(`✔ ${words.join("; ")}`) : ui.dim(t().noAnswer);
|
|
40
|
+
}
|
|
41
|
+
return ui.success(t().done);
|
|
42
|
+
}
|
|
43
|
+
function ask(kind, prompt, signal, choice) {
|
|
29
44
|
return new Promise((resolve) => {
|
|
30
45
|
const checkpoint = {
|
|
31
46
|
id: nextId++,
|
|
32
47
|
kind,
|
|
33
48
|
prompt,
|
|
49
|
+
...(choice ? { choice } : {}),
|
|
34
50
|
answer(value) {
|
|
35
51
|
if (!remove(checkpoint))
|
|
36
52
|
return;
|
|
37
|
-
record(prompt
|
|
53
|
+
record(prompt.markdown ? { ...prompt, lines: renderMarkdown(prompt.lines.join("\n"), 80) } : prompt, settledAs(kind, value, choice));
|
|
38
54
|
resolve(value);
|
|
39
55
|
},
|
|
40
56
|
};
|
|
@@ -50,6 +66,8 @@ export function createInkInteraction(note) {
|
|
|
50
66
|
port: {
|
|
51
67
|
askDecision: (prompt, signal) => ask("decision", prompt, signal),
|
|
52
68
|
askManualIntervention: (prompt, signal) => ask("manual-intervention", prompt, signal),
|
|
69
|
+
askText: (prompt, signal) => ask("text", prompt, signal),
|
|
70
|
+
askChoice: (prompt, choice, signal) => ask("choice", prompt, signal, choice),
|
|
53
71
|
notify: (message) => note(message),
|
|
54
72
|
},
|
|
55
73
|
subscribe(listener) {
|
|
@@ -244,12 +244,17 @@ export async function runChatInk(options, tuiOptions = {}) {
|
|
|
244
244
|
let run;
|
|
245
245
|
let knownCommands;
|
|
246
246
|
let generation = 0;
|
|
247
|
+
let unsubscribeMode;
|
|
247
248
|
function connect() {
|
|
248
249
|
const own = ++generation;
|
|
249
250
|
queue = createInputQueue({ modeControl: modeControl() });
|
|
250
251
|
run = runQuery(queue.iterable, { ...current.options, promptSuggestions: tuiOptions.promptSuggestions ?? true });
|
|
251
252
|
const events = run.events[Symbol.asyncIterator]();
|
|
252
253
|
input.setMode(modeControl()?.mode ?? null);
|
|
254
|
+
// The mode can also change from inside the session (the agent leaving plan mode with
|
|
255
|
+
// present_plan): the status bar follows it.
|
|
256
|
+
unsubscribeMode?.();
|
|
257
|
+
unsubscribeMode = modeControl()?.subscribe?.((mode) => input.setMode(mode));
|
|
253
258
|
// Every registered command name and alias, built-ins included, for completion and for
|
|
254
259
|
// catching an unknown "/command" before it reaches the model as plain text (see chatTui.ts).
|
|
255
260
|
knownCommands = run.supportedCommands().then((commands) => new Set(commands.flatMap((c) => [c.name, ...(c.aliases ?? [])])), () => null);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentEvent, SessionUsage } from "../../core/runner.js";
|
|
2
2
|
import { type ConsoleRenderer } from "../consoleRenderer.js";
|
|
3
3
|
import { type ResultFormatter, type ToolDetail, type ToolPhrase } from "./toolGroup.js";
|
|
4
|
+
import { type Todo } from "../../core/todos.js";
|
|
4
5
|
export interface HistoryItem {
|
|
5
6
|
id: number;
|
|
6
7
|
text: string;
|
|
@@ -36,6 +37,8 @@ export interface SessionSnapshot {
|
|
|
36
37
|
usage: SessionUsage | null;
|
|
37
38
|
/** How full the context window is (0-100), after the latest turn; null until known. */
|
|
38
39
|
contextPercent: number | null;
|
|
40
|
+
/** The main agent's task list (TodoWrite), while any task is still to do; null otherwise. */
|
|
41
|
+
todos: Todo[] | null;
|
|
39
42
|
}
|
|
40
43
|
export interface SessionModelOptions {
|
|
41
44
|
formatAction?: (toolName: string, toolInput: unknown) => string;
|
|
@@ -5,6 +5,7 @@ import { finishedLength, hasTable, renderMarkdown } from "./markdown.js";
|
|
|
5
5
|
import { toolGroupExpanded, toolGroupSummary } from "./toolGroup.js";
|
|
6
6
|
import * as ui from "../ui.js";
|
|
7
7
|
import { t } from "../../core/messages/index.js";
|
|
8
|
+
import { hasOpenTodos, parseTodos, TODO_TOOL } from "../../core/todos.js";
|
|
8
9
|
/**
|
|
9
10
|
* Columns the live area may use: short of the edge, where the Windows console wraps early
|
|
10
11
|
* (see lineBuffer.ts). Read on every use, so a resized terminal is picked up.
|
|
@@ -45,6 +46,7 @@ export function createSessionModel(options = {}) {
|
|
|
45
46
|
turns: 0,
|
|
46
47
|
usage: null,
|
|
47
48
|
contextPercent: null,
|
|
49
|
+
todos: null,
|
|
48
50
|
};
|
|
49
51
|
function update(changes) {
|
|
50
52
|
snapshot = { ...snapshot, ...changes };
|
|
@@ -190,6 +192,15 @@ export function createSessionModel(options = {}) {
|
|
|
190
192
|
return;
|
|
191
193
|
}
|
|
192
194
|
case "action": {
|
|
195
|
+
// The task list isn't a tool call to show: it's drawn above the prompt, and the task
|
|
196
|
+
// in progress becomes the spinner's label.
|
|
197
|
+
const todos = event.toolName === TODO_TOOL ? parseTodos(event.input) : null;
|
|
198
|
+
if (todos) {
|
|
199
|
+
const current = todos.find((todo) => todo.status === "in_progress");
|
|
200
|
+
snapshot = { ...snapshot, todos: hasOpenTodos(todos) ? todos : null, activity: current?.activeForm ?? snapshot.activity };
|
|
201
|
+
refreshLive();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
193
204
|
flushSegment(true);
|
|
194
205
|
group ??= [];
|
|
195
206
|
group.push({ id: event.toolUseId, label: formatAction(event.toolName, event.input), toolName: event.toolName, result: null });
|
|
@@ -198,6 +209,8 @@ export function createSessionModel(options = {}) {
|
|
|
198
209
|
return;
|
|
199
210
|
}
|
|
200
211
|
case "tool-result": {
|
|
212
|
+
if (event.toolName === TODO_TOOL)
|
|
213
|
+
return;
|
|
201
214
|
const call = group?.find((c) => c.id === event.toolUseId);
|
|
202
215
|
if (call)
|
|
203
216
|
call.result = { isError: event.isError, text: event.text };
|
|
@@ -282,7 +295,7 @@ export function createSessionModel(options = {}) {
|
|
|
282
295
|
lastKind = null;
|
|
283
296
|
gapPending = false;
|
|
284
297
|
reply = "";
|
|
285
|
-
snapshot = { ...snapshot, items: [], live: "", liveGap: false, turns: 0, usage: null, contextPercent: null };
|
|
298
|
+
snapshot = { ...snapshot, items: [], live: "", liveGap: false, turns: 0, usage: null, contextPercent: null, todos: null };
|
|
286
299
|
refreshLive();
|
|
287
300
|
},
|
|
288
301
|
replay(messages, userLine) {
|
|
@@ -80,6 +80,8 @@ async function askOnTerminal(prompt, signal) {
|
|
|
80
80
|
export const terminalInteractionPort = {
|
|
81
81
|
askDecision: askOnTerminal,
|
|
82
82
|
askManualIntervention: askOnTerminal,
|
|
83
|
+
askText: askOnTerminal,
|
|
84
|
+
askChoice: (prompt, choice, signal) => askOnTerminal({ ...prompt, lines: [...prompt.lines, ...choice.options.map((option, i) => `${i + 1}. ${option}`)], question: t().choiceQuestion(choice.multiple) }, signal),
|
|
83
85
|
notify(message) {
|
|
84
86
|
if (stdin.isTTY)
|
|
85
87
|
console.log(message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falkenslab/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
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",
|
|
@@ -62,11 +62,17 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "^10.0.1",
|
|
65
|
+
"@mozilla/readability": "^0.6.0",
|
|
65
66
|
"@types/node": "^24.0.0",
|
|
67
|
+
"@types/turndown": "^5.0.6",
|
|
66
68
|
"eslint": "^10.9.1",
|
|
69
|
+
"fflate": "^0.8.3",
|
|
67
70
|
"globals": "^17.11.0",
|
|
68
71
|
"ink-testing-library": "^4.0.0",
|
|
72
|
+
"linkedom": "^0.18.13",
|
|
73
|
+
"mammoth": "^1.13.0",
|
|
69
74
|
"tsx": "^4.19.0",
|
|
75
|
+
"turndown": "^7.2.4",
|
|
70
76
|
"typescript": "^5.7.0",
|
|
71
77
|
"typescript-eslint": "^8.68.0"
|
|
72
78
|
},
|
|
@@ -75,5 +81,29 @@
|
|
|
75
81
|
},
|
|
76
82
|
"publishConfig": {
|
|
77
83
|
"access": "public"
|
|
84
|
+
},
|
|
85
|
+
"peerDependencies": {
|
|
86
|
+
"@mozilla/readability": ">=0.5.0",
|
|
87
|
+
"linkedom": ">=0.16.0",
|
|
88
|
+
"turndown": ">=7.0.0",
|
|
89
|
+
"mammoth": ">=1.6.0",
|
|
90
|
+
"fflate": ">=0.8.0"
|
|
91
|
+
},
|
|
92
|
+
"peerDependenciesMeta": {
|
|
93
|
+
"@mozilla/readability": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"linkedom": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
99
|
+
"turndown": {
|
|
100
|
+
"optional": true
|
|
101
|
+
},
|
|
102
|
+
"mammoth": {
|
|
103
|
+
"optional": true
|
|
104
|
+
},
|
|
105
|
+
"fflate": {
|
|
106
|
+
"optional": true
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
109
|
}
|