@evandu/super-pi 0.1.72 → 0.1.73

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.
@@ -52,6 +52,7 @@ const Parameters = Type.Object({
52
52
  task_id: Type.Optional(Type.String({ description: "Resume a previous task when supported" })),
53
53
  });
54
54
  const QuestionParameters = Type.Object({ question: Type.String(), options: Type.Array(Type.String(), { minItems: 1 }), multiple: Type.Optional(Type.Boolean()), extraNote: Type.Optional(Type.Boolean()) });
55
+ const ToolRegisterParameters = Type.Object({ slug: Type.String({ description: "Lowercase tool slug, e.g. json-viewer" }), name: Type.String({ description: "Human-readable tool name" }), description: Type.Optional(Type.String()), entrypoint: Type.String({ description: "Workspace-relative HTML entrypoint, normally tools/<slug>/index.html" }) });
55
56
 
56
57
  /**
57
58
  * The `todowrite` parameter schema, OpenCode's `tool/todo.ts:6-8` in the TypeBox dialect this adapter
@@ -144,6 +145,18 @@ const extension = (pi: ExtensionApi): void => {
144
145
  // cancellation. The text says what was asked and what was chosen, in words.
145
146
  return text(JSON.stringify({ answers: (payload.answers ?? []).map((answer) => ({ question: p.question, selected: answer.selected, ...(answer.note ? { note: answer.note } : {}) })), ...(payload.summary ? { summary: payload.summary } : {}) }));
146
147
  } });
148
+ pi.registerTool({ name: "tool_register", label: "Register Tool", description: "Register a workspace tool in Super Pi's right sidebar. Use this when the user asks to create or add a right-sidebar tool. First create the HTML/JS/CSS files under the workspace, then call this tool; do not start another HTTP server.", promptSnippet: "register a newly created right-sidebar tool with Super Pi", parameters: ToolRegisterParameters, execute: async (_id, raw, _signal, _update, context): Promise<TaskToolResult> => {
149
+ const endpoint = process.env.SUPER_PI_TOOLS_ENDPOINT;
150
+ const text = (body: string, isError?: boolean): TaskToolResult => ({ content: [{ type: "text", text: body }], details: undefined, ...(isError ? { isError: true } : {}) });
151
+ if (!endpoint) return text("The Super Pi tool registry is not available in this run.", true);
152
+ const p = raw as { slug: string; name: string; description?: string; entrypoint: string };
153
+ try {
154
+ const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...p, workspacePath: context.cwd }) });
155
+ const payload = await response.json() as { id?: string; error?: string };
156
+ if (!response.ok || payload.error) return text(payload.error ?? `Tool registration failed (HTTP ${response.status}).`, true);
157
+ return text(JSON.stringify({ registered: true, id: payload.id, url: `/tools/${payload.id}/` }));
158
+ } catch (error) { return text(`Tool registration could not reach Super Pi: ${error instanceof Error ? error.message : "unknown error"}`, true); }
159
+ });
147
160
  // Read ONCE at load, not per call: the environment cannot change under a running Pi, and `prepareLoadout`
148
161
  // is synchronous - it cannot await a fetch for a roster - so the roster has to already be here.
149
162
  const config = readTaskConfig(process.env);
@@ -171,9 +171,9 @@ export function createHttpServer(options) {
171
171
  }
172
172
  }
173
173
  const body = JSON.parse(raw);
174
- const workspace = store.snapshot().workspaces.find((x) => x.id === body.workspaceId);
174
+ const workspace = store.snapshot().workspaces.find((x) => x.id === body.workspaceId || (body.workspacePath && resolve(x.path) === resolve(body.workspacePath)));
175
175
  if (!workspace || !body.slug || !body.name || !body.entrypoint) {
176
- json(400, { error: "workspaceId, slug, name and entrypoint are required" });
176
+ json(400, { error: "workspaceId (or workspacePath), slug, name and entrypoint are required" });
177
177
  return;
178
178
  }
179
179
  const tool = await toolRegistry.register({ workspaceId: workspace.id, slug: body.slug, name: body.name, description: body.description, entrypoint: body.entrypoint }, workspace.path);
@@ -47,6 +47,7 @@ const execEndpoint = `http://${internalHost}:${port}/api/exec`;
47
47
  const todoEndpoint = `http://${internalHost}:${port}/api/todos`;
48
48
  const workflowEndpoint = `http://${internalHost}:${port}/api/workflow/run`;
49
49
  const questionEndpoint = `http://${internalHost}:${port}/api/question`;
50
+ const toolsEndpoint = `http://${internalHost}:${port}/api/tools`;
50
51
  // ------------------------------------------------------------- Core singletons
51
52
  const store = await openStateStore(dataDir, process.env.SUPER_PI_PERSISTENCE ?? "sqlite");
52
53
  const toolRegistry = new ToolRegistry(dataDir);
@@ -66,7 +67,7 @@ const paths = {
66
67
  };
67
68
  const runtime = process.env.SUPER_PI_RUNTIME === "welcome"
68
69
  ? new WelcomeRuntime()
69
- : new PiRpcRuntime(join(dataDir, "pi-sessions"), undefined, { dir: initialAgentDirs, promptDir: agentPromptDir }, { endpoint: taskEndpoint, execEndpoint, todoEndpoint, workflowEndpoint, questionEndpoint, uploadsDir });
70
+ : new PiRpcRuntime(join(dataDir, "pi-sessions"), undefined, { dir: initialAgentDirs, promptDir: agentPromptDir }, { endpoint: taskEndpoint, execEndpoint, todoEndpoint, workflowEndpoint, questionEndpoint, toolsEndpoint, uploadsDir });
70
71
  const broadcaster = new WsBroadcaster(() => store.snapshot());
71
72
  const memory = new ActiveMemory(join(dataDir, "memories"), store, runtime);
72
73
  process.env.SUPER_PI_MEMORY_ENDPOINT = `http://${internalHost}:${port}/api/memory/tool`;
@@ -450,6 +450,7 @@ export declare class PiRpcRuntime implements AgentRuntime {
450
450
  todoEndpoint?: string;
451
451
  workflowEndpoint?: string;
452
452
  questionEndpoint?: string;
453
+ toolsEndpoint?: string;
453
454
  uploadsDir?: string;
454
455
  });
455
456
  /**
@@ -388,6 +388,7 @@ export class PiRpcRuntime {
388
388
  [TODO_ENDPOINT_ENV]: this.options.todoEndpoint ?? "",
389
389
  [WORKFLOW_ENDPOINT_ENV]: this.options.workflowEndpoint ?? "",
390
390
  SUPER_PI_QUESTION_ENDPOINT: this.options.questionEndpoint ?? "",
391
+ SUPER_PI_TOOLS_ENDPOINT: this.options.toolsEndpoint ?? "",
391
392
  // MINTED HERE, AT SPAWN, rather than read from anywhere: this is the only moment the value is
392
393
  // decided, so it cannot change underneath a running child. `SubagentSupervisor` never calls this
393
394
  // function, so no sub-agent process is ever handed a capability - which is what makes a sub-agent
@@ -438,7 +439,7 @@ export class PiRpcRuntime {
438
439
  // other. They also share the extension's existence - `extensionArgs` is non-empty exactly when
439
440
  // `index.ts` is on disk and an endpoint was configured - so there is no case where one is mounted and
440
441
  // the other is not.
441
- 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"] : [])] });
442
+ 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", "tool_register"] : []), ...(memoryEnabled ? ["memory", "skill_manage", "session_search"] : [])] });
442
443
  // Refused here, with the reason, rather than handed to the spawn: an ENOENT from `spawn` reads as
443
444
  // "the app is broken" and hides the one fact that matters, which is where it looked for Pi.
444
445
  if (!this.pi.found)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",