@evandu/super-pi 0.1.57 → 0.1.59
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.
|
@@ -43,6 +43,11 @@ export const TASK_ROSTER_ENV = "SUPER_PI_TASK_ROSTER";
|
|
|
43
43
|
export const TASK_CONVERSATION_ENV = "SUPER_PI_TASK_CONVERSATION";
|
|
44
44
|
export const TASK_DEPTH_ENV = "SUPER_PI_TASK_DEPTH";
|
|
45
45
|
|
|
46
|
+
/** The independent background-command tool. NOT a sub-agent dispatch: a shell command run asynchronously. */
|
|
47
|
+
export const RUN_ASYNC_TOOL_NAME = "run_async";
|
|
48
|
+
/** Capability env name reused for `run_async` - same per-turn credential, same wire, same server check. */
|
|
49
|
+
export const RUN_ASYNC_CAPABILITY_ENV = "SUPER_PI_TASK_CAPABILITY";
|
|
50
|
+
|
|
46
51
|
/**
|
|
47
52
|
* The `todowrite` extension's half of the same environment contract, declared here for the reason the
|
|
48
53
|
* four above are. `src/server/task-tool.ts` is the other end; `scripts/todo-tool.test.mjs` asserts they
|
|
@@ -72,6 +77,43 @@ export type TodoEntry = { content: string; status: "pending" | "in_progress" | "
|
|
|
72
77
|
/** The three statuses Super Pi renders, as the model writes them. */
|
|
73
78
|
const TODO_STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
74
79
|
|
|
80
|
+
/** Parameters for the `run_async` tool. Plain shell command, no subagent plumbing. */
|
|
81
|
+
export type RunAsyncParams = { command: string; description?: string };
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The model-facing description for the `run_async` tool.
|
|
85
|
+
*
|
|
86
|
+
* THIS IS A SHELL COMMAND, NOT A SUB-AGENT. The tool runs the command via the platform's shell
|
|
87
|
+
* (bash on Unix, PowerShell on Windows) in the conversation's workspace, returns immediately with
|
|
88
|
+
* state="running", and delivers stdout, stderr and exit code as a follow-up turn when the command
|
|
89
|
+
* settles. Use it whenever the user says "in the background", "async", "without blocking me",
|
|
90
|
+
* "run this and tell me later", or "kick off a build" - any work measured in seconds to minutes
|
|
91
|
+
* that they would otherwise be waiting on.
|
|
92
|
+
*
|
|
93
|
+
* Do NOT use `task` with `background: true` for this: `task` dispatches a sub-agent, and a
|
|
94
|
+
* sub-agent for a one-shot shell command is minutes of process startup to do what `run_async`
|
|
95
|
+
* does in tens of milliseconds.
|
|
96
|
+
*/
|
|
97
|
+
export const RUN_ASYNC_DESCRIPTION = `Run a shell command asynchronously in the current workspace and deliver its output as a follow-up message when it finishes.
|
|
98
|
+
|
|
99
|
+
When to use this:
|
|
100
|
+
- The user says "in the background", "async", "kick off", "start a build and tell me when it's done", or anything else that means "do not block me on this".
|
|
101
|
+
- A command is expected to take seconds to minutes (a long build, a test suite, a download, a file watcher).
|
|
102
|
+
- You want to start several independent commands and let them all run in parallel.
|
|
103
|
+
|
|
104
|
+
How it works:
|
|
105
|
+
1. The command runs in the conversation's workspace under the platform's shell (bash on Linux/macOS, PowerShell on Windows).
|
|
106
|
+
2. The call returns immediately. You will see <task id state="running"> in the result.
|
|
107
|
+
3. When the command finishes, its stdout, stderr and exit code arrive in a new turn wrapped in a <task> envelope.
|
|
108
|
+
|
|
109
|
+
Usage notes:
|
|
110
|
+
1. The "command" field is the shell command verbatim - do not wrap it in JSON.
|
|
111
|
+
2. "description" is optional and only labels the row in the right-rail "background tasks" panel.
|
|
112
|
+
3. Do NOT poll, sleep, or call this tool repeatedly to check on the same command. The result arrives on its own.
|
|
113
|
+
4. For commands that should run in the FOREGROUND and whose output you need before the next step, use the bash tool instead.
|
|
114
|
+
5. For delegating a multi-step task to a specialist sub-agent, use the task tool. This tool is for plain shell commands.
|
|
115
|
+
6. The result envelope looks like <task id="..." state="completed|error"><task_result>stdout</task_result></task> or with <task_error> for failures. Read it the same way you would read a sub-agent's result.`;
|
|
116
|
+
|
|
75
117
|
/** The parameters, exactly OpenCode's (`tool/task.ts:43-52`) minus the ones this step does not do. */
|
|
76
118
|
export type TaskParams = {
|
|
77
119
|
description: string;
|
|
@@ -143,8 +185,9 @@ Usage notes:
|
|
|
143
185
|
6. If a subagent's description says it should be used proactively, use it without being asked.
|
|
144
186
|
7. BY DEFAULT A DISPATCH BLOCKS: the call does not return until that subagent has finished, and its answer comes back to you in this same turn. So you CAN dispatch several in one turn and use each answer before writing the next prompt - which is the normal way to chain specialists.
|
|
145
187
|
8. A LATER DISPATCH MUST CARRY THE EARLIER ONE'S ANSWER IN ITS PROMPT, because the next subagent starts with a fresh context and cannot see what the first one found. The tool does not do this for you. Write the findings you need into the next prompt yourself.
|
|
146
|
-
9. Pass background: true
|
|
147
|
-
10.
|
|
188
|
+
9. Pass background: true to run this sub-agent ASYNCHRONOUSLY. The dispatch returns immediately with state="running" and the answer arrives as a follow-up turn when the sub-agent finishes. Do NOT use this for a plain shell command - use the run_async tool instead, which is much faster for a one-shot command and does not spin up an entire sub-agent.
|
|
189
|
+
10. To run a SHELL COMMAND in the background (a long build, a test suite, a watcher, anything measured in seconds to minutes), use the run_async tool. Do NOT pass background: true to the task tool for this.
|
|
190
|
+
11. For three or more steps where each depends on the previous one, prefer the workflow tool: it runs the chain server-side and hands each step the previous step's output without you copying it through your context.`;
|
|
148
191
|
|
|
149
192
|
/**
|
|
150
193
|
* The roster block, OpenCode's `describeTask()` reduced to its two jobs: the ids that are legal, and
|
|
@@ -259,10 +302,9 @@ export function refuseTask(config: TaskToolConfig, params: TaskParams): string |
|
|
|
259
302
|
if (!config.endpoint) return `The task tool is not available in this session: ${TASK_ENDPOINT_ENV} is not set, so there is no Super Pi server to dispatch to. Do the work yourself with your other tools.`;
|
|
260
303
|
if (!config.conversationId) return `The task tool is not available in this session: ${TASK_CONVERSATION_ENV} is not set, so this run cannot be attributed to a conversation. Do the work yourself with your other tools.`;
|
|
261
304
|
if (!config.roster.length) return "This install has no dispatchable sub-agents (no agents/*.md with `mode: subagent`), so there is nobody to delegate to. Do the work yourself with your other tools.";
|
|
262
|
-
if (!params.subagent_type.trim()) return `subagent_type is required. Available: ${config.roster.map((entry) => entry.id).join(", ")}.`;
|
|
305
|
+
if (!params.subagent_type.trim()) return `subagent_type is required. Available: ${config.roster.map((entry) => entry.id).join(", ")}. If you wanted to run a SHELL COMMAND in the background instead of dispatching a sub-agent, use the run_async tool.`;
|
|
263
306
|
if (!config.roster.some((entry) => entry.id === params.subagent_type)) return `${JSON.stringify(params.subagent_type)} is not a dispatchable subagent_type. Available: ${config.roster.map((entry) => entry.id).join(", ")}. Pick one of those, or do the work yourself.`;
|
|
264
307
|
if (!params.prompt.trim()) return "prompt is required and must not be empty: the sub-agent starts with a fresh context and your prompt is everything it will know.";
|
|
265
|
-
if (params.background === true && !config.execEndpoint) return `Background tasks are not available: ${TASK_EXEC_ENDPOINT_ENV} is not set. Do the work yourself with your other tools.`;
|
|
266
308
|
return undefined;
|
|
267
309
|
}
|
|
268
310
|
|
|
@@ -289,36 +331,6 @@ export async function executeTask(
|
|
|
289
331
|
const refusal = refuseTask(config, params);
|
|
290
332
|
if (refusal) return text(renderTaskResult({ id: "refused", state: "error", text: refusal }), true);
|
|
291
333
|
|
|
292
|
-
// Background: true uses the lightweight exec endpoint instead of the subagent dispatcher.
|
|
293
|
-
// This avoids spawning a full Pi agent process for a plain shell command.
|
|
294
|
-
if (params.background === true && config.execEndpoint) {
|
|
295
|
-
const send = deps.fetch ?? globalFetch;
|
|
296
|
-
let response: Awaited<ReturnType<TaskFetch>>;
|
|
297
|
-
try {
|
|
298
|
-
response = await send(config.execEndpoint, {
|
|
299
|
-
method: "POST",
|
|
300
|
-
headers: { "content-type": "application/json", ...(config.token ? { authorization: `Bearer ${config.token}` } : {}) },
|
|
301
|
-
body: JSON.stringify({
|
|
302
|
-
cmd: params.prompt,
|
|
303
|
-
cwd: context.cwd,
|
|
304
|
-
conversationId: config.conversationId,
|
|
305
|
-
capability: config.capability,
|
|
306
|
-
}),
|
|
307
|
-
...(deps.signal ? { signal: deps.signal } : {}),
|
|
308
|
-
});
|
|
309
|
-
} catch (error) {
|
|
310
|
-
return text(renderTaskResult({ id: "unreachable", state: "error", text: `Could not reach the Super Pi server at ${config.execEndpoint}: ${error instanceof Error ? error.message : "unknown error"}` }), true);
|
|
311
|
-
}
|
|
312
|
-
let payload: { sessionId?: string; status?: string; error?: string };
|
|
313
|
-
try { payload = JSON.parse(await response.text()) as typeof payload; }
|
|
314
|
-
catch { return text(renderTaskResult({ id: "unreadable", state: "error", text: `The exec endpoint answered ${response.status} with something that is not JSON.` }), true); }
|
|
315
|
-
const id = payload.sessionId ?? "refused";
|
|
316
|
-
if (payload.status === "running") {
|
|
317
|
-
return text(`<task id="${id}" state="running">\n<summary>${params.description.trim() || params.prompt.slice(0, 60)}</summary>\n<task_result>The command is running in the background. You will be notified automatically when it finishes. Do not sleep, poll for progress, or duplicate this work. Continue with non-overlapping work.</task_result>\n</task>`);
|
|
318
|
-
}
|
|
319
|
-
return text(renderTaskResult({ id, state: "error", text: payload.error ?? `Exec endpoint returned status ${payload.status}` }), true);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
334
|
const send = deps.fetch ?? globalFetch;
|
|
323
335
|
let response: Awaited<ReturnType<TaskFetch>>;
|
|
324
336
|
try {
|
|
@@ -503,6 +515,65 @@ export function refuseTodo(config: TodoToolConfig, params: TodoParams): string |
|
|
|
503
515
|
return undefined;
|
|
504
516
|
}
|
|
505
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Refuses a `run_async` call that cannot work.
|
|
520
|
+
*
|
|
521
|
+
* Two checks, for the same three-case shape `refuseTask` and `refuseTodo` use: the call must reach the
|
|
522
|
+
* server, and the server must be able to attribute it to a conversation. The roster is not consulted
|
|
523
|
+
* because this tool is not a dispatch.
|
|
524
|
+
*/
|
|
525
|
+
export function refuseRunAsync(config: TaskToolConfig, params: RunAsyncParams): string | undefined {
|
|
526
|
+
if (!config.execEndpoint) return `The run_async tool is not available in this session: ${TASK_EXEC_ENDPOINT_ENV} is not set, so there is no Super Pi server to run commands on. Do the work yourself with your other tools.`;
|
|
527
|
+
if (!config.conversationId) return `The run_async tool is not available in this session: ${TASK_CONVERSATION_ENV} is not set, so this run cannot be attributed to a conversation. Do the work yourself with your other tools.`;
|
|
528
|
+
if (typeof params.command !== "string" || !params.command.trim()) return "command is required and must not be empty.";
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Runs one shell command asynchronously and returns the envelope the model reads.
|
|
534
|
+
*
|
|
535
|
+
* Independent of `executeTask` even though both post JSON to the same server: the model reaches for
|
|
536
|
+
* `run_async` when the user wants a background shell command, and reaches for `task` when they want
|
|
537
|
+
* a sub-agent. Routing them through one function would mean sharing a refuse-list, a description,
|
|
538
|
+
* and a wire that a model can never get wrong by accident.
|
|
539
|
+
*/
|
|
540
|
+
export async function executeAsync(
|
|
541
|
+
params: RunAsyncParams,
|
|
542
|
+
context: TaskToolContext,
|
|
543
|
+
config: TaskToolConfig,
|
|
544
|
+
deps: { signal?: AbortSignal; fetch?: TaskFetch } = {},
|
|
545
|
+
): Promise<TaskToolResult> {
|
|
546
|
+
const text = (body: string, isError?: boolean): TaskToolResult => ({ content: [{ type: "text", text: body }], details: undefined, ...(isError ? { isError: true } : {}) });
|
|
547
|
+
const refusal = refuseRunAsync(config, params);
|
|
548
|
+
if (refusal) return text(renderTaskResult({ id: "refused", state: "error", text: refusal }), true);
|
|
549
|
+
|
|
550
|
+
const send = deps.fetch ?? globalFetch;
|
|
551
|
+
let response: Awaited<ReturnType<TaskFetch>>;
|
|
552
|
+
try {
|
|
553
|
+
response = await send(config.execEndpoint, {
|
|
554
|
+
method: "POST",
|
|
555
|
+
headers: { "content-type": "application/json", ...(config.token ? { authorization: `Bearer ${config.token}` } : {}) },
|
|
556
|
+
body: JSON.stringify({
|
|
557
|
+
cmd: params.command,
|
|
558
|
+
cwd: context.cwd,
|
|
559
|
+
conversationId: config.conversationId,
|
|
560
|
+
capability: config.capability,
|
|
561
|
+
}),
|
|
562
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
563
|
+
});
|
|
564
|
+
} catch (error) {
|
|
565
|
+
return text(renderTaskResult({ id: "unreachable", state: "error", text: `Could not reach the Super Pi server at ${config.execEndpoint}: ${error instanceof Error ? error.message : "unknown error"}` }), true);
|
|
566
|
+
}
|
|
567
|
+
let payload: { sessionId?: string; status?: string; error?: string };
|
|
568
|
+
try { payload = JSON.parse(await response.text()) as typeof payload; }
|
|
569
|
+
catch { return text(renderTaskResult({ id: "unreadable", state: "error", text: `The exec endpoint answered ${response.status} with something that is not JSON.` }), true); }
|
|
570
|
+
const id = payload.sessionId ?? "refused";
|
|
571
|
+
if (payload.status === "running") {
|
|
572
|
+
return text(`<task id="${id}" state="running">\n<summary>${(params.description ?? "").trim() || params.command.slice(0, 60)}</summary>\n<task_result>The command is running in the background. You will be notified automatically when it finishes. Do not sleep, poll for progress, or duplicate this work. Continue with non-overlapping work.</task_result>\n</task>`);
|
|
573
|
+
}
|
|
574
|
+
return text(renderTaskResult({ id, state: "error", text: payload.error ?? `Exec endpoint returned status ${payload.status}` }), true);
|
|
575
|
+
}
|
|
576
|
+
|
|
506
577
|
/** One HTTP seam, injected so a test can drive the tool without a socket and production uses global fetch. */
|
|
507
578
|
export type TodoFetch = (url: string, init: { method: string; headers: Record<string, string>; body: string; signal?: AbortSignal }) => Promise<{ ok: boolean; status: number; text(): Promise<string> }>;
|
|
508
579
|
|
|
@@ -21,6 +21,12 @@ import {
|
|
|
21
21
|
readTodoConfig,
|
|
22
22
|
type TodoParams,
|
|
23
23
|
} from "./core.ts";
|
|
24
|
+
import {
|
|
25
|
+
RUN_ASYNC_DESCRIPTION,
|
|
26
|
+
RUN_ASYNC_TOOL_NAME,
|
|
27
|
+
executeAsync,
|
|
28
|
+
type RunAsyncParams,
|
|
29
|
+
} from "./core.ts";
|
|
24
30
|
|
|
25
31
|
/**
|
|
26
32
|
* The Pi adapter for the `task` and `todowrite` tools. Everything with a decision in it is in
|
|
@@ -225,6 +231,31 @@ const extension = (pi: ExtensionApi): void => {
|
|
|
225
231
|
execute: async (_toolCallId, params, signal) =>
|
|
226
232
|
await executeWorkflow(params as WorkflowParams, workflowConfig, { signal: signal ?? undefined }),
|
|
227
233
|
});
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* `run_async` - asynchronous shell command execution. NOT a sub-agent dispatch.
|
|
237
|
+
*
|
|
238
|
+
* Registered LAST so a model scanning the tool list reads `task` (the more powerful delegation
|
|
239
|
+
* primitive) before the more specialised background-command tool. A model reaching for the wrong
|
|
240
|
+
* one is the failure this ordering tries to prevent.
|
|
241
|
+
*
|
|
242
|
+
* SHARES THE EXEC ENDPOINT with `task` when it had `background: true`, but is its OWN tool so the
|
|
243
|
+
* description can say "shell command" rather than "sub-agent with a flag" and refuse-task does not
|
|
244
|
+
* have to make the subagent_type optional in a way that confused a model reading the description.
|
|
245
|
+
*/
|
|
246
|
+
const RunAsyncParameters = Type.Object({
|
|
247
|
+
command: Type.String({ description: "The shell command to run in the workspace. Runs under bash on Linux/macOS and PowerShell on Windows." }),
|
|
248
|
+
description: Type.Optional(Type.String({ description: "3-5 word label shown in the background tasks panel." })),
|
|
249
|
+
});
|
|
250
|
+
pi.registerTool({
|
|
251
|
+
name: RUN_ASYNC_TOOL_NAME,
|
|
252
|
+
label: "Run Async",
|
|
253
|
+
description: RUN_ASYNC_DESCRIPTION,
|
|
254
|
+
promptSnippet: "run a shell command in the background and deliver its output as a follow-up message",
|
|
255
|
+
parameters: RunAsyncParameters,
|
|
256
|
+
execute: async (_toolCallId, params, signal, _onUpdate, context) =>
|
|
257
|
+
await executeAsync(params as RunAsyncParams, context, config, { signal: signal ?? undefined }),
|
|
258
|
+
});
|
|
228
259
|
};
|
|
229
260
|
|
|
230
261
|
export default extension;
|
|
@@ -201,13 +201,19 @@ export function createHttpServer(options) {
|
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
203
|
response.writeHead(202, { "content-type": "application/json; charset=utf-8" }).end(JSON.stringify({ status: "updating" }));
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
// Use npm directly instead of `pi update` to avoid Windows detached+shell exit timing issues.
|
|
205
|
+
// npm's exit code correctly reflects whether the update succeeded.
|
|
206
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
207
|
+
const child = spawn(npmCmd, ["install", "-g", "@evandu/super-pi"], { stdio: "ignore", shell: process.platform === "win32", env: process.env });
|
|
206
208
|
child.on("close", (code) => {
|
|
207
|
-
if (code
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
if (code === 0 || code === null) {
|
|
210
|
+
console.log("Super Pi: npm install completed, triggering restart.");
|
|
211
|
+
const restart = spawn(process.execPath, [join(rootDir, "scripts", "restart-server.mjs")], { cwd: rootDir, detached: true, stdio: "ignore", env: process.env });
|
|
212
|
+
restart.unref();
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
console.error(`Super Pi: npm install failed with code ${code}`);
|
|
216
|
+
}
|
|
211
217
|
});
|
|
212
218
|
child.on("error", (error) => { console.warn(`Super Pi: update spawn failed: ${error instanceof Error ? error.message : "unknown error"}`); });
|
|
213
219
|
child.unref();
|
|
@@ -220,7 +226,12 @@ export function createHttpServer(options) {
|
|
|
220
226
|
}
|
|
221
227
|
try {
|
|
222
228
|
const current = checkDiskVersion(rootDir);
|
|
223
|
-
|
|
229
|
+
// Query the npm registry for the latest @evandu/super-pi version.
|
|
230
|
+
const remoteVersion = await fetch("https://registry.npmjs.org/@evandu/super-pi/latest").then(async (result) => {
|
|
231
|
+
if (!result.ok)
|
|
232
|
+
throw new Error(`npm registry HTTP ${result.status}`);
|
|
233
|
+
return result.json();
|
|
234
|
+
});
|
|
224
235
|
const latestVersion = typeof remoteVersion.version === "string" ? remoteVersion.version : undefined;
|
|
225
236
|
response.writeHead(200, { "content-type": "application/json; charset=utf-8" }).end(JSON.stringify({ ...current, latestVersion, updateAvailable: Boolean(latestVersion && latestVersion !== current.diskVersion) }));
|
|
226
237
|
}
|
|
@@ -371,10 +382,14 @@ export function createHttpServer(options) {
|
|
|
371
382
|
json(403, { error: result.message });
|
|
372
383
|
return;
|
|
373
384
|
}
|
|
374
|
-
// Validate cwd is a known workspace
|
|
375
|
-
|
|
385
|
+
// Validate cwd is a known workspace. Path comparison is normalized so Windows case
|
|
386
|
+
// differences or trailing separators do not produce a false "unknown workspace" failure
|
|
387
|
+
// when the extension hands back the same directory the server handed to it at spawn.
|
|
388
|
+
const normalized = (value) => value.replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
389
|
+
const normalizedCwd = normalized(cwd);
|
|
390
|
+
const workspace = store.snapshot().workspaces.find((w) => normalized(w.path) === normalizedCwd);
|
|
376
391
|
if (!workspace) {
|
|
377
|
-
json(400, { error:
|
|
392
|
+
json(400, { error: `cwd is not a known workspace: ${cwd} (known: ${store.snapshot().workspaces.map((w) => w.path).join(" | ")})` });
|
|
378
393
|
return;
|
|
379
394
|
}
|
|
380
395
|
// Set up the background task record BEFORE spawning, so the UI shows it immediately
|
package/dist/server/runtime.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolve, join } from "node:path";
|
|
|
6
6
|
import { agentSpawnArgs, readAgents } from "./agents.js";
|
|
7
7
|
import { TASK_CAPABILITY_ENV, mintCapability, releaseCapability } from "./capability.js";
|
|
8
8
|
import { resolvePiCommand, shellQuote } from "./pi-command.js";
|
|
9
|
-
import { TASK_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_EXEC_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
9
|
+
import { TASK_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_EXEC_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
10
10
|
import { BLOCKING_UI_METHODS, EXTENSION_UI_METHODS, } from "./types.js";
|
|
11
11
|
import { loadConversationImages } from "./upload.js";
|
|
12
12
|
import { describeToolCall, describeToolCallArgs, describeToolCallInProgress, extractPartialResultText, extractToolResultText, readQuestionCall } from "./tool-calls.js";
|
|
@@ -766,7 +766,7 @@ export class PiRpcRuntime {
|
|
|
766
766
|
// other. They also share the extension's existence - `extensionArgs` is non-empty exactly when
|
|
767
767
|
// `index.ts` is on disk and an endpoint was configured - so there is no case where one is mounted and
|
|
768
768
|
// the other is not.
|
|
769
|
-
const agentArgs = memoryReview ? ["--no-extensions", "--no-skills", "--no-mcp", "--no-context-files", "--no-prompt-templates", "--tools", "memory,skill_manage,session_search"] : await agentSpawnArgs(this.agents.promptDir, agent, { extraAllowedTools: [...(extensionArgs.length ? [TASK_TOOL_NAME, TODO_TOOL_NAME, WORKFLOW_TOOL_NAME, "question"] : []), ...(memoryEnabled ? ["memory", "skill_manage", "session_search"] : [])] });
|
|
769
|
+
const agentArgs = memoryReview ? ["--no-extensions", "--no-skills", "--no-mcp", "--no-context-files", "--no-prompt-templates", "--tools", "memory,skill_manage,session_search"] : await agentSpawnArgs(this.agents.promptDir, agent, { extraAllowedTools: [...(extensionArgs.length ? [TASK_TOOL_NAME, TODO_TOOL_NAME, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, "question"] : []), ...(memoryEnabled ? ["memory", "skill_manage", "session_search"] : [])] });
|
|
770
770
|
// Refused here, with the reason, rather than handed to the spawn: an ENOENT from `spawn` reads as
|
|
771
771
|
// "the app is broken" and hides the one fact that matters, which is where it looked for Pi.
|
|
772
772
|
if (!this.pi.found)
|
|
@@ -25,6 +25,15 @@ import type { AgentDefinition, TodoItem } from "./types.js";
|
|
|
25
25
|
*/
|
|
26
26
|
/** The name the model calls. Also the key Pi's tool allowlist matches on, and the deny key in `tools:`. */
|
|
27
27
|
export declare const TASK_TOOL_NAME = "task";
|
|
28
|
+
/**
|
|
29
|
+
* The independent background-command tool. NOT a sub-agent dispatch.
|
|
30
|
+
*
|
|
31
|
+
* Mirrored from `agents/super-pi-task/core.ts` for the same reason `TASK_TOOL_NAME` is: a tool name
|
|
32
|
+
* is a wire between two files Pi's module loader cannot cross, and `scripts/task-tool.test.mjs`
|
|
33
|
+
* asserts the two copies agree. Renaming on one side alone would fail at registerTool time with a
|
|
34
|
+
* message nobody reads.
|
|
35
|
+
*/
|
|
36
|
+
export declare const RUN_ASYNC_TOOL_NAME = "run_async";
|
|
28
37
|
/**
|
|
29
38
|
* How many task hops are allowed below a primary agent.
|
|
30
39
|
*
|
package/dist/server/task-tool.js
CHANGED
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
*/
|
|
25
25
|
/** The name the model calls. Also the key Pi's tool allowlist matches on, and the deny key in `tools:`. */
|
|
26
26
|
export const TASK_TOOL_NAME = "task";
|
|
27
|
+
/**
|
|
28
|
+
* The independent background-command tool. NOT a sub-agent dispatch.
|
|
29
|
+
*
|
|
30
|
+
* Mirrored from `agents/super-pi-task/core.ts` for the same reason `TASK_TOOL_NAME` is: a tool name
|
|
31
|
+
* is a wire between two files Pi's module loader cannot cross, and `scripts/task-tool.test.mjs`
|
|
32
|
+
* asserts the two copies agree. Renaming on one side alone would fail at registerTool time with a
|
|
33
|
+
* message nobody reads.
|
|
34
|
+
*/
|
|
35
|
+
export const RUN_ASYNC_TOOL_NAME = "run_async";
|
|
27
36
|
/**
|
|
28
37
|
* How many task hops are allowed below a primary agent.
|
|
29
38
|
*
|