@evandu/super-pi 0.1.1 → 0.1.2
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/agents/super-pi-task/core.ts +748 -485
- package/agents/super-pi-task/index.ts +198 -141
- package/dist/client/assets/index-CHw4ZoY4.js +43 -0
- package/dist/client/assets/{index-CsbZzuMy.css → index-WlIFA_Dt.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/application/run-service.d.ts +48 -1
- package/dist/server/application/run-service.js +78 -2
- package/dist/server/capability.d.ts +89 -0
- package/dist/server/capability.js +136 -0
- package/dist/server/command-handler.js +4 -0
- package/dist/server/http-server.js +20 -0
- package/dist/server/index.js +25 -2
- package/dist/server/runtime.d.ts +1 -0
- package/dist/server/runtime.js +13 -2
- package/dist/server/store.d.ts +42 -0
- package/dist/server/store.js +144 -1
- package/dist/server/task-tool.d.ts +21 -0
- package/dist/server/task-tool.js +21 -0
- package/dist/server/types.d.ts +34 -0
- package/dist/server/workflow-engine.d.ts +129 -0
- package/dist/server/workflow-engine.js +253 -0
- package/dist/server/workflow.d.ts +287 -0
- package/dist/server/workflow.js +387 -0
- package/package.json +5 -1
- package/dist/client/assets/index-BMQsEdio.js +0 -24
|
@@ -1,141 +1,198 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import {
|
|
3
|
-
TASK_TOOL_NAME,
|
|
4
|
-
describeTask,
|
|
5
|
-
executeTask,
|
|
6
|
-
readTaskConfig,
|
|
7
|
-
type TaskParams,
|
|
8
|
-
} from "./core.ts";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
} from "./core.ts";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
type
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import {
|
|
3
|
+
TASK_TOOL_NAME,
|
|
4
|
+
describeTask,
|
|
5
|
+
executeTask,
|
|
6
|
+
readTaskConfig,
|
|
7
|
+
type TaskParams,
|
|
8
|
+
} from "./core.ts";
|
|
9
|
+
import {
|
|
10
|
+
WORKFLOW_DESCRIPTION,
|
|
11
|
+
WORKFLOW_TOOL_NAME,
|
|
12
|
+
executeWorkflow,
|
|
13
|
+
readWorkflowConfig,
|
|
14
|
+
type WorkflowParams,
|
|
15
|
+
} from "./core.ts";
|
|
16
|
+
import {
|
|
17
|
+
TODO_DESCRIPTION,
|
|
18
|
+
TODO_TOOL_NAME,
|
|
19
|
+
executeTodo,
|
|
20
|
+
readTodoConfig,
|
|
21
|
+
type TodoParams,
|
|
22
|
+
} from "./core.ts";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The Pi adapter for the `task` and `todowrite` tools. Everything with a decision in it is in
|
|
26
|
+
* `./core.ts`; this file is the mounting hardware and nothing else.
|
|
27
|
+
*
|
|
28
|
+
* `@sinclair/typebox` is Pi's VIRTUAL MODULE (packages/coding-agent/src/core/extensions/virtual-modules.ts),
|
|
29
|
+
* which is why importing it works here and would not work anywhere else in this repository: it is not in
|
|
30
|
+
* `package.json` and is not installed in `node_modules`. Nothing outside Pi may import this file for that
|
|
31
|
+
* reason - `scripts/task-tool.test.mjs` drives `core.ts` instead, and reads this file as TEXT for the
|
|
32
|
+
* things that cannot be executed outside Pi.
|
|
33
|
+
*
|
|
34
|
+
* The parameter schema is OpenCode's `task.ts:43-52` verbatim, including `task_id`. `task_id` is
|
|
35
|
+
* accepted and ignored in this step: resuming a sub-agent's session would mean keeping its Pi process
|
|
36
|
+
* and its transcript alive between calls, and `runtime.ts` kills the primary's Pi after every turn, so
|
|
37
|
+
* there is nothing for a session id to point at yet. It is kept in the schema because removing it would
|
|
38
|
+
* make this tool incompatible with a model that has been told the OpenCode shape, and because the next
|
|
39
|
+
* step is precisely the thing that gives it meaning.
|
|
40
|
+
*/
|
|
41
|
+
const Parameters = Type.Object({
|
|
42
|
+
description: Type.String({ description: "A short (3-5 words) description of the task" }),
|
|
43
|
+
prompt: Type.String({ description: "The task for the agent to perform" }),
|
|
44
|
+
subagent_type: Type.String({ description: "The type of specialized agent to use for this task" }),
|
|
45
|
+
task_id: Type.Optional(Type.String({ description: "Resume a previous task when supported" })),
|
|
46
|
+
background: Type.Optional(Type.Boolean({ description: "Run asynchronously and deliver the result as a follow-up message" })),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The `todowrite` parameter schema, OpenCode's `tool/todo.ts:6-8` in the TypeBox dialect this adapter
|
|
51
|
+
* speaks. `todos` is the WHOLE list every time - it replaces the list rather than adding to it - which is
|
|
52
|
+
* stated in the per-property description, because a model that assumes it appends will send one item and
|
|
53
|
+
* watch the other forty disappear.
|
|
54
|
+
*
|
|
55
|
+
* `id` is absent from the schema on purpose and this is the ONE place Super Pi's copy differs from
|
|
56
|
+
* OpenCode's: OpenCode's `Todo.Info` carries only content/status/priority. Super Pi keeps those three and
|
|
57
|
+
* ADDS a stable id server-side (`normalizeTodos`), which the schema does not mention so the model is not
|
|
58
|
+
* invited to invent ids it will not control.
|
|
59
|
+
*/
|
|
60
|
+
const TodoParameters = Type.Object({
|
|
61
|
+
todos: Type.Array(Type.Object({
|
|
62
|
+
content: Type.String({ description: "The task text, specific and actionable" }),
|
|
63
|
+
status: Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("completed")], { description: "pending = not started, in_progress = being worked on now (exactly one at a time), completed = finished successfully" }),
|
|
64
|
+
priority: Type.Optional(Type.Union([Type.Literal("high"), Type.Literal("medium"), Type.Literal("low")], { description: "How urgent this item is" })),
|
|
65
|
+
}), { description: "The COMPLETE updated todo list - this replaces the previous list entirely, so include every item you still want on screen" }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The only structural type this file needs, declared locally.
|
|
70
|
+
*
|
|
71
|
+
* It was `import type { ExtensionFactory } from "F:/code/open_agents_repo/pi/..."` before, which is an
|
|
72
|
+
* absolute path into one machine's checkout: fine there, unresolvable on any other, and it made this
|
|
73
|
+
* file impossible to open without that checkout present. Pi is a runtime, not a build dependency of
|
|
74
|
+
* Super Pi, so there is no package to import its types from - declaring the two shapes this file uses is
|
|
75
|
+
* the honest alternative, and `npm run check` would not have caught the old one because `agents/` is
|
|
76
|
+
* outside every tsconfig.
|
|
77
|
+
*/
|
|
78
|
+
type ExtensionToolDefinition = {
|
|
79
|
+
name: string;
|
|
80
|
+
label: string;
|
|
81
|
+
description: string;
|
|
82
|
+
parameters: unknown;
|
|
83
|
+
execute(toolCallId: string, params: unknown, signal: AbortSignal | undefined, onUpdate: unknown, context: { cwd: string }): Promise<unknown>;
|
|
84
|
+
prepareLoadout?(loadout: unknown): { descriptions?: Record<string, string> } | undefined;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* The `workflow` parameter schema.
|
|
88
|
+
*
|
|
89
|
+
* `agentId` rather than `subagent_type`, and the difference is that it is REQUIRED here. A single
|
|
90
|
+
* dispatch can infer itself from its prompt - a model asking for one delegation is obvious about which
|
|
91
|
+
* agent it wants - but a plan of up to eight steps cannot: a step whose agent is unspecified would have to
|
|
92
|
+
* be guessed by the server, and a server that guesses which specialist to run is exactly the
|
|
93
|
+
* role-name branch this whole feature refuses to have.
|
|
94
|
+
*
|
|
95
|
+
* `onFailure` is a plain string rather than a TypeBox union so the SERVER owns the whitelist. A closed
|
|
96
|
+
* enum here would be a second list that could disagree with `parseFailurePolicy`, and the message a model
|
|
97
|
+
* gets for a bad value is better written by the side that knows the real options.
|
|
98
|
+
*/
|
|
99
|
+
const WorkflowParameters = Type.Object({
|
|
100
|
+
summary: Type.String({ description: "One line saying what this whole plan is for, shown to the user above the timeline" }),
|
|
101
|
+
onFailure: Type.Optional(Type.String({ description: "What to do when a step FAILS TO RUN: stop (default), skip, retry:1, retry:2 or retry:3" })),
|
|
102
|
+
steps: Type.Array(Type.Object({
|
|
103
|
+
agentId: Type.String({ description: "The dispatchable sub-agent type to run, e.g. scout" }),
|
|
104
|
+
label: Type.Optional(Type.String({ description: "3-4 words saying what this step is FOR, shown to the user only" })),
|
|
105
|
+
prompt: Type.String({ description: "The instruction for THIS step alone. Earlier steps' output is added for you." }),
|
|
106
|
+
}), { minItems: 1, maxItems: 8, description: "The steps, in the order they should run" }),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
type ExtensionApi = { registerTool(tool: ExtensionToolDefinition): void };
|
|
110
|
+
|
|
111
|
+
const extension = (pi: ExtensionApi): void => {
|
|
112
|
+
// Read ONCE at load, not per call: the environment cannot change under a running Pi, and `prepareLoadout`
|
|
113
|
+
// is synchronous - it cannot await a fetch for a roster - so the roster has to already be here.
|
|
114
|
+
const config = readTaskConfig(process.env);
|
|
115
|
+
// Same rule for the todo tool, and for a stronger reason: the todo endpoint is derived from the same
|
|
116
|
+
// listen address as the task endpoint, so reading it per call would mean re-reading an environment
|
|
117
|
+
// that cannot have changed since the process started.
|
|
118
|
+
const todoConfig = readTodoConfig(process.env);
|
|
119
|
+
// Same rule for the workflow tool: one read at load, because the environment cannot change under a
|
|
120
|
+
// running Pi. The capability it reads is minted for THIS turn and dies with it, so it is valid for
|
|
121
|
+
// exactly as long as this process is - which is the whole reason a sub-agent cannot present one.
|
|
122
|
+
const workflowConfig = readWorkflowConfig(process.env);
|
|
123
|
+
|
|
124
|
+
pi.registerTool({
|
|
125
|
+
name: TASK_TOOL_NAME,
|
|
126
|
+
label: "Task",
|
|
127
|
+
// The fallback for the seconds between registration and the first `prepareLoadout`: the guidance with
|
|
128
|
+
// no roster, which is still true text rather than a placeholder.
|
|
129
|
+
description: describeTask(config.roster),
|
|
130
|
+
promptSnippet: "delegate substantial work to a specialized subagent",
|
|
131
|
+
parameters: Parameters,
|
|
132
|
+
/**
|
|
133
|
+
* Injects the roster into the description the model is shown, on every loadout.
|
|
134
|
+
*
|
|
135
|
+
* Pi's own mechanism for a tool that lists other tools, and the reason it is used rather than
|
|
136
|
+
* editing the system prompt: `ToolDefinition.prepareLoadout` is documented for exactly this
|
|
137
|
+
* (types.ts:620-622 - "Tools that orchestrate other tools use it"), it is called whenever the active
|
|
138
|
+
* tools change, and a description that Pi receives is a description that cannot drift from the tool
|
|
139
|
+
* set it belongs to. `describeTask()` in OpenCode does the same thing one layer up, inside
|
|
140
|
+
* `registry.ts`.
|
|
141
|
+
*
|
|
142
|
+
* Read from the environment the server filled at spawn time, never from a list written here, so a
|
|
143
|
+
* sub-agent added to `agents/*.md` while the server runs is dispatchable on the next message.
|
|
144
|
+
*/
|
|
145
|
+
prepareLoadout: () => ({ descriptions: { [TASK_TOOL_NAME]: describeTask(config.roster) } }),
|
|
146
|
+
execute: async (_toolCallId, params, signal, _onUpdate, context) =>
|
|
147
|
+
await executeTask(params as TaskParams, context, config, { signal: signal ?? undefined }),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* `todowrite`, registered SECOND and for one reason: order in the tool list is the order a model reads
|
|
152
|
+
* them in, and a plan is worth more in front of the eye than a dispatcher is. Nothing depends on the
|
|
153
|
+
* order functionally - `runtime.ts` names both tools in its allowlist and Pi activates whichever of them
|
|
154
|
+
* the agent's own `tools:` map allows.
|
|
155
|
+
*
|
|
156
|
+
* NO `prepareLoadout`, and that is a decision rather than an omission. The task tool needs one because
|
|
157
|
+
* its description embeds the ROSTER, which changes on disk while the server runs; the todo description
|
|
158
|
+
* embeds nothing that changes, and the thing that does change - the current list - is the tool's own
|
|
159
|
+
* OUTPUT, which the model reads back from its own transcript on every subsequent turn. Re-injecting the
|
|
160
|
+
* list into the description would be a second source of truth for the same fact, and it would put a
|
|
161
|
+
* snapshot of the plan in front of the model that could disagree with the one its history already
|
|
162
|
+
* contains.
|
|
163
|
+
*/
|
|
164
|
+
pi.registerTool({
|
|
165
|
+
name: TODO_TOOL_NAME,
|
|
166
|
+
label: "Todo",
|
|
167
|
+
description: TODO_DESCRIPTION,
|
|
168
|
+
promptSnippet: "track multi-step work as a live task list the user watches",
|
|
169
|
+
parameters: TodoParameters,
|
|
170
|
+
execute: async (_toolCallId, params, signal) =>
|
|
171
|
+
await executeTodo(params as TodoParams, todoConfig, { signal: signal ?? undefined }),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* `workflow`, registered THIRD and last.
|
|
176
|
+
*
|
|
177
|
+
* Last on purpose: a tool list is read in order, and this is the one a model should reach for LAST. It
|
|
178
|
+
* is also the most expensive call available - it blocks a whole pipeline of sub-agent runs - so the
|
|
179
|
+
* entry before it in the list being `todowrite` (a plan, free) rather than `workflow` is doing real work:
|
|
180
|
+
* the cheap way to express a plan should be what a model sees first.
|
|
181
|
+
*
|
|
182
|
+
* NO `prepareLoadout`, unlike `task`, because unlike `task` its description embeds nothing that changes
|
|
183
|
+
* while the server runs. It does not list the roster: the ids it accepts are the same ones `task` lists
|
|
184
|
+
* one entry above, in the same loadout, so injecting them a second time would be the same fact in two
|
|
185
|
+
* places free to disagree - and its description points the model at that list rather than repeating it.
|
|
186
|
+
*/
|
|
187
|
+
pi.registerTool({
|
|
188
|
+
name: WORKFLOW_TOOL_NAME,
|
|
189
|
+
label: "Workflow",
|
|
190
|
+
description: WORKFLOW_DESCRIPTION,
|
|
191
|
+
promptSnippet: "run an ordered plan of sub-agents, each handed the previous step's output",
|
|
192
|
+
parameters: WorkflowParameters,
|
|
193
|
+
execute: async (_toolCallId, params, signal) =>
|
|
194
|
+
await executeWorkflow(params as WorkflowParams, workflowConfig, { signal: signal ?? undefined }),
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export default extension;
|