@evandu/super-pi 0.1.46 → 0.1.47

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.
@@ -38,6 +38,7 @@
38
38
  */
39
39
  export const TASK_TOOL_NAME = "task";
40
40
  export const TASK_ENDPOINT_ENV = "SUPER_PI_TASK_ENDPOINT";
41
+ export const TASK_EXEC_ENDPOINT_ENV = "SUPER_PI_EXEC_ENDPOINT";
41
42
  export const TASK_ROSTER_ENV = "SUPER_PI_TASK_ROSTER";
42
43
  export const TASK_CONVERSATION_ENV = "SUPER_PI_TASK_CONVERSATION";
43
44
  export const TASK_DEPTH_ENV = "SUPER_PI_TASK_DEPTH";
@@ -200,6 +201,8 @@ export function renderTaskResult(input: { id: string; state: "completed" | "erro
200
201
  /** What the extension learns from its own environment before it can dispatch anything. */
201
202
  export type TaskToolConfig = {
202
203
  endpoint: string;
204
+ /** The exec endpoint for background shell commands. Opaque to the caller. */
205
+ execEndpoint: string;
203
206
  conversationId: string;
204
207
  depth: number;
205
208
  roster: RosterEntry[];
@@ -223,6 +226,7 @@ export function readTaskConfig(env: Record<string, string | undefined>): TaskToo
223
226
  const capability = env[TASK_CAPABILITY_ENV];
224
227
  return {
225
228
  endpoint: env[TASK_ENDPOINT_ENV] ?? "",
229
+ execEndpoint: env[TASK_EXEC_ENDPOINT_ENV] ?? "",
226
230
  conversationId: env[TASK_CONVERSATION_ENV] ?? "",
227
231
  // An unreadable depth is 0, the primary agent's own value, rather than a number that would claim to
228
232
  // be nested and trip the server's refusal for a reason that has nothing to do with this call.
@@ -258,6 +262,7 @@ export function refuseTask(config: TaskToolConfig, params: TaskParams): string |
258
262
  if (!params.subagent_type.trim()) return `subagent_type is required. Available: ${config.roster.map((entry) => entry.id).join(", ")}.`;
259
263
  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.`;
260
264
  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.`;
261
266
  return undefined;
262
267
  }
263
268
 
@@ -284,6 +289,36 @@ export async function executeTask(
284
289
  const refusal = refuseTask(config, params);
285
290
  if (refusal) return text(renderTaskResult({ id: "refused", state: "error", text: refusal }), true);
286
291
 
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
+
287
322
  const send = deps.fetch ?? globalFetch;
288
323
  let response: Awaited<ReturnType<TaskFetch>>;
289
324
  try {
@@ -298,7 +333,7 @@ export async function executeTask(
298
333
  prompt: params.prompt,
299
334
  description: params.description,
300
335
  depth: config.depth,
301
- background: params.background === true,
336
+ background: false,
302
337
  // Always sent, even when undefined, so a body that omits the field is unambiguously a caller that
303
338
  // did not have one - which the server treats as a refusal, not as a legacy request to let through.
304
339
  capability: config.capability,