@tt-a1i/openpi 0.1.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 +643 -0
- package/SETUP.md +74 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-package.png +0 -0
- package/assets/readme-hero-mobile.svg +72 -0
- package/assets/readme-hero.svg +118 -0
- package/assets/readme-runtime-mobile.svg +91 -0
- package/assets/readme-runtime.svg +111 -0
- package/extensions/ask-user/handoff.ts +205 -0
- package/extensions/ask-user/index.ts +1110 -0
- package/extensions/ask-user/limits.ts +89 -0
- package/extensions/ask-user/prompt.ts +76 -0
- package/extensions/background-terminals/index.ts +653 -0
- package/extensions/background-terminals/src/domain.ts +99 -0
- package/extensions/background-terminals/src/manager.ts +989 -0
- package/extensions/background-terminals/src/output.ts +84 -0
- package/extensions/background-terminals/src/prompt.ts +195 -0
- package/extensions/background-terminals/src/result-delivery.ts +43 -0
- package/extensions/background-terminals/src/runtime.ts +36 -0
- package/extensions/background-terminals/src/ui/output-view.ts +55 -0
- package/extensions/background-terminals/src/ui/ps.ts +642 -0
- package/extensions/background-terminals/src/ui/tool-result.ts +146 -0
- package/extensions/background-terminals/src/watch.ts +192 -0
- package/extensions/context-pivot/index.ts +222 -0
- package/extensions/copy-all/index.ts +65 -0
- package/extensions/cron/index.ts +173 -0
- package/extensions/cron/schedule.ts +127 -0
- package/extensions/file-mutation-display/index.ts +105 -0
- package/extensions/file-mutation-display/render.ts +107 -0
- package/extensions/file-search/index.ts +515 -0
- package/extensions/file-search/src/args.ts +129 -0
- package/extensions/file-search/src/binaries.ts +419 -0
- package/extensions/file-search/src/output.ts +142 -0
- package/extensions/file-search/src/process.ts +309 -0
- package/extensions/file-search/src/prompt.ts +53 -0
- package/extensions/git-info/index.ts +272 -0
- package/extensions/git-info/src/changed-files-view.ts +414 -0
- package/extensions/git-info/src/process.ts +107 -0
- package/extensions/git-info/src/refresh-coordinator.ts +13 -0
- package/extensions/git-info/src/runtime.ts +28 -0
- package/extensions/goal/controller.ts +794 -0
- package/extensions/goal/index.ts +521 -0
- package/extensions/goal/prompts.ts +122 -0
- package/extensions/goal/state.ts +763 -0
- package/extensions/goal/ui.ts +158 -0
- package/extensions/model-info/index.ts +234 -0
- package/extensions/plan-mode/bash-policy.ts +313 -0
- package/extensions/plan-mode/index.ts +539 -0
- package/extensions/post-edit/index.ts +129 -0
- package/extensions/sessions/LICENSE.upstream +21 -0
- package/extensions/sessions/git-stats.ts +226 -0
- package/extensions/sessions/index.ts +1092 -0
- package/extensions/sessions/sessions.ts +385 -0
- package/extensions/setup/index.ts +408 -0
- package/extensions/shared/activity-status.ts +65 -0
- package/extensions/shared/below-editor-navigation.ts +343 -0
- package/extensions/shared/child-session.ts +352 -0
- package/extensions/shared/context-utilization.ts +47 -0
- package/extensions/shared/dashboard-state.ts +102 -0
- package/extensions/shared/plan-mode-state.ts +65 -0
- package/extensions/shared/setup-config.ts +971 -0
- package/extensions/shared/subagent-roles.ts +22 -0
- package/extensions/shared/terminal-text.ts +38 -0
- package/extensions/shared/tool-call-timeout.ts +104 -0
- package/extensions/shared/worktree.ts +526 -0
- package/extensions/subagents/index.ts +1225 -0
- package/extensions/subagents/navigation.ts +121 -0
- package/extensions/subagents/src/agent-types.ts +543 -0
- package/extensions/subagents/src/backend.ts +63 -0
- package/extensions/subagents/src/backends/pi.ts +493 -0
- package/extensions/subagents/src/backends/stub.ts +296 -0
- package/extensions/subagents/src/by-the-way.ts +21 -0
- package/extensions/subagents/src/domain.ts +271 -0
- package/extensions/subagents/src/format.ts +48 -0
- package/extensions/subagents/src/manager.ts +769 -0
- package/extensions/subagents/src/prompt.ts +190 -0
- package/extensions/subagents/src/result-delivery.ts +20 -0
- package/extensions/subagents/src/runtime.ts +51 -0
- package/extensions/subagents/src/ui/takeover.ts +615 -0
- package/extensions/subagents/src/ui/transcript.ts +293 -0
- package/extensions/subagents/src/ui/wait-result.ts +89 -0
- package/extensions/suggestions/index.ts +172 -0
- package/extensions/suggestions/src/config.ts +12 -0
- package/extensions/suggestions/src/predictor.ts +147 -0
- package/extensions/suggestions/src/prompt.ts +20 -0
- package/extensions/suggestions/src/transcript.ts +233 -0
- package/extensions/suggestions/src/ui.ts +224 -0
- package/extensions/tasks/index.ts +512 -0
- package/extensions/tasks/tasks.ts +649 -0
- package/extensions/tasks/ui.ts +421 -0
- package/extensions/turn-time/index.ts +61 -0
- package/extensions/ui-customization/footer.ts +512 -0
- package/extensions/ui-customization/index.ts +217 -0
- package/extensions/workflows/acceptance.ts +298 -0
- package/extensions/workflows/artifacts.ts +225 -0
- package/extensions/workflows/controller.ts +210 -0
- package/extensions/workflows/dashboard.ts +1226 -0
- package/extensions/workflows/index.ts +1884 -0
- package/extensions/workflows/journal.ts +188 -0
- package/extensions/workflows/meta.ts +250 -0
- package/extensions/workflows/model.ts +423 -0
- package/extensions/workflows/navigation.ts +93 -0
- package/extensions/workflows/prompt.ts +212 -0
- package/extensions/workflows/replay-safety.ts +577 -0
- package/extensions/workflows/runner.ts +786 -0
- package/extensions/workflows/sandbox-child.cjs +402 -0
- package/extensions/workflows/sandbox.ts +397 -0
- package/extensions/workflows/serialization.ts +162 -0
- package/extensions/workflows/worktree-handoff.ts +216 -0
- package/package.json +87 -0
- package/scripts/prepare-effect-tsgo.mjs +16 -0
- package/skills/background-terminals/SKILL.md +30 -0
- package/skills/subagents/SKILL.md +15 -0
- package/themes/github-dark-default.json +89 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import type {
|
|
3
|
+
ExtensionAPI,
|
|
4
|
+
ExtensionContext,
|
|
5
|
+
ToolInfo,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Key, Text } from "@earendil-works/pi-tui";
|
|
8
|
+
import { Type } from "typebox";
|
|
9
|
+
import {
|
|
10
|
+
TASKS_ENTRY_TYPE,
|
|
11
|
+
TASKS_LIMITS,
|
|
12
|
+
TASK_STATUSES,
|
|
13
|
+
TaskRestoreError,
|
|
14
|
+
applyTaskAdd,
|
|
15
|
+
applyTaskUpdate,
|
|
16
|
+
createSessionTasks,
|
|
17
|
+
emptyTaskSnapshot,
|
|
18
|
+
projectTasks,
|
|
19
|
+
restoreTaskSnapshot,
|
|
20
|
+
type TaskFilter,
|
|
21
|
+
type TaskItem,
|
|
22
|
+
type TaskSnapshot,
|
|
23
|
+
} from "./tasks.ts";
|
|
24
|
+
import {
|
|
25
|
+
openTasksScreen,
|
|
26
|
+
TASK_WIDGET_LIMIT,
|
|
27
|
+
renderTaskWidget,
|
|
28
|
+
renderToolResult,
|
|
29
|
+
taskCounts,
|
|
30
|
+
type TaskToolDetails,
|
|
31
|
+
} from "./ui.ts";
|
|
32
|
+
|
|
33
|
+
const TOOL_NAMES = ["tasks_add", "tasks_update", "tasks_list"] as const;
|
|
34
|
+
const TASK_WIDGET_KEY = "session-tasks-panel";
|
|
35
|
+
const CONFLICT_NAMES = new Set(["todo", "TodoWrite", "update_plan"]);
|
|
36
|
+
const TOOL_PURPOSE =
|
|
37
|
+
"Records session work intent. It does not execute, schedule, or delegate work.";
|
|
38
|
+
|
|
39
|
+
export interface TaskConflict {
|
|
40
|
+
name: string;
|
|
41
|
+
source?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function findTaskConflict(tools: readonly ToolInfo[]) {
|
|
45
|
+
const conflict = tools.find((tool) => CONFLICT_NAMES.has(tool.name));
|
|
46
|
+
if (!conflict) return undefined;
|
|
47
|
+
const source = conflict.sourceInfo?.path || conflict.sourceInfo?.source;
|
|
48
|
+
return { name: conflict.name, source } satisfies TaskConflict;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function injectTaskProjection(
|
|
52
|
+
messages: readonly unknown[],
|
|
53
|
+
projection: string,
|
|
54
|
+
) {
|
|
55
|
+
// Keep this pure for direct tests. Pi already supplies the context hook a
|
|
56
|
+
// deep copy, so this is defensive rather than required by the runtime.
|
|
57
|
+
const next = structuredClone(messages) as Array<{
|
|
58
|
+
role?: string;
|
|
59
|
+
content?: unknown;
|
|
60
|
+
}>;
|
|
61
|
+
for (let index = next.length - 1; index >= 0; index--) {
|
|
62
|
+
const message = next[index];
|
|
63
|
+
if (message.role !== "user") continue;
|
|
64
|
+
const safeProjection = projection
|
|
65
|
+
.replaceAll("<session-tasks>", "[session-tasks]")
|
|
66
|
+
.replaceAll("</session-tasks>", "[/session-tasks]");
|
|
67
|
+
const block = {
|
|
68
|
+
type: "text",
|
|
69
|
+
text: `\n\n<session-tasks>\n${safeProjection}\n</session-tasks>`,
|
|
70
|
+
};
|
|
71
|
+
if (typeof message.content === "string") {
|
|
72
|
+
message.content = [{ type: "text", text: message.content }, block];
|
|
73
|
+
} else if (Array.isArray(message.content)) {
|
|
74
|
+
message.content.push(block);
|
|
75
|
+
} else {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function taskConflictMessage(conflict: TaskConflict) {
|
|
84
|
+
return `Session tasks disabled because tool “${conflict.name}” is already registered${conflict.source ? ` by ${conflict.source}` : ""}. Disable the other Todo/plan extension and run /reload.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default function sessionTasks(pi: ExtensionAPI) {
|
|
88
|
+
let tasks = createSessionTasks();
|
|
89
|
+
let lockedReason: string | undefined;
|
|
90
|
+
let conflict: TaskConflict | undefined;
|
|
91
|
+
let toolsRegistered = false;
|
|
92
|
+
let coldRun = true;
|
|
93
|
+
let activeRun = false;
|
|
94
|
+
let frozenProjection = "";
|
|
95
|
+
let notifiedProblem: string | undefined;
|
|
96
|
+
let taskWidgetVisible = true;
|
|
97
|
+
let taskWidgetExpanded = false;
|
|
98
|
+
let ui: ExtensionContext["ui"] | undefined;
|
|
99
|
+
let uiMode: ExtensionContext["mode"] | undefined;
|
|
100
|
+
|
|
101
|
+
const snapshot = () => tasks.snapshot();
|
|
102
|
+
|
|
103
|
+
const actionableTaskCount = () =>
|
|
104
|
+
snapshot().items.filter(
|
|
105
|
+
(item) =>
|
|
106
|
+
item.status === "pending" ||
|
|
107
|
+
item.status === "in_progress" ||
|
|
108
|
+
item.status === "blocked",
|
|
109
|
+
).length;
|
|
110
|
+
|
|
111
|
+
const hasActionableTasks = () => actionableTaskCount() > 0;
|
|
112
|
+
|
|
113
|
+
const updateTaskWidget = (ctx?: ExtensionContext) => {
|
|
114
|
+
if (ctx?.hasUI) {
|
|
115
|
+
ui = ctx.ui;
|
|
116
|
+
uiMode = ctx.mode;
|
|
117
|
+
}
|
|
118
|
+
if (!ui || uiMode !== "tui") return false;
|
|
119
|
+
const current = snapshot();
|
|
120
|
+
const shown =
|
|
121
|
+
taskWidgetVisible && !problemMessage() && hasActionableTasks();
|
|
122
|
+
if (!shown) {
|
|
123
|
+
ui.setWidget(TASK_WIDGET_KEY, undefined);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
ui.setWidget(TASK_WIDGET_KEY, (_tui, theme) => ({
|
|
127
|
+
render: (width) =>
|
|
128
|
+
renderTaskWidget(current, theme, width, taskWidgetExpanded),
|
|
129
|
+
invalidate() {},
|
|
130
|
+
}));
|
|
131
|
+
return true;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const taskWidgetFeedback = (shown: boolean) =>
|
|
135
|
+
shown
|
|
136
|
+
? "Task panel shown."
|
|
137
|
+
: uiMode !== "tui"
|
|
138
|
+
? "Task panel is available only in interactive TUI mode."
|
|
139
|
+
: taskWidgetVisible
|
|
140
|
+
? "Task panel enabled; it will appear when active tasks exist."
|
|
141
|
+
: "Task panel hidden.";
|
|
142
|
+
|
|
143
|
+
const restore = (ctx: ExtensionContext) => {
|
|
144
|
+
try {
|
|
145
|
+
tasks = createSessionTasks(
|
|
146
|
+
restoreTaskSnapshot(ctx.sessionManager.getBranch()),
|
|
147
|
+
);
|
|
148
|
+
lockedReason = undefined;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
tasks = createSessionTasks(emptyTaskSnapshot());
|
|
151
|
+
lockedReason =
|
|
152
|
+
error instanceof TaskRestoreError || error instanceof Error
|
|
153
|
+
? error.message
|
|
154
|
+
: String(error);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const problemMessage = () => {
|
|
159
|
+
if (conflict) return taskConflictMessage(conflict);
|
|
160
|
+
if (lockedReason) {
|
|
161
|
+
return `Session tasks are locked because their newest snapshot is invalid: ${lockedReason}. Navigate to a clean branch or start a new session.`;
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const notifyProblem = (ctx: ExtensionContext) => {
|
|
167
|
+
const problem = problemMessage();
|
|
168
|
+
if (!problem) {
|
|
169
|
+
notifiedProblem = undefined;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (ctx.hasUI && notifiedProblem !== problem) {
|
|
173
|
+
notifiedProblem = problem;
|
|
174
|
+
ctx.ui.notify(problem, "warning");
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const assertAvailable = () => {
|
|
179
|
+
const problem = problemMessage();
|
|
180
|
+
if (problem) throw new Error(problem);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const persistThenCommit = (candidate: TaskSnapshot) => {
|
|
184
|
+
if (candidate.revision === snapshot().revision) return false;
|
|
185
|
+
// Keep this path synchronous. An await here would let sibling tool calls
|
|
186
|
+
// reorder state and persistence, recreating the upstream Todo lost-update bug.
|
|
187
|
+
pi.appendEntry(TASKS_ENTRY_TYPE, candidate);
|
|
188
|
+
tasks.commit(candidate);
|
|
189
|
+
updateTaskWidget();
|
|
190
|
+
return true;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const toolDetails = (
|
|
194
|
+
action: TaskToolDetails["action"],
|
|
195
|
+
items: TaskItem[],
|
|
196
|
+
batchClosed = false,
|
|
197
|
+
): TaskToolDetails => ({
|
|
198
|
+
action,
|
|
199
|
+
items,
|
|
200
|
+
total: snapshot().items.length,
|
|
201
|
+
revision: snapshot().revision,
|
|
202
|
+
// From the live snapshot, not `items`: a tools_update carries only the one
|
|
203
|
+
// row it touched, and a header counted from that would claim the batch is
|
|
204
|
+
// a single task.
|
|
205
|
+
counts: taskCounts(snapshot().items),
|
|
206
|
+
...(batchClosed ? { batchClosed: true } : {}),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const registerTools = () => {
|
|
210
|
+
if (toolsRegistered || conflict) return;
|
|
211
|
+
toolsRegistered = true;
|
|
212
|
+
|
|
213
|
+
pi.registerTool({
|
|
214
|
+
name: "tasks_add",
|
|
215
|
+
label: "Tasks Add",
|
|
216
|
+
description: `${TOOL_PURPOSE} Add one or more stable-ID items to the current Pi session tasks. Use this only for work spanning multiple agent runs or user turns, or for an explicit user task list. When every item in a batch reaches done/dropped the batch closes and the list clears; the next tasks_add starts a fresh batch numbered from T1, so a T-id only identifies work within its own batch (past evidence remains in the session history, not this list).`,
|
|
217
|
+
promptSnippet:
|
|
218
|
+
"Add stable work-intent items to the current session tasks",
|
|
219
|
+
promptGuidelines: [
|
|
220
|
+
"Use tasks_add only for work spanning multiple agent runs or user turns, or when the user explicitly provides a task list; do not use it as a per-step scratchpad within one run.",
|
|
221
|
+
"Task tools record advisory intent only; Subagents and Workflows execute work, while files, git, tests, tool results, artifacts, and user confirmation remain truth.",
|
|
222
|
+
],
|
|
223
|
+
parameters: Type.Object({
|
|
224
|
+
items: Type.Array(
|
|
225
|
+
Type.Object({
|
|
226
|
+
subject: Type.String({
|
|
227
|
+
minLength: 1,
|
|
228
|
+
maxLength: TASKS_LIMITS.subjectChars,
|
|
229
|
+
description: "Short imperative description.",
|
|
230
|
+
}),
|
|
231
|
+
detail: Type.Optional(
|
|
232
|
+
Type.String({ maxLength: TASKS_LIMITS.detailChars }),
|
|
233
|
+
),
|
|
234
|
+
}),
|
|
235
|
+
{ minItems: 1, maxItems: TASKS_LIMITS.addBatch },
|
|
236
|
+
),
|
|
237
|
+
}),
|
|
238
|
+
execute(_id, params) {
|
|
239
|
+
assertAvailable();
|
|
240
|
+
const mutation = applyTaskAdd(snapshot(), params.items);
|
|
241
|
+
persistThenCommit(mutation.snapshot);
|
|
242
|
+
return Promise.resolve({
|
|
243
|
+
content: [
|
|
244
|
+
{
|
|
245
|
+
type: "text" as const,
|
|
246
|
+
text: `Added ${mutation.items.map((item) => `T${item.id}`).join(", ")}.`,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
details: toolDetails("add", mutation.items),
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
renderCall(args, theme) {
|
|
253
|
+
return new Text(
|
|
254
|
+
`${theme.fg("toolTitle", theme.bold("tasks_add"))} ${theme.fg("muted", `${args.items.length} item(s)`)}`,
|
|
255
|
+
0,
|
|
256
|
+
0,
|
|
257
|
+
);
|
|
258
|
+
},
|
|
259
|
+
renderResult(result, options, theme) {
|
|
260
|
+
const first = result.content[0];
|
|
261
|
+
return renderToolResult(
|
|
262
|
+
result.details,
|
|
263
|
+
options.expanded,
|
|
264
|
+
theme,
|
|
265
|
+
first?.type === "text" ? first.text : "Tasks unavailable.",
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
pi.registerTool({
|
|
271
|
+
name: "tasks_update",
|
|
272
|
+
label: "Tasks Update",
|
|
273
|
+
description: `${TOOL_PURPOSE} Patch one task item by numeric ID. blocked, done, and dropped status changes require a fresh note explaining the blocker, observable evidence, or drop reason.`,
|
|
274
|
+
promptSnippet: "Update one session task item by stable ID",
|
|
275
|
+
promptGuidelines: [
|
|
276
|
+
"Keep tasks_update status current when tracked work materially changes, but avoid ceremonial status churn.",
|
|
277
|
+
"Before setting a task item to done, include a note citing an observable check, artifact, commit, tool result, or user confirmation; Tasks record this claim but do not verify it.",
|
|
278
|
+
],
|
|
279
|
+
parameters: Type.Object({
|
|
280
|
+
id: Type.Integer({ minimum: 1 }),
|
|
281
|
+
subject: Type.Optional(
|
|
282
|
+
Type.String({ minLength: 1, maxLength: TASKS_LIMITS.subjectChars }),
|
|
283
|
+
),
|
|
284
|
+
detail: Type.Optional(
|
|
285
|
+
Type.Union([
|
|
286
|
+
Type.String({ maxLength: TASKS_LIMITS.detailChars }),
|
|
287
|
+
Type.Null(),
|
|
288
|
+
]),
|
|
289
|
+
),
|
|
290
|
+
status: Type.Optional(StringEnum(TASK_STATUSES)),
|
|
291
|
+
note: Type.Optional(
|
|
292
|
+
Type.Union([
|
|
293
|
+
Type.String({ maxLength: TASKS_LIMITS.noteChars }),
|
|
294
|
+
Type.Null(),
|
|
295
|
+
]),
|
|
296
|
+
),
|
|
297
|
+
}),
|
|
298
|
+
execute(_id, params) {
|
|
299
|
+
assertAvailable();
|
|
300
|
+
const before = snapshot();
|
|
301
|
+
const closesBatch = mutationWillCloseBatch(
|
|
302
|
+
before,
|
|
303
|
+
params.id,
|
|
304
|
+
params.status,
|
|
305
|
+
);
|
|
306
|
+
const mutation = applyTaskUpdate(before, params);
|
|
307
|
+
const changed = persistThenCommit(mutation.snapshot);
|
|
308
|
+
return Promise.resolve({
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text" as const,
|
|
312
|
+
text: changed
|
|
313
|
+
? closesBatch
|
|
314
|
+
? `${params.status === "dropped" ? "Dropped" : "Completed"} T${params.id}. Task batch closed; the next tasks_add starts again at T1.`
|
|
315
|
+
: `Updated T${params.id}.`
|
|
316
|
+
: `T${params.id} already has that state; no update recorded.`,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
details: toolDetails("update", mutation.items, closesBatch),
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
renderCall(args, theme) {
|
|
323
|
+
return new Text(
|
|
324
|
+
`${theme.fg("toolTitle", theme.bold("tasks_update"))} ${theme.fg("accent", `T${args.id}`)}${args.status ? ` ${theme.fg("muted", args.status)}` : ""}`,
|
|
325
|
+
0,
|
|
326
|
+
0,
|
|
327
|
+
);
|
|
328
|
+
},
|
|
329
|
+
renderResult(result, options, theme) {
|
|
330
|
+
const first = result.content[0];
|
|
331
|
+
return renderToolResult(
|
|
332
|
+
result.details,
|
|
333
|
+
options.expanded,
|
|
334
|
+
theme,
|
|
335
|
+
first?.type === "text" ? first.text : "Tasks unavailable.",
|
|
336
|
+
);
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
pi.registerTool({
|
|
341
|
+
name: "tasks_list",
|
|
342
|
+
label: "Tasks List",
|
|
343
|
+
description:
|
|
344
|
+
"Reads the current session's work-intent tasks, optionally filtered by ID and status; does not execute, schedule, or delegate work.",
|
|
345
|
+
promptSnippet: "List current session work-intent task items",
|
|
346
|
+
parameters: Type.Object({
|
|
347
|
+
id: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
348
|
+
status: Type.Optional(StringEnum(TASK_STATUSES)),
|
|
349
|
+
}),
|
|
350
|
+
execute(_id, params) {
|
|
351
|
+
assertAvailable();
|
|
352
|
+
const filter = params satisfies TaskFilter;
|
|
353
|
+
const items = tasks.list(filter);
|
|
354
|
+
const preview = items.slice(0, 5);
|
|
355
|
+
const rendered = tasks.render(filter, 3_800);
|
|
356
|
+
const text =
|
|
357
|
+
rendered.endsWith("…") || items.length > preview.length
|
|
358
|
+
? `${rendered}\nShowing a bounded view of ${items.length} matched item(s); filter by status or id for a narrower result.`
|
|
359
|
+
: rendered;
|
|
360
|
+
return Promise.resolve({
|
|
361
|
+
content: [{ type: "text" as const, text }],
|
|
362
|
+
details: toolDetails("list", preview),
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
renderCall(_args, theme) {
|
|
366
|
+
return new Text(theme.fg("toolTitle", theme.bold("tasks_list")), 0, 0);
|
|
367
|
+
},
|
|
368
|
+
renderResult(result, options, theme) {
|
|
369
|
+
const first = result.content[0];
|
|
370
|
+
return renderToolResult(
|
|
371
|
+
result.details,
|
|
372
|
+
options.expanded,
|
|
373
|
+
theme,
|
|
374
|
+
first?.type === "text" ? first.text : "Tasks unavailable.",
|
|
375
|
+
);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
pi.registerCommand("tasks", {
|
|
381
|
+
description: "Inspect the current session work-intent tasks",
|
|
382
|
+
handler: async (args, ctx) => {
|
|
383
|
+
const problem = problemMessage();
|
|
384
|
+
if (problem) {
|
|
385
|
+
if (ctx.hasUI) ctx.ui.notify(problem, "warning");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const action = args.trim().toLowerCase();
|
|
389
|
+
if (action === "hide" || action === "show" || action === "toggle") {
|
|
390
|
+
taskWidgetVisible =
|
|
391
|
+
action === "show"
|
|
392
|
+
? true
|
|
393
|
+
: action === "hide"
|
|
394
|
+
? false
|
|
395
|
+
: !taskWidgetVisible;
|
|
396
|
+
taskWidgetExpanded = false;
|
|
397
|
+
const shown = updateTaskWidget(ctx);
|
|
398
|
+
if (ctx.hasUI) ctx.ui.notify(taskWidgetFeedback(shown), "info");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
await openTasksScreen(ctx, snapshot());
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
pi.registerShortcut(Key.ctrlShift("t"), {
|
|
406
|
+
description: "Show all active tasks above the editor, or collapse to four",
|
|
407
|
+
handler: async (ctx) => {
|
|
408
|
+
const problem = problemMessage();
|
|
409
|
+
if (problem) {
|
|
410
|
+
if (ctx.hasUI) ctx.ui.notify(problem, "warning");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const count = actionableTaskCount();
|
|
414
|
+
if (count === 0) {
|
|
415
|
+
if (ctx.hasUI) {
|
|
416
|
+
ctx.ui.notify(
|
|
417
|
+
"No active tasks to show. Use /tasks to inspect task history.",
|
|
418
|
+
"info",
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
taskWidgetVisible = true;
|
|
424
|
+
taskWidgetExpanded =
|
|
425
|
+
count > TASK_WIDGET_LIMIT ? !taskWidgetExpanded : false;
|
|
426
|
+
updateTaskWidget(ctx);
|
|
427
|
+
if (ctx.hasUI) {
|
|
428
|
+
ctx.ui.notify(
|
|
429
|
+
count <= TASK_WIDGET_LIMIT
|
|
430
|
+
? `All ${count} active task${count === 1 ? " is" : "s are"} already visible.`
|
|
431
|
+
: taskWidgetExpanded
|
|
432
|
+
? `Showing all ${count} active tasks above the editor.`
|
|
433
|
+
: `Task panel collapsed to ${TASK_WIDGET_LIMIT} active tasks.`,
|
|
434
|
+
"info",
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
pi.on("session_start", (_event, ctx) => {
|
|
441
|
+
ui = ctx.hasUI ? ctx.ui : undefined;
|
|
442
|
+
uiMode = ctx.hasUI ? ctx.mode : undefined;
|
|
443
|
+
restore(ctx);
|
|
444
|
+
conflict = findTaskConflict(pi.getAllTools());
|
|
445
|
+
coldRun = true;
|
|
446
|
+
activeRun = false;
|
|
447
|
+
frozenProjection = "";
|
|
448
|
+
taskWidgetVisible = true;
|
|
449
|
+
taskWidgetExpanded = false;
|
|
450
|
+
registerTools();
|
|
451
|
+
notifyProblem(ctx);
|
|
452
|
+
updateTaskWidget(ctx);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
456
|
+
restore(ctx);
|
|
457
|
+
taskWidgetExpanded = false;
|
|
458
|
+
coldRun = true;
|
|
459
|
+
activeRun = false;
|
|
460
|
+
frozenProjection = "";
|
|
461
|
+
notifyProblem(ctx);
|
|
462
|
+
updateTaskWidget(ctx);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
pi.on("session_compact", () => {
|
|
466
|
+
coldRun = true;
|
|
467
|
+
frozenProjection = "";
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
pi.on("agent_start", () => {
|
|
471
|
+
activeRun = true;
|
|
472
|
+
if (coldRun) {
|
|
473
|
+
frozenProjection = !problemMessage() ? projectTasks(snapshot()) : "";
|
|
474
|
+
coldRun = false;
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
pi.on("agent_settled", () => {
|
|
479
|
+
activeRun = false;
|
|
480
|
+
frozenProjection = "";
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
pi.on("context", (event) => {
|
|
484
|
+
if (!activeRun || !frozenProjection || problemMessage()) return;
|
|
485
|
+
const messages = injectTaskProjection(event.messages, frozenProjection);
|
|
486
|
+
if (messages) return { messages: messages as typeof event.messages };
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
pi.on("session_shutdown", () => {
|
|
490
|
+
try {
|
|
491
|
+
ui?.setWidget(TASK_WIDGET_KEY, undefined);
|
|
492
|
+
} catch {
|
|
493
|
+
// The interactive UI may already be disposed.
|
|
494
|
+
}
|
|
495
|
+
ui = undefined;
|
|
496
|
+
uiMode = undefined;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export { TOOL_NAMES as TASK_TOOL_NAMES };
|
|
501
|
+
|
|
502
|
+
function mutationWillCloseBatch(
|
|
503
|
+
snapshot: TaskSnapshot,
|
|
504
|
+
id: number,
|
|
505
|
+
status: TaskItem["status"] | undefined,
|
|
506
|
+
) {
|
|
507
|
+
if (status !== "done" && status !== "dropped") return false;
|
|
508
|
+
return snapshot.items.every(
|
|
509
|
+
(item) =>
|
|
510
|
+
item.id === id || item.status === "done" || item.status === "dropped",
|
|
511
|
+
);
|
|
512
|
+
}
|