@maxiaochao/pi-toolkit 0.10.5 → 0.10.6
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/CHANGELOG.md +7 -0
- package/README.md +26 -4
- package/package.json +3 -5
- package/packages/herdr-subagents/CHANGELOG.md +6 -0
- package/packages/herdr-subagents/README.md +12 -1
- package/packages/herdr-subagents/TESTING.md +4 -3
- package/packages/herdr-subagents/agents/analyst.md +8 -0
- package/packages/herdr-subagents/extensions/herdr-subagents.ts +1 -1
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/parameters.ts +2 -2
- package/packages/herdr-subagents/src/status.ts +95 -0
- package/packages/herdr-subagents/src/tool.ts +13 -2
- package/packages/herdr-subagents/src/validation.ts +2 -1
- package/packages/herdr-subagents/tests/extension-contract.ts +4 -0
- package/packages/herdr-subagents/tests/run-tests.mjs +40 -2
- package/skills/agent-team/SKILL.md +33 -57
- package/skills/agent-team/models.json +19 -12
- package/packages/todo/CHANGELOG.md +0 -9
- package/packages/todo/README.md +0 -73
- package/packages/todo/TESTING.md +0 -22
- package/packages/todo/docs/demo.gif +0 -0
- package/packages/todo/docs/screenshot.png +0 -0
- package/packages/todo/extensions/todo.ts +0 -279
- package/packages/todo/package.json +0 -25
- package/packages/todo/src/todo-state.ts +0 -159
- package/packages/todo/tests/todo-extension.mjs +0 -134
- package/packages/todo/tests/todo-state.mjs +0 -108
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import registerTodo from "../extensions/todo.ts";
|
|
3
|
-
|
|
4
|
-
const theme = {
|
|
5
|
-
fg(_color, text) { return text; },
|
|
6
|
-
bold(text) { return text; },
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
function createHarness(branch = [], hasUI = true) {
|
|
10
|
-
const handlers = new Map();
|
|
11
|
-
const commands = new Map();
|
|
12
|
-
let tool;
|
|
13
|
-
let currentBranch = branch;
|
|
14
|
-
let widget;
|
|
15
|
-
let historyComponent;
|
|
16
|
-
let renderRequests = 0;
|
|
17
|
-
const tui = { requestRender() { renderRequests += 1; } };
|
|
18
|
-
const ui = {
|
|
19
|
-
setWidget(_key, value) {
|
|
20
|
-
if (value === undefined) {
|
|
21
|
-
widget = undefined;
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
widget = value(tui, theme);
|
|
25
|
-
},
|
|
26
|
-
notify() {},
|
|
27
|
-
async custom(factory) {
|
|
28
|
-
historyComponent = factory(tui, theme, {}, () => {});
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
const pi = {
|
|
32
|
-
on(name, handler) { handlers.set(name, handler); },
|
|
33
|
-
registerTool(definition) { tool = definition; },
|
|
34
|
-
registerCommand(name, definition) { commands.set(name, definition); },
|
|
35
|
-
};
|
|
36
|
-
const childGuard = process.env.PI_HERDR_SUBAGENTS_CHILD;
|
|
37
|
-
delete process.env.PI_HERDR_SUBAGENTS_CHILD;
|
|
38
|
-
try {
|
|
39
|
-
registerTodo(pi);
|
|
40
|
-
} finally {
|
|
41
|
-
if (childGuard === undefined) delete process.env.PI_HERDR_SUBAGENTS_CHILD;
|
|
42
|
-
else process.env.PI_HERDR_SUBAGENTS_CHILD = childGuard;
|
|
43
|
-
}
|
|
44
|
-
const context = {
|
|
45
|
-
hasUI,
|
|
46
|
-
mode: hasUI ? "tui" : "print",
|
|
47
|
-
ui,
|
|
48
|
-
sessionManager: { getBranch: () => currentBranch },
|
|
49
|
-
};
|
|
50
|
-
return {
|
|
51
|
-
handlers,
|
|
52
|
-
commands,
|
|
53
|
-
context,
|
|
54
|
-
get tool() { return tool; },
|
|
55
|
-
get widget() { return widget; },
|
|
56
|
-
get historyComponent() { return historyComponent; },
|
|
57
|
-
get renderRequests() { return renderRequests; },
|
|
58
|
-
setBranch(value) { currentBranch = value; },
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function toolResult(todos, nextId) {
|
|
63
|
-
return {
|
|
64
|
-
type: "message",
|
|
65
|
-
message: {
|
|
66
|
-
role: "toolResult",
|
|
67
|
-
toolName: "todo",
|
|
68
|
-
details: { action: "list", todos, nextId },
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const branch = [toolResult([
|
|
74
|
-
{ id: 1, text: "Closed history", status: "done" },
|
|
75
|
-
{ id: 2, text: "Current work", status: "doing" },
|
|
76
|
-
{ id: 3, text: "Later work", status: "todo" },
|
|
77
|
-
], 4)];
|
|
78
|
-
const harness = createHarness(branch);
|
|
79
|
-
await harness.handlers.get("session_start")({}, harness.context);
|
|
80
|
-
assert.ok(harness.tool, "Todo tool is registered");
|
|
81
|
-
assert.deepEqual(harness.tool.parameters.properties.action.enum, ["plan", "add_step", "complete_current", "cancel_current", "list"]);
|
|
82
|
-
assert.equal(harness.renderRequests, 1, "mounting the widget explicitly requests a TUI render");
|
|
83
|
-
const mountedWidget = harness.widget;
|
|
84
|
-
const initialWidget = mountedWidget.render(100).join("\n");
|
|
85
|
-
assert.match(initialWidget, /1\. Current work/);
|
|
86
|
-
assert.match(initialWidget, /2\. Later work/);
|
|
87
|
-
assert.doesNotMatch(initialWidget, /#2|#3|Closed history|done|cancelled/, "widget hides IDs and closed history");
|
|
88
|
-
|
|
89
|
-
const completed = await harness.tool.execute("complete", {
|
|
90
|
-
action: "complete_current",
|
|
91
|
-
expected_current_id: 2,
|
|
92
|
-
}, undefined, undefined, harness.context);
|
|
93
|
-
assert.equal(harness.widget, mountedWidget, "live updates keep the mounted widget component");
|
|
94
|
-
assert.ok(harness.renderRequests >= 2, "state change invalidates the widget and requests another render");
|
|
95
|
-
const updatedWidget = mountedWidget.render(100).join("\n");
|
|
96
|
-
assert.match(updatedWidget, /1\. Later work/);
|
|
97
|
-
assert.doesNotMatch(updatedWidget, /Current work/, "mounted widget reads current state instead of a captured row snapshot");
|
|
98
|
-
assert.equal(completed.content[0].text, "CURRENT plan\n#3 [doing] Later work");
|
|
99
|
-
|
|
100
|
-
await harness.commands.get("todos").handler("", harness.context);
|
|
101
|
-
const history = harness.historyComponent.render(100).join("\n");
|
|
102
|
-
assert.match(history, /#1 Closed history/);
|
|
103
|
-
assert.match(history, /#2 Current work/);
|
|
104
|
-
assert.match(history, /#3 Later work/, "/todos alone exposes complete branch history");
|
|
105
|
-
|
|
106
|
-
await harness.tool.execute("empty", { action: "plan", items: [] }, undefined, undefined, harness.context);
|
|
107
|
-
assert.equal(harness.widget, undefined, "widget disappears when the current plan is done or cancelled");
|
|
108
|
-
assert.ok(harness.renderRequests >= 3, "unmounting the widget requests a render");
|
|
109
|
-
|
|
110
|
-
harness.setBranch([toolResult([
|
|
111
|
-
{ id: 10, text: "Selected branch only", status: "doing" },
|
|
112
|
-
{ id: 11, text: "Selected branch next", status: "todo" },
|
|
113
|
-
], 12)]);
|
|
114
|
-
await harness.handlers.get("session_tree")({}, harness.context);
|
|
115
|
-
const branchList = await harness.tool.execute("branch-list", { action: "list" }, undefined, undefined, harness.context);
|
|
116
|
-
assert.equal(branchList.content[0].text, "CURRENT plan\n#10 [doing] Selected branch only\n#11 [todo] Selected branch next");
|
|
117
|
-
assert.doesNotMatch(branchList.content[0].text, /Closed history|Later work/, "tree navigation reconstructs only getBranch snapshots");
|
|
118
|
-
|
|
119
|
-
const serialized = createHarness([], false);
|
|
120
|
-
await serialized.handlers.get("session_start")({}, serialized.context);
|
|
121
|
-
const [planned, firstCompletion, repeatedCompletion] = await Promise.all([
|
|
122
|
-
serialized.tool.execute("plan", { action: "plan", items: ["First", "Second", "Third"] }, undefined, undefined, serialized.context),
|
|
123
|
-
serialized.tool.execute("complete-one", { action: "complete_current", expected_current_id: 1 }, undefined, undefined, serialized.context),
|
|
124
|
-
serialized.tool.execute("complete-repeat", { action: "complete_current", expected_current_id: 1 }, undefined, undefined, serialized.context),
|
|
125
|
-
]);
|
|
126
|
-
assert.equal(planned.content[0].text, "CURRENT plan\n#1 [doing] First\n#2 [todo] Second\n#3 [todo] Third");
|
|
127
|
-
assert.equal(firstCompletion.content[0].text, "CURRENT plan\n#2 [doing] Second\n#3 [todo] Third");
|
|
128
|
-
assert.equal(repeatedCompletion.isError, true, "same-turn repeated completion fails after the first queued call advances");
|
|
129
|
-
assert.match(repeatedCompletion.content[0].text, /expected current #1, but current is #2/);
|
|
130
|
-
assert.match(repeatedCompletion.content[0].text, /#2 \[doing\] Second/);
|
|
131
|
-
const afterConcurrentCalls = await serialized.tool.execute("list", { action: "list" }, undefined, undefined, serialized.context);
|
|
132
|
-
assert.equal(afterConcurrentCalls.content[0].text, "CURRENT plan\n#2 [doing] Second\n#3 [todo] Third");
|
|
133
|
-
|
|
134
|
-
console.log("Todo extension integration tests passed");
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
3
|
-
activeTodoRows,
|
|
4
|
-
activeTodos,
|
|
5
|
-
applyTodoAction,
|
|
6
|
-
formatCurrentPlan,
|
|
7
|
-
restoreTodoState,
|
|
8
|
-
} from "../src/todo-state.ts";
|
|
9
|
-
|
|
10
|
-
const empty = { todos: [], nextId: 1 };
|
|
11
|
-
|
|
12
|
-
let state = applyTodoAction(empty, {
|
|
13
|
-
action: "plan",
|
|
14
|
-
items: ["Inspect the failure", "Implement the fix", "Run verification"],
|
|
15
|
-
});
|
|
16
|
-
assert.deepEqual(state.todos, [
|
|
17
|
-
{ id: 1, text: "Inspect the failure", status: "doing" },
|
|
18
|
-
{ id: 2, text: "Implement the fix", status: "todo" },
|
|
19
|
-
{ id: 3, text: "Run verification", status: "todo" },
|
|
20
|
-
]);
|
|
21
|
-
assert.equal(state.nextId, 4);
|
|
22
|
-
|
|
23
|
-
state = applyTodoAction(state, { action: "complete_current", expected_current_id: 1 });
|
|
24
|
-
assert.deepEqual(state.todos.map(({ id, status }) => ({ id, status })), [
|
|
25
|
-
{ id: 1, status: "done" },
|
|
26
|
-
{ id: 2, status: "doing" },
|
|
27
|
-
{ id: 3, status: "todo" },
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
const stale = applyTodoAction(state, { action: "complete_current", expected_current_id: 1 });
|
|
31
|
-
assert.match(stale.error ?? "", /current is #2/);
|
|
32
|
-
assert.deepEqual(stale.todos, state.todos, "repeated or stale completion is atomic");
|
|
33
|
-
assert.equal(stale.nextId, state.nextId);
|
|
34
|
-
|
|
35
|
-
const missingExpectedId = applyTodoAction(state, { action: "cancel_current" });
|
|
36
|
-
assert.match(missingExpectedId.error ?? "", /expected_current_id is required/);
|
|
37
|
-
assert.deepEqual(missingExpectedId.todos, state.todos);
|
|
38
|
-
|
|
39
|
-
state = applyTodoAction(state, { action: "cancel_current", expected_current_id: 2 });
|
|
40
|
-
assert.deepEqual(state.todos.map(({ id, status }) => ({ id, status })), [
|
|
41
|
-
{ id: 1, status: "done" },
|
|
42
|
-
{ id: 2, status: "cancelled" },
|
|
43
|
-
{ id: 3, status: "doing" },
|
|
44
|
-
], "cancelling only the current step keeps the later plan valid and advances once");
|
|
45
|
-
|
|
46
|
-
state = applyTodoAction(state, { action: "add_step", text: "Review the result" });
|
|
47
|
-
assert.deepEqual(activeTodos(state.todos).map(({ id, status }) => ({ id, status })), [
|
|
48
|
-
{ id: 3, status: "doing" },
|
|
49
|
-
{ id: 4, status: "todo" },
|
|
50
|
-
], "add_step only appends to the current plan");
|
|
51
|
-
|
|
52
|
-
state = applyTodoAction(state, { action: "plan", items: ["Investigate new direction", "Verify new direction"] });
|
|
53
|
-
assert.deepEqual(state.todos, [
|
|
54
|
-
{ id: 1, text: "Inspect the failure", status: "done" },
|
|
55
|
-
{ id: 2, text: "Implement the fix", status: "cancelled" },
|
|
56
|
-
{ id: 3, text: "Run verification", status: "cancelled" },
|
|
57
|
-
{ id: 4, text: "Review the result", status: "cancelled" },
|
|
58
|
-
{ id: 5, text: "Investigate new direction", status: "doing" },
|
|
59
|
-
{ id: 6, text: "Verify new direction", status: "todo" },
|
|
60
|
-
], "replanning preserves closed history and cancels every old unfinished step");
|
|
61
|
-
assert.equal(state.nextId, 7, "replanning allocates fresh monotonic IDs");
|
|
62
|
-
assert.deepEqual(activeTodoRows(state.todos).map(({ position, todo }) => ({ position, id: todo.id })), [
|
|
63
|
-
{ position: 1, id: 5 },
|
|
64
|
-
{ position: 2, id: 6 },
|
|
65
|
-
], "display positions are contiguous and independent of internal IDs");
|
|
66
|
-
assert.equal(formatCurrentPlan(state.todos), "CURRENT plan\n#5 [doing] Investigate new direction\n#6 [todo] Verify new direction");
|
|
67
|
-
|
|
68
|
-
const invalidPlan = applyTodoAction(state, { action: "plan", items: ["valid", " "] });
|
|
69
|
-
assert.ok(invalidPlan.error);
|
|
70
|
-
assert.deepEqual(invalidPlan.todos, state.todos, "invalid plan text leaves history and current plan unchanged");
|
|
71
|
-
|
|
72
|
-
const repeatedText = applyTodoAction(state, { action: "add_step", text: "Verify new direction" });
|
|
73
|
-
assert.equal(repeatedText.error, undefined, "step text is not interpreted as a marker or uniqueness key");
|
|
74
|
-
assert.equal(repeatedText.todos.at(-1)?.text, "Verify new direction");
|
|
75
|
-
|
|
76
|
-
state = applyTodoAction(state, { action: "plan", items: [] });
|
|
77
|
-
assert.equal(activeTodos(state.todos).length, 0, "an empty plan leaves no current plan");
|
|
78
|
-
assert.equal(formatCurrentPlan(state.todos), "CURRENT plan: none");
|
|
79
|
-
assert.equal(state.nextId, 7, "an empty plan does not reset or consume IDs");
|
|
80
|
-
assert.equal(state.todos.find(({ id }) => id === 1)?.status, "done", "empty plan retains closed history");
|
|
81
|
-
|
|
82
|
-
const appendedAfterEmpty = applyTodoAction(state, { action: "add_step", text: "A later explicit step" });
|
|
83
|
-
assert.deepEqual(appendedAfterEmpty.todos.at(-1), { id: 7, text: "A later explicit step", status: "doing" });
|
|
84
|
-
|
|
85
|
-
const restored = restoreTodoState([
|
|
86
|
-
{ id: 4, text: "legacy complete", done: true },
|
|
87
|
-
{ id: 2, text: "first active", status: "doing" },
|
|
88
|
-
{ id: 3, text: "second active", status: "doing" },
|
|
89
|
-
], 2);
|
|
90
|
-
assert.deepEqual(restored.todos.map(({ id, status }) => ({ id, status })), [
|
|
91
|
-
{ id: 2, status: "doing" },
|
|
92
|
-
{ id: 3, status: "todo" },
|
|
93
|
-
{ id: 4, status: "done" },
|
|
94
|
-
]);
|
|
95
|
-
assert.equal(restored.nextId, 5, "branch restoration repairs stale ID counters");
|
|
96
|
-
|
|
97
|
-
const legacyReordered = restoreTodoState([
|
|
98
|
-
{ id: 1, text: "demoted by legacy update", status: "todo" },
|
|
99
|
-
{ id: 2, text: "legacy current", status: "doing" },
|
|
100
|
-
{ id: 3, text: "legacy queued", status: "todo" },
|
|
101
|
-
]);
|
|
102
|
-
assert.deepEqual(activeTodos(legacyReordered.todos).map(({ id, status }) => ({ id, status })), [
|
|
103
|
-
{ id: 2, status: "doing" },
|
|
104
|
-
{ id: 1, status: "todo" },
|
|
105
|
-
{ id: 3, status: "todo" },
|
|
106
|
-
], "legacy snapshots project the sole doing step before every queued step");
|
|
107
|
-
|
|
108
|
-
console.log("Todo state tests passed");
|