@gavin-hu/my-pi-agent 0.14.0 → 0.15.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/README.md +55 -33
- package/README.zh-CN.md +44 -27
- package/extensions/bytefly/README.md +193 -140
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +3 -2
- package/extensions/bytefly/index.ts +3 -3
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +6 -11
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +21 -1
- package/extensions/bytefly/panel/format.ts +5 -2
- package/extensions/bytefly/panel/settings-runtime.ts +26 -6
- package/extensions/bytefly/panel/settings.ts +41 -4
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
- package/extensions/bytefly/panel/tui.ts +94 -152
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +14 -4
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +1 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +94 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +34 -71
- package/extensions/plan/web.ts +13 -40
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +185 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +74 -7
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/subagent/README.md +61 -6
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +4 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +91 -2
- package/extensions/subagent/render.ts +13 -0
- package/extensions/subagent/run.ts +17 -16
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/types.ts +11 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +14 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +1 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +50 -3
- package/lib/errors.ts +12 -0
- package/lib/format.ts +50 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +91 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +410 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +22 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -25,6 +25,9 @@ import {
|
|
|
25
25
|
formatAgentList,
|
|
26
26
|
formatAgentNames,
|
|
27
27
|
} from "./agents.ts";
|
|
28
|
+
import { createBackgroundRuntime } from "./background.ts";
|
|
29
|
+
import { registerAgentsCommand } from "./commands.ts";
|
|
30
|
+
import { createSubagentUi } from "./ui.ts";
|
|
28
31
|
import { loadSubagentConfig, type SubagentConfig } from "./config.ts";
|
|
29
32
|
import { runChainMode, runParallelMode, runSingleMode, type ModeContext } from "./orchestrate.ts";
|
|
30
33
|
import { renderSubagentCall, renderSubagentResult } from "./render.ts";
|
|
@@ -48,6 +51,7 @@ const DESCRIPTION = [
|
|
|
48
51
|
"Delegate tasks to specialized subagents with isolated context windows.",
|
|
49
52
|
"Modes: single (`agent` + `task`), parallel (`tasks`), chain (sequential, `{previous}` placeholder).",
|
|
50
53
|
"Each runs in its own process, inheriting the current model and thinking level.",
|
|
54
|
+
"A single run can start in the background (`background: true`) and is managed with `/agents`.",
|
|
51
55
|
`Built-in agents: ${AGENT_NAMES}.`,
|
|
52
56
|
].join(" ");
|
|
53
57
|
|
|
@@ -55,6 +59,7 @@ const GUIDELINES = [
|
|
|
55
59
|
"Use `subagent` for broad codebase exploration, parallel research, or isolated implementation work that would bloat the main context.",
|
|
56
60
|
"Prefer `explorer` first when another agent would otherwise re-read many files, then pass its output to `planner` or `worker`.",
|
|
57
61
|
"Run independent tasks with `tasks` (max 8, up to 4 at once); use `chain` only when each step needs the previous step's output.",
|
|
62
|
+
"Use `background: true` only for one long task you will collect from `/agents`.",
|
|
58
63
|
"Project-local agents are repository-controlled and load only when `agentScope` includes them; an untrusted project requires confirmation.",
|
|
59
64
|
];
|
|
60
65
|
|
|
@@ -73,6 +78,7 @@ export default function subagent(pi: ExtensionAPI, deps: SubagentDeps = {}): voi
|
|
|
73
78
|
// Read once per session; a call that arrives before `session_start` reads the
|
|
74
79
|
// same files lazily, so the two paths cannot disagree.
|
|
75
80
|
let settings: SubagentConfig | undefined;
|
|
81
|
+
let activeCtx: ExtensionContext | undefined;
|
|
76
82
|
|
|
77
83
|
const startSession = async (ctx: ExtensionContext): Promise<void> => {
|
|
78
84
|
settings = loadSubagentConfig(ctx.cwd);
|
|
@@ -84,8 +90,6 @@ export default function subagent(pi: ExtensionAPI, deps: SubagentDeps = {}): voi
|
|
|
84
90
|
}
|
|
85
91
|
};
|
|
86
92
|
|
|
87
|
-
pi.on("session_start", (_event, ctx) => startSession(ctx));
|
|
88
|
-
|
|
89
93
|
/**
|
|
90
94
|
* Account for each finished child, then hand the result on unchanged.
|
|
91
95
|
*
|
|
@@ -106,6 +110,29 @@ export default function subagent(pi: ExtensionAPI, deps: SubagentDeps = {}): voi
|
|
|
106
110
|
return result;
|
|
107
111
|
};
|
|
108
112
|
|
|
113
|
+
const background = createBackgroundRuntime({
|
|
114
|
+
appendEntry: (customType, data) => pi.appendEntry(customType, data),
|
|
115
|
+
onFinish: (run) => ui.notifyFinished(run),
|
|
116
|
+
});
|
|
117
|
+
const ui = createSubagentUi({
|
|
118
|
+
runs: () => background.list(activeCtx?.sessionManager.getBranch() ?? []),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
122
|
+
background.reset();
|
|
123
|
+
activeCtx = ctx;
|
|
124
|
+
ui.attach(ctx);
|
|
125
|
+
background.subscribe(() => ui.paint());
|
|
126
|
+
ui.paint();
|
|
127
|
+
await startSession(ctx);
|
|
128
|
+
});
|
|
129
|
+
pi.on("session_shutdown", () => {
|
|
130
|
+
background.shutdown();
|
|
131
|
+
ui.detach();
|
|
132
|
+
activeCtx = undefined;
|
|
133
|
+
});
|
|
134
|
+
registerAgentsCommand(pi, background);
|
|
135
|
+
|
|
109
136
|
pi.registerTool({
|
|
110
137
|
name: TOOL_NAME,
|
|
111
138
|
label: "Subagent",
|
|
@@ -195,6 +222,68 @@ export default function subagent(pi: ExtensionAPI, deps: SubagentDeps = {}): voi
|
|
|
195
222
|
}
|
|
196
223
|
}
|
|
197
224
|
|
|
225
|
+
if (args.background) {
|
|
226
|
+
if (mode !== "single") {
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text" as const, text: "`background` supports single mode only." }],
|
|
229
|
+
details: makeDetails([]),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (!agents.some((candidate) => candidate.name === args.agent)) {
|
|
233
|
+
return {
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: "text" as const,
|
|
237
|
+
text: `Unknown agent: "${args.agent ?? ""}". Available agents: ${formatAgentList(agents)}`,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
details: makeDetails([]),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const started = background.start(
|
|
244
|
+
{
|
|
245
|
+
agent: args.agent ?? "",
|
|
246
|
+
task: args.task ?? "",
|
|
247
|
+
cwd: args.cwd,
|
|
248
|
+
readOnly: args.readOnly,
|
|
249
|
+
},
|
|
250
|
+
ctx.sessionManager.getBranch(),
|
|
251
|
+
{
|
|
252
|
+
run,
|
|
253
|
+
agents,
|
|
254
|
+
defaults,
|
|
255
|
+
defaultCwd: root,
|
|
256
|
+
timeoutMs: config.timeoutMs,
|
|
257
|
+
transcripts,
|
|
258
|
+
backgroundMax: config.backgroundMax,
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
if (!started.ok) {
|
|
262
|
+
return {
|
|
263
|
+
content: [{ type: "text" as const, text: started.error }],
|
|
264
|
+
details: makeDetails([]),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text" as const,
|
|
271
|
+
text: `Started ${started.run.agent} as ${started.run.id} in the background. Use /agents to inspect, collect, or cancel it.`,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
details: {
|
|
275
|
+
...makeDetails([]),
|
|
276
|
+
background: {
|
|
277
|
+
runId: started.run.id,
|
|
278
|
+
agent: started.run.agent,
|
|
279
|
+
task: started.run.task,
|
|
280
|
+
cwd: started.run.cwd,
|
|
281
|
+
startedAt: started.run.startedAt,
|
|
282
|
+
},
|
|
283
|
+
} satisfies SubagentDetails,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
198
287
|
const context: ModeContext = {
|
|
199
288
|
run,
|
|
200
289
|
args,
|
|
@@ -488,6 +488,19 @@ export function renderSubagentResult(
|
|
|
488
488
|
): Component {
|
|
489
489
|
const now = options.now ?? Date.now();
|
|
490
490
|
const details = toolDetails<SubagentDetails>(result.details);
|
|
491
|
+
if (details?.background) {
|
|
492
|
+
syncElapsedTimer(false, context);
|
|
493
|
+
const text = reuseText(context?.lastComponent);
|
|
494
|
+
const background = details.background;
|
|
495
|
+
text.setText(
|
|
496
|
+
paint(
|
|
497
|
+
theme,
|
|
498
|
+
"meta",
|
|
499
|
+
`Started ${sanitizeLine(background.agent)} as ${sanitizeLine(background.runId)} in the background. Use /agents to collect or cancel it.`,
|
|
500
|
+
),
|
|
501
|
+
);
|
|
502
|
+
return text;
|
|
503
|
+
}
|
|
491
504
|
if (!details || details.results.length === 0) {
|
|
492
505
|
syncElapsedTimer(false, context);
|
|
493
506
|
const text = reuseText(context?.lastComponent);
|
|
@@ -13,6 +13,7 @@ import * as os from "node:os";
|
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { formatDuration } from "../../lib/format.ts";
|
|
16
|
+
import { LineSplitter } from "../../lib/lines.ts";
|
|
16
17
|
import { BUILTIN_AGENTS, formatAgentList, type AgentConfig } from "./agents.ts";
|
|
17
18
|
import type { TranscriptMode } from "./config.ts";
|
|
18
19
|
import { buildAgentArgs, getPiInvocation, READ_ONLY_NOTE } from "./invocation.ts";
|
|
@@ -44,6 +45,8 @@ export interface RunOptions {
|
|
|
44
45
|
spawn?: SpawnFn;
|
|
45
46
|
/** Per-run wall-clock limit in ms; `0` or absent disables it. */
|
|
46
47
|
timeoutMs?: number;
|
|
48
|
+
/** Return an aborted result instead of throwing (used by background runs). */
|
|
49
|
+
abortAsResult?: boolean;
|
|
47
50
|
/** Where and when a finished run writes a transcript. */
|
|
48
51
|
transcripts?: { dir: string; mode: Exclude<TranscriptMode, "off"> };
|
|
49
52
|
}
|
|
@@ -88,7 +91,7 @@ function spawnAndCollect(context: SpawnContext): Promise<SpawnOutcome> {
|
|
|
88
91
|
const { command, args, cwd, timeoutMs, signal, result, onEvent, transcript, spawn } = context;
|
|
89
92
|
return new Promise<SpawnOutcome>((resolve) => {
|
|
90
93
|
const proc = spawn(command, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
91
|
-
|
|
94
|
+
const splitter = new LineSplitter();
|
|
92
95
|
let settled = false;
|
|
93
96
|
let abortRequested = false;
|
|
94
97
|
let timedOut = false;
|
|
@@ -117,10 +120,8 @@ function spawnAndCollect(context: SpawnContext): Promise<SpawnOutcome> {
|
|
|
117
120
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
118
121
|
if (killTimer) clearTimeout(killTimer);
|
|
119
122
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const event = parseJsonLine(buffer);
|
|
123
|
-
if (event && applyEvent(result, event)) onEvent();
|
|
123
|
+
for (const line of splitter.flush()) {
|
|
124
|
+
if (line.trim()) processLine(line);
|
|
124
125
|
}
|
|
125
126
|
resolve({ exitCode, aborted: abortRequested, timedOut });
|
|
126
127
|
};
|
|
@@ -132,15 +133,8 @@ function spawnAndCollect(context: SpawnContext): Promise<SpawnOutcome> {
|
|
|
132
133
|
};
|
|
133
134
|
|
|
134
135
|
proc.stdout?.on("data", (chunk) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Keep the final partial line in the buffer for the close handler.
|
|
138
|
-
let newline = buffer.indexOf("\n");
|
|
139
|
-
while (newline !== -1) {
|
|
140
|
-
processLine(buffer.slice(0, newline));
|
|
141
|
-
buffer = buffer.slice(newline + 1);
|
|
142
|
-
newline = buffer.indexOf("\n");
|
|
143
|
-
}
|
|
136
|
+
// The splitter holds the final partial line for the close handler.
|
|
137
|
+
for (const line of splitter.push(chunk)) processLine(line);
|
|
144
138
|
});
|
|
145
139
|
|
|
146
140
|
proc.stderr?.on("data", (chunk) => {
|
|
@@ -236,12 +230,19 @@ export async function runSingleAgent(options: RunOptions): Promise<SingleResult>
|
|
|
236
230
|
spawn,
|
|
237
231
|
});
|
|
238
232
|
|
|
239
|
-
result.exitCode = outcome.timedOut ? 1 : outcome.exitCode;
|
|
233
|
+
result.exitCode = outcome.timedOut || outcome.aborted ? 1 : outcome.exitCode;
|
|
240
234
|
result.finishedAt = Date.now();
|
|
241
235
|
// A deadline fails the run rather than rejecting it: a chain then stops at
|
|
242
236
|
// this step and reports why, instead of discarding the steps already done.
|
|
243
237
|
if (outcome.timedOut) result.errorMessage = `Timed out after ${formatDuration(timeoutMs)}`;
|
|
244
|
-
if (outcome.aborted)
|
|
238
|
+
if (outcome.aborted) {
|
|
239
|
+
if (options.abortAsResult) {
|
|
240
|
+
result.stopReason = "aborted";
|
|
241
|
+
result.errorMessage = "Aborted";
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
throw new Error("Subagent was aborted");
|
|
245
|
+
}
|
|
245
246
|
return result;
|
|
246
247
|
} finally {
|
|
247
248
|
// Flushed here, not after the call: the abort path throws past every caller.
|
|
@@ -66,6 +66,11 @@ export const SubagentParams = Type.Object({
|
|
|
66
66
|
description: "Force a read-only tool set (readers only). Plan mode sets this.",
|
|
67
67
|
}),
|
|
68
68
|
),
|
|
69
|
+
background: Type.Optional(
|
|
70
|
+
Type.Boolean({
|
|
71
|
+
description: "Start a single run and return immediately; inspect and collect it with /agents.",
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
69
74
|
});
|
|
70
75
|
|
|
71
76
|
export type SubagentArgs = Static<typeof SubagentParams>;
|
|
@@ -85,6 +90,13 @@ export function resolveMode(params: SubagentArgs): ModeResolution {
|
|
|
85
90
|
const hasTasks = taskCount > 0;
|
|
86
91
|
const hasSingle = Boolean(params.agent && params.task);
|
|
87
92
|
|
|
93
|
+
if (params.background === true && (hasChain || hasTasks)) {
|
|
94
|
+
return { error: "`background` supports single mode only; provide `agent` and `task`." };
|
|
95
|
+
}
|
|
96
|
+
if (params.background === true && !hasSingle) {
|
|
97
|
+
return { error: "`background` requires `agent` and `task`." };
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
if (hasChain && hasTasks) return { error: "Provide either `chain` or `tasks`, not both." };
|
|
89
101
|
if (hasChain && hasSingle) return { error: "Provide either `chain` or `agent`+`task`, not both." };
|
|
90
102
|
if (hasTasks && hasSingle) return { error: "Provide either `tasks` or `agent`+`task`, not both." };
|
|
@@ -64,6 +64,15 @@ export interface SingleResult {
|
|
|
64
64
|
artifactError?: string;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/** A background run the tool just started. */
|
|
68
|
+
export interface BackgroundStart {
|
|
69
|
+
runId: string;
|
|
70
|
+
agent: string;
|
|
71
|
+
task: string;
|
|
72
|
+
cwd: string;
|
|
73
|
+
startedAt: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
/** Structured result carried on the `subagent` tool result for rendering. */
|
|
68
77
|
export interface SubagentDetails {
|
|
69
78
|
mode: SubagentMode;
|
|
@@ -74,6 +83,8 @@ export interface SubagentDetails {
|
|
|
74
83
|
projectAgentsDir?: string | null;
|
|
75
84
|
/** Whether the run forced a read-only tool set (plan mode). */
|
|
76
85
|
readOnly?: boolean;
|
|
86
|
+
/** Present when the tool started a background run instead of waiting. */
|
|
87
|
+
background?: BackgroundStart;
|
|
77
88
|
/**
|
|
78
89
|
* Total steps/tasks requested, which can exceed `results.length` when a chain
|
|
79
90
|
* stops early. Defaults to `results.length` when absent.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer status and finish notices for background subagent runs.
|
|
3
|
+
*
|
|
4
|
+
* The runtime owns lifecycle and results; this owns the running-count chip and
|
|
5
|
+
* the notice that points back at `/agents`. A cancelled run stays silent because
|
|
6
|
+
* the user initiated it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { sanitizeLine } from "../../lib/format.ts";
|
|
10
|
+
import { GLYPHS, STATUS_KEYS } from "../../lib/tui/glyphs.ts";
|
|
11
|
+
import { createStatusController, type StatusController } from "../../lib/tui/status-controller.ts";
|
|
12
|
+
import type { BackgroundRun } from "./background.ts";
|
|
13
|
+
|
|
14
|
+
export interface SubagentUiOptions {
|
|
15
|
+
/** The current runs, read fresh on every paint. */
|
|
16
|
+
runs(): readonly BackgroundRun[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SubagentUiController extends StatusController {
|
|
20
|
+
notifyFinished(run: BackgroundRun): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A status word a notice can read without exposing the type's spelling. */
|
|
24
|
+
function statusText(status: BackgroundRun["status"]): string {
|
|
25
|
+
return status === "timedout" ? "timed out" : status;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createSubagentUi(options: SubagentUiOptions): SubagentUiController {
|
|
29
|
+
const controller = createStatusController({
|
|
30
|
+
keys: [STATUS_KEYS.agents],
|
|
31
|
+
enabled: () => true,
|
|
32
|
+
chips: () => {
|
|
33
|
+
const running = options.runs().filter((run) => run.status === "running").length;
|
|
34
|
+
return [{ key: STATUS_KEYS.agents, glyph: GLYPHS.delegation, count: running, role: "accent" }];
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const notifyFinished = (run: BackgroundRun): void => {
|
|
39
|
+
// A cancelled run stays silent because the user initiated it.
|
|
40
|
+
if (run.status === "cancelled") return;
|
|
41
|
+
const failed = run.status === "failed" || run.status === "timedout";
|
|
42
|
+
const action = run.status === "finished" ? "collect" : "inspect";
|
|
43
|
+
controller.notify(
|
|
44
|
+
`Background subagent ${run.id} (${sanitizeLine(run.agent)}) ${statusText(run.status)}. Use /agents to ${action} it.`,
|
|
45
|
+
failed ? "error" : "info",
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return { ...controller, notifyFinished };
|
|
50
|
+
}
|
|
@@ -174,13 +174,17 @@ depends on config.
|
|
|
174
174
|
- **Whole-list replacement, not mutations.** Every call is a snapshot, so
|
|
175
175
|
`todos: []` is the natural clear and there is no "forgot to toggle" failure
|
|
176
176
|
mode. No ids or per-item update actions exist.
|
|
177
|
-
- **State in tool-result `details`.** `
|
|
178
|
-
branch and takes the last
|
|
179
|
-
the list for that point in history.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
- **State in tool-result `details`.** `lib/todo-state.ts`'s `lastTodos` replays
|
|
178
|
+
the active branch and takes the last write that fully validates, so `/resume`
|
|
179
|
+
and `/tree` reproduce the list for that point in history. The same replay is
|
|
180
|
+
what `plan` and `goal` read, so there is one rule for which write is a state
|
|
181
|
+
write. `pi.appendEntry()` was rejected because a side-channel list would not
|
|
182
|
+
branch; error results are skipped as non-writes.
|
|
183
|
+
- **Validation is pure and fail-safe.** The parser (`normalizeTodos` in
|
|
184
|
+
`lib/todo-state.ts`) throws a model-readable message, and the tool turns that
|
|
185
|
+
into an `isError` result that carries the *previous* list. A rejected call
|
|
186
|
+
never half-applies, and branch replay follows the same rule: a stored write
|
|
187
|
+
that fails validation is ignored whole rather than partially applied.
|
|
184
188
|
- **One `in_progress` item, with a label.** The rule is validated, not silently
|
|
185
189
|
coerced, so the model learns it from the error. The item must carry a
|
|
186
190
|
non-blank `activeForm`; branch replay passes `{ requireActiveForm: false }`
|
|
@@ -210,10 +214,10 @@ depends on config.
|
|
|
210
214
|
answer and never reopens that path. `nudged` caps it at one per user turn,
|
|
211
215
|
`setTodos` clears the counters, and `context` drops the hidden message on the
|
|
212
216
|
next user turn so it never accumulates.
|
|
213
|
-
- **Content is sanitized at the boundary.** `normalizeTodos`
|
|
214
|
-
characters and collapses whitespace
|
|
215
|
-
keeping `format.ts` free of terminal
|
|
216
|
-
per item.
|
|
217
|
+
- **Content is sanitized at the boundary.** `normalizeTodos` in
|
|
218
|
+
`lib/todo-state.ts` replaces control characters and collapses whitespace
|
|
219
|
+
before the length and duplicate checks, keeping `format.ts` free of terminal
|
|
220
|
+
concerns and every surface to one row per item.
|
|
217
221
|
- **The widget summarizes; full views keep the model's order.** The persistent
|
|
218
222
|
widget is a single line (`currentTodo` picks the `in_progress` item, else the
|
|
219
223
|
first pending); `/todos` keeps the submitted order, and the transcript result
|
|
@@ -234,8 +238,9 @@ depends on config.
|
|
|
234
238
|
|---|---|
|
|
235
239
|
| `index.ts` | Factory: wire the runtime, tool, command, reminder events, and session events. |
|
|
236
240
|
| `types.ts` | `Todo`, `TodoStatus`, `TodoDetails`. |
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
241
|
+
| `types.ts` | `Todo` and `TodoDetails`; re-exports `TodoStatus` / `TODO_STATUSES` from `lib/todo-state.ts`. |
|
|
242
|
+
| `schema.ts` | TypeBox parameters; the parser and limits live in `lib/todo-state.ts`. |
|
|
243
|
+
| `state.ts` | Status queries over the replayed list (pure). |
|
|
239
244
|
| `nudge.ts` | Lag detection: mutating and file-write classification, due check, reminder text (pure). |
|
|
240
245
|
| `format.ts` | Model-facing and transcript text (pure). |
|
|
241
246
|
| `tui.ts` | Widget and scrollable `/todos` components, drawn from the `lib/tui/` kit (`titleRule`, `dockListFrame`, `listRow`, `emptyState`). |
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
7
|
-
import {
|
|
7
|
+
import { GLYPH_GAP, GLYPHS } from "../../lib/tui/glyphs.ts";
|
|
8
|
+
import { overflowHint } from "../../lib/tui/tool.ts";
|
|
8
9
|
import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
|
|
9
10
|
import { completedCount, currentTodo } from "./state.ts";
|
|
10
11
|
import type { Todo, TodoStatus } from "./types.ts";
|
|
@@ -149,7 +150,7 @@ export function todoRailLines(input: TodoRailInput, theme: Theme, width: number)
|
|
|
149
150
|
});
|
|
150
151
|
}
|
|
151
152
|
if (input.more && input.more > 0) {
|
|
152
|
-
lines.push(`${continuation}${
|
|
153
|
+
lines.push(`${continuation}${overflowHint(theme, input.more)}`);
|
|
153
154
|
}
|
|
154
155
|
return lines.map((line) => truncateToWidth(line, w, "…"));
|
|
155
156
|
}
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
import { onRailsSuppressed, onUpperRailChanged } from "../../lib/rails.ts";
|
|
15
|
+
import { lastTodos } from "../../lib/todo-state.ts";
|
|
15
16
|
import { DEFAULT_TODO_CONFIG, type TodoConfig } from "./config.ts";
|
|
16
17
|
import { shouldNudge } from "./nudge.ts";
|
|
17
|
-
import { hasOpenTodos
|
|
18
|
+
import { hasOpenTodos } from "./state.ts";
|
|
18
19
|
import { TodoWidget, WIDGET_KEY } from "./tui.ts";
|
|
19
20
|
import type { Todo } from "./types.ts";
|
|
20
21
|
|
|
@@ -115,7 +116,7 @@ export function createTodoRuntime(pi?: Pick<ExtensionAPI, "events">): TodoRuntim
|
|
|
115
116
|
notify();
|
|
116
117
|
},
|
|
117
118
|
reconstruct: (ctx) => {
|
|
118
|
-
todos =
|
|
119
|
+
todos = lastTodos(ctx.sessionManager.getBranch());
|
|
119
120
|
mutations = 0;
|
|
120
121
|
writes = 0;
|
|
121
122
|
touched = false;
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parameter schema
|
|
2
|
+
* Parameter schema for the `todo` tool.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The schema is the host-facing shape; the semantic validation and the branch
|
|
5
|
+
* replay live together in `lib/todo-state.ts`, so the tool, `plan`, and `goal`
|
|
6
|
+
* read the same rules. Invalid input still throws a model-readable `Error`
|
|
7
|
+
* before any state changes.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
10
11
|
import { Type, type Static } from "typebox";
|
|
11
|
-
import {
|
|
12
|
-
import { TODO_STATUSES, type Todo, type TodoStatus } from "./types.ts";
|
|
13
|
-
|
|
14
|
-
/** Maximum number of items in one list. */
|
|
15
|
-
export const MAX_TODOS = 50;
|
|
16
|
-
/** Maximum length of a single item's text. */
|
|
17
|
-
export const MAX_CONTENT = 500;
|
|
12
|
+
import { MAX_TODOS, TODO_STATUSES } from "../../lib/todo-state.ts";
|
|
18
13
|
|
|
19
14
|
/**
|
|
20
15
|
* One item in the tool's input and output schemas.
|
|
21
16
|
*
|
|
22
17
|
* The input schema (`TodoParams`) enforces `maxItems` and the status enum
|
|
23
|
-
* before `execute` runs
|
|
24
|
-
*
|
|
25
|
-
* (one `in_progress`, unique non-empty content, content length).
|
|
18
|
+
* before `execute` runs; `normalizeTodos` in `lib/todo-state.ts` re-enforces
|
|
19
|
+
* both and owns the semantic rules the schema cannot express.
|
|
26
20
|
*/
|
|
27
21
|
export const TodoItem = Type.Object({
|
|
28
22
|
content: Type.String({ description: "Imperative description of the task" }),
|
|
@@ -53,73 +47,3 @@ export const TodoResult = Type.Object({
|
|
|
53
47
|
});
|
|
54
48
|
|
|
55
49
|
export type TodoArgs = Static<typeof TodoParams>;
|
|
56
|
-
|
|
57
|
-
const STATUS_SET: ReadonlySet<string> = new Set(TODO_STATUSES);
|
|
58
|
-
|
|
59
|
-
function normalizeStatus(raw: unknown, index: number): TodoStatus {
|
|
60
|
-
const value = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
|
61
|
-
if (STATUS_SET.has(value)) return value as TodoStatus;
|
|
62
|
-
throw new Error(`Todo ${index + 1}: status must be one of ${TODO_STATUSES.join(", ")}.`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Options for {@link normalizeTodos}. */
|
|
66
|
-
export interface NormalizeOptions {
|
|
67
|
-
/**
|
|
68
|
-
* Require a non-blank `activeForm` on `in_progress` items. Live tool input
|
|
69
|
-
* enforces this (the default); branch replay passes `false` so lists written
|
|
70
|
-
* before the rule was added still load.
|
|
71
|
-
*/
|
|
72
|
-
requireActiveForm?: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validate and normalize the model's list.
|
|
77
|
-
*
|
|
78
|
-
* Content and `activeForm` are sanitized to a single safe line (see
|
|
79
|
-
* `sanitizeLine`). Rejects an empty description, an unknown status, duplicate
|
|
80
|
-
* content, an over-long field, more than one `in_progress` item, and — unless
|
|
81
|
-
* `options.requireActiveForm` is `false` — an `in_progress` item without a
|
|
82
|
-
* non-blank `activeForm`. `activeForm` is dropped when blank.
|
|
83
|
-
*/
|
|
84
|
-
export function normalizeTodos(raw: unknown, options: NormalizeOptions = {}): Todo[] {
|
|
85
|
-
const requireActiveForm = options.requireActiveForm ?? true;
|
|
86
|
-
if (raw === undefined || raw === null) return [];
|
|
87
|
-
if (!Array.isArray(raw)) throw new Error("todos must be an array.");
|
|
88
|
-
if (raw.length > MAX_TODOS) throw new Error(`At most ${MAX_TODOS} todos are allowed.`);
|
|
89
|
-
|
|
90
|
-
const todos: Todo[] = [];
|
|
91
|
-
const seen = new Set<string>();
|
|
92
|
-
let inProgress = 0;
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < raw.length; i++) {
|
|
95
|
-
const item = (raw[i] ?? {}) as Partial<Todo>;
|
|
96
|
-
const content = typeof item.content === "string" ? sanitizeLine(item.content) : "";
|
|
97
|
-
if (!content) throw new Error(`Todo ${i + 1}: content is required.`);
|
|
98
|
-
if (content.length > MAX_CONTENT) {
|
|
99
|
-
throw new Error(`Todo ${i + 1}: content is longer than ${MAX_CONTENT} characters.`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const key = content.toLowerCase();
|
|
103
|
-
if (seen.has(key)) throw new Error(`Todo ${i + 1}: duplicate content "${content}".`);
|
|
104
|
-
seen.add(key);
|
|
105
|
-
|
|
106
|
-
const status = normalizeStatus(item.status, i);
|
|
107
|
-
if (status === "in_progress") inProgress++;
|
|
108
|
-
|
|
109
|
-
const activeForm = typeof item.activeForm === "string" ? sanitizeLine(item.activeForm) : "";
|
|
110
|
-
if (activeForm.length > MAX_CONTENT) {
|
|
111
|
-
throw new Error(`Todo ${i + 1}: activeForm is longer than ${MAX_CONTENT} characters.`);
|
|
112
|
-
}
|
|
113
|
-
if (status === "in_progress" && requireActiveForm && !activeForm) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`Todo ${i + 1}: activeForm is required for an in_progress item (present-continuous, e.g. "Running tests").`,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
todos.push(activeForm ? { content, status, activeForm } : { content, status });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (inProgress > 1) {
|
|
122
|
-
throw new Error("At most one todo may be in_progress; mark the rest pending or completed.");
|
|
123
|
-
}
|
|
124
|
-
return todos;
|
|
125
|
-
}
|
package/extensions/todo/state.ts
CHANGED
|
@@ -1,53 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Status queries over the list the branch published (pure).
|
|
3
3
|
*
|
|
4
|
-
* The list
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* values are re-validated and re-sanitized so corrupt or tampered branch data
|
|
8
|
-
* cannot inject escapes, crash reconstruction, or wipe a valid list.
|
|
4
|
+
* The list itself — the item contract, its validation, and the single strict
|
|
5
|
+
* replay — lives in `lib/todo-state.ts`, shared with `plan` and `goal`. These
|
|
6
|
+
* are the derived facts the widget and the transcript ask of it.
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
|
-
import {
|
|
12
|
-
import { normalizeTodos } from "./schema.ts";
|
|
13
|
-
import type { Todo, TodoDetails } from "./types.ts";
|
|
14
|
-
|
|
15
|
-
/** Minimal shape of a session entry, used for runtime narrowing. */
|
|
16
|
-
interface BranchEntryLike {
|
|
17
|
-
type?: string;
|
|
18
|
-
message?: {
|
|
19
|
-
role?: string;
|
|
20
|
-
toolName?: string;
|
|
21
|
-
details?: unknown;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** The last valid list written on the branch, or an empty list. */
|
|
26
|
-
export function reconstructTodos(entries: Iterable<unknown>): Todo[] {
|
|
27
|
-
let todos: Todo[] = [];
|
|
28
|
-
for (const raw of entries) {
|
|
29
|
-
const entry = raw as BranchEntryLike;
|
|
30
|
-
if (entry?.type !== "message") continue;
|
|
31
|
-
const message = entry.message;
|
|
32
|
-
if (message?.role !== "toolResult" || message.toolName !== TODO_TOOL) continue;
|
|
33
|
-
const details = message.details as TodoDetails | undefined;
|
|
34
|
-
if (!details || details.todos === undefined) continue;
|
|
35
|
-
// A rejected call carries the unchanged list for the model to read; it is
|
|
36
|
-
// not a state write and must not be replayed as one.
|
|
37
|
-
if (details.error) continue;
|
|
38
|
-
// Anything that does not re-validate as a todo list is ignored, so a
|
|
39
|
-
// malformed entry cannot wipe a valid one. Replay is lenient about
|
|
40
|
-
// `activeForm`: a list written before that field was required still loads
|
|
41
|
-
// instead of being dropped.
|
|
42
|
-
if (!Array.isArray(details.todos)) continue;
|
|
43
|
-
try {
|
|
44
|
-
todos = normalizeTodos(details.todos, { requireActiveForm: false });
|
|
45
|
-
} catch {
|
|
46
|
-
// Keep the previous list.
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return todos;
|
|
50
|
-
}
|
|
9
|
+
import type { Todo } from "./types.ts";
|
|
51
10
|
|
|
52
11
|
/** Number of completed items. */
|
|
53
12
|
export function completedCount(todos: Todo[]): number {
|
package/extensions/todo/tools.ts
CHANGED
|
@@ -15,12 +15,13 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
15
15
|
import { sanitizeLine } from "../../lib/format.ts";
|
|
16
16
|
import { TODO_TOOL } from "../../lib/tool-names.ts";
|
|
17
17
|
import { GLYPHS } from "../../lib/tui/glyphs.ts";
|
|
18
|
+
import { normalizeTodos } from "../../lib/todo-state.ts";
|
|
18
19
|
import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
|
|
19
20
|
import { rejectionLine, reuseCallText, toolCallTitle } from "../../lib/tui/tool.ts";
|
|
20
21
|
import { paint } from "../../lib/tui/tokens.ts";
|
|
21
22
|
import { compareByActivity, formatCallText, formatTodoText, type TodoRailInput } from "./format.ts";
|
|
22
23
|
import type { TodoRuntime } from "./runtime.ts";
|
|
23
|
-
import {
|
|
24
|
+
import { TodoParams, TodoResult, type TodoArgs } from "./schema.ts";
|
|
24
25
|
import { TodoResult as TodoResultView } from "./tui.ts";
|
|
25
26
|
import type { Todo, TodoDetails } from "./types.ts";
|
|
26
27
|
|
package/extensions/todo/types.ts
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared types for the todo extension.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The item contract itself lives in `lib/todo-state.ts`, which owns the parser
|
|
5
|
+
* and the branch replay shared with `plan` and `goal`; this module re-exports it
|
|
6
|
+
* under the name the extension uses and adds the tool-result shape.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import type { ExistingTodo } from "../../lib/todo-state.ts";
|
|
10
10
|
|
|
11
|
-
export type TodoStatus
|
|
11
|
+
export { TODO_STATUSES, type TodoStatus } from "../../lib/todo-state.ts";
|
|
12
12
|
|
|
13
13
|
/** One item in the list. */
|
|
14
|
-
export type Todo =
|
|
15
|
-
/** Imperative description of the task, e.g. "Write the parser tests". */
|
|
16
|
-
content: string;
|
|
17
|
-
status: TodoStatus;
|
|
18
|
-
/**
|
|
19
|
-
* Present-continuous label shown while the item is in progress, e.g.
|
|
20
|
-
* "Writing the parser tests". Falls back to `content` when absent.
|
|
21
|
-
*/
|
|
22
|
-
activeForm?: string;
|
|
23
|
-
};
|
|
14
|
+
export type Todo = ExistingTodo;
|
|
24
15
|
|
|
25
16
|
/** Structured result carried in the tool's `details` and used for reconstruction. */
|
|
26
17
|
export interface TodoDetails {
|