Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/mcp-instance-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ That response means the instance runtime path is working and correctly refusing

## MCP Tool Groups

The server currently has 196 tool registrations across `workers/mcp/src`.
The server currently has 197 tool registrations across `workers/mcp/src`.
Some are capability-gated and appear only for users with matching agent surfaces, so
`tools/list` on your own connection is the authoritative surface — 23 of those
registrations are gated. The `/health` marker reports the same total from
Expand Down Expand Up @@ -184,7 +184,7 @@ Status: active

Verified live:

- MCP exposed its tool set (26 at the time of this 2026-06-11 record; 196 tool registrations in source today — see MCP Tool Groups above).
- MCP exposed its tool set (26 at the time of this 2026-06-11 record; 197 tool registrations in source today — see MCP Tool Groups above).
- Required instance tools were present.
- `subscribe_agent` returned the existing active instance.
- `add_instance_knowledge` saved a document to that private instance.
Expand Down
6 changes: 3 additions & 3 deletions platform-docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Confirm before destructive actions.

## What `initialize` Answers

- `serverInfo.version`: `0.1.38`
- `serverInfo.version`: `0.1.39`

That is the same value the published MCP-registry manifest (`server.json`) carries, and both
are read from one constant — `MCP_SERVER_VERSION` in `workers/mcp/src/server-version.ts` —
Expand Down Expand Up @@ -249,7 +249,7 @@ The two published hints are **derived, not hand-maintained per tool**.
`workers/mcp/src/tool-metadata.ts` classifies every tool `read` / `write` / `runtime` /
`destructive` in one table, and `annotationsFor()` maps that classification onto the two
hints. The classification is then derived **back out of the handlers** by `index.test.ts`,
which drives all 196 tools under two different scope sets and reads the required scope out
which drives all 197 tools under two different scope sets and reads the required scope out
of each refusal — so a tool announced read-only that enforces a write gate fails the build
rather than reaching a host. `conformance.test.ts` asserts the same thing against a real
`tools/list` response.
Expand Down Expand Up @@ -412,7 +412,7 @@ More recipes, with real argument names, are in

## Tool Surface

The server registers **196 tools**. 172 are always present. The remaining 24 are gated to
The server registers **197 tools**. 173 are always present. The remaining 24 are gated to
the console surfaces of the connected user's own subscribed agents, so the surface is
per-connection:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ the private runtime is working. The instance is correctly refusing to spend plat

## Tool Surface

The server registers 196 tools; call `tools/list` for the current set. The tool table in
The server registers 197 tools; call `tools/list` for the current set. The tool table in
`workers/mcp/README.md` (or the published `platform-docs/mcp.md`) lists every tool with its
scope, dry-run support, and confirmation value. The always-on `platform_guide` tool returns a
plain-text map of the most commonly used ones — call it at the start of a session to orient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ the private runtime is working. The instance is correctly refusing to spend plat

## Tool Surface

The server registers 196 tools; call `tools/list` for the current set. The tool table in
The server registers 197 tools; call `tools/list` for the current set. The tool table in
`workers/mcp/README.md` (or the published `platform-docs/mcp.md`) lists every tool with its
scope, dry-run support, and confirmation value. The always-on `platform_guide` tool returns a
plain-text map of the most commonly used ones — call it at the start of a session to orient
Expand Down
10 changes: 8 additions & 2 deletions scripts/check-file-size.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1296,7 +1296,11 @@ const PINS = {
// which has no checkout to repair. The brief the flag stands for, and every sentence about it,
// live in `lib/repo-sync-gate.ts`; what is here is the one fact only this route knows, which is
// that an objective is required EXCEPT on a repair run.
"workers/api/src/routes/tools.ts": 1259,
// +1 at #806: `registerLoopContinueRoutes(toolRoutes)` and its import — `POST
// /:id/loop/:runId/continue`, which carries a stopped run's objective onto a fresh run with a
// new ceiling. The handler is NOT here: it is `routes/loop-continue-routes.ts`, for the same
// reason the queue's is not, which is what keeps this at +1 rather than +100.
"workers/api/src/routes/tools.ts": 1261,
// First entry at #477: Usage.tsx crossed 800 lines as BudgetPanel expanded to cover per-tree
// run knobs (perTreeCostMicros, perTreeDelegations, perTreeMaxDepth, loopMaxIterations) and
// their edit fields. The page is one coherent screen — usage data + the limits that bound it —
Expand Down Expand Up @@ -1708,7 +1712,9 @@ const PINS = {
// why — most of it the boundary that was measured and left alone — plus these three. Same price
// as the first split's note directly above.
// +5 at #814: the coding-session raise above (four lines of why) and this one.
"scripts/check-file-size.mjs": 1792,
// +6 at #806: the tools.ts raise above (four lines of why, naming where the handler went)
// and these two.
"scripts/check-file-size.mjs": 1798,
};

/**
Expand Down
2 changes: 1 addition & 1 deletion server.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "io.github.ProAgentStore/platform",
"title": "ProAgentStore",
"description": "MCP-first control plane for ProAgentStore agents and private instances.",
"version": "0.1.38",
"version": "0.1.39",
"repository": {
"url": "https://github.com/ProAgentStore/platform",
"source": "github"
Expand Down
2 changes: 1 addition & 1 deletion skills/proagentstore-mcp-operator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ the private runtime is working. The instance is correctly refusing to spend plat

## Tool Surface

The server registers 196 tools; call `tools/list` for the current set. The tool table in
The server registers 197 tools; call `tools/list` for the current set. The tool table in
`workers/mcp/README.md` (or the published `platform-docs/mcp.md`) lists every tool with its
scope, dry-run support, and confirmation value. The always-on `platform_guide` tool returns a
plain-text map of the most commonly used ones — call it at the start of a session to orient
Expand Down
2 changes: 1 addition & 1 deletion store/.well-known/mcp-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "io.github.ProAgentStore/platform",
"title": "ProAgentStore",
"description": "MCP-first control plane for ProAgentStore agents and private instances.",
"version": "0.1.38",
"version": "0.1.39",
"repository": {
"url": "https://github.com/ProAgentStore/platform",
"source": "github"
Expand Down
2 changes: 1 addition & 1 deletion store/about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ <h2>Data isolation</h2>

<h2>Developer tools</h2>
<p><strong>CLI</strong> — <code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">npm i -g @proagentstore/cli</code> then <code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">pags up</code>: one runner serves ALL your agents over a WebSocket relay — no tunnel binary, no config. Plus <code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">pags login</code>, <code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">pags init</code>, <code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">pags check</code>, <code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">pags publish</code>.</p>
<p><strong>MCP Server</strong> — manage agents from Claude Code, Cursor, or VS Code. 196 tools across creator operations, public trials, private instance runtime, coding, and error observability (<code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">list_errors</code>).</p>
<p><strong>MCP Server</strong> — manage agents from Claude Code, Cursor, or VS Code. 197 tools across creator operations, public trials, private instance runtime, coding, and error observability (<code style="background:var(--panel);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.85rem">list_errors</code>).</p>
<p><strong>Skills</strong> — install the same ProAgentStore MCP workflow in Codex and Claude Code from <a href="/skills/">the skills catalog</a>.</p>
<p><strong>Webhook ingestion</strong> — Zapier, Make, n8n can POST documents directly into an instance's knowledge base.</p>
<p><strong>API key vault</strong> — store your OpenAI, Anthropic, Google AI keys once. Encrypted AES-256-GCM. Agents access via proxy.</p>
Expand Down
46 changes: 46 additions & 0 deletions store/console/src/lib/loopContinue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Which ended runs offer Continue (#806), and the one assertion that is not about this file.
*
* The last test reads the WORKER's own `RESUMABLE_STOP_REASONS` and requires the two lists to
* match. The module header explains why a copy is acceptable and which direction of drift is
* survivable; this is what keeps the drift from happening silently anyway. It is a source read
* rather than an import because the console and the worker are separate builds — importing worker
* code into a console test would bring `Env` and the D1 types with it.
*/
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { canContinueRun, CONTINUABLE_STOP_REASONS } from "./loopContinue";

describe("canContinueRun", () => {
it.each(["interrupted", "max_iterations", "engine_limit", "provider_credit"])("offers Continue after %s", (stopReason) => {
expect(canContinueRun({ status: "failed", stopReason })).toBe(true);
});

it.each(["done", "failed", "cancelled", "escalated", "no_progress", "budget"])("does not offer it after %s", (stopReason) => {
expect(canContinueRun({ status: "failed", stopReason })).toBe(false);
});

it("never offers it on a run that is still going", () => {
// Belt and braces: a running run carries no stop reason today, but a Continue button
// underneath a live Stop button is the one outcome that must be impossible by construction.
expect(canContinueRun({ status: "running", stopReason: null })).toBe(false);
expect(canContinueRun({ status: "running", stopReason: "max_iterations" })).toBe(false);
});

it("says no to a run it knows nothing about", () => {
expect(canContinueRun(null)).toBe(false);
expect(canContinueRun(undefined)).toBe(false);
expect(canContinueRun({ status: "failed" })).toBe(false);
});
});

describe("the copy of the server's list", () => {
it("matches RESUMABLE_STOP_REASONS in the API worker", () => {
const source = readFileSync(new URL("../../../../workers/api/src/lib/agent-loop-store.ts", import.meta.url).pathname, "utf8");
const declared = /export const RESUMABLE_STOP_REASONS = \[([^\]]*)\]/.exec(source);
expect(declared).not.toBeNull();
const server = [...(declared as RegExpExecArray)[1].matchAll(/"([a-z_]+)"/g)].map((m) => m[1]);
expect(server.length).toBeGreaterThan(0);
expect([...CONTINUABLE_STOP_REASONS]).toEqual(server);
});
});
45 changes: 45 additions & 0 deletions store/console/src/lib/loopContinue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Which ended runs offer a Continue button (#806 item 3(c)).
*
* ── The server is the authority, and this is a filter
*
* `RESUMABLE_STOP_REASONS` lives in `workers/api/src/lib/agent-loop-store.ts` and is where the
* decision is made: `POST /:id/loop/:runId/continue` refuses anything else with a 409 that names
* what to do instead. This list exists only so the button is not OFFERED on a run the server will
* refuse — a control that always errors is worse than no control.
*
* So the two lists can drift, and the drift is survivable in one direction only, which is why it is
* worth stating: a reason added to the server and not here costs a button that could have been
* shown; a reason added here and not to the server costs a click that comes back as an explained
* 409. Neither can start work the server would not have started. The console cannot import the
* worker's copy — different build, no shared package — and vendoring is this repo's answer to that
* (see the workspace CLAUDE.md), so a copy with its direction of failure written down beats an
* import that does not exist.
*
* ── Why "ended" is checked separately from the reason
*
* A run still going has no `stopReason` at all, so the reason test alone would be enough today. It
* is checked anyway because the two facts answer different questions — "is this over" and "was it
* over WITHOUT a verdict" — and a run that reports a reason while still running (a cooperative
* cancel settling, a park) must not grow a Continue button underneath its Stop button.
*/

/** The endings the server will continue. Kept in the order `RESUMABLE_STOP_REASONS` lists them. */
export const CONTINUABLE_STOP_REASONS = ["interrupted", "max_iterations", "engine_limit", "provider_credit"] as const;

/** The fields of a loop run that decide whether Continue is offered (`LoopRunView` has more). */
export interface LoopRunContinueLike {
status: string;
stopReason?: string | null;
}

/**
* Is this run one the owner can carry on from?
*
* Deliberately not "should we show a Continue button" — the caller decides that, because the same
* answer is wanted in two places that render differently. This is the fact, not the layout.
*/
export function canContinueRun(run: LoopRunContinueLike | null | undefined): boolean {
if (!run || run.status === "running") return false;
return (CONTINUABLE_STOP_REASONS as readonly string[]).includes(run.stopReason ?? "");
}
44 changes: 42 additions & 2 deletions store/console/src/tabs/LoopRunsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from "react";
import { api } from "@proagentstore/sdk/client";
import { loopStopControl, type LoopPhase } from "../lib/loopStopState";
import { canContinueRun } from "../lib/loopContinue";
import { activityLabel, isOpen, runActivity, type RunHealth } from "../lib/workInFlight";
import Button from "../components/Button";
import Card from "../components/Card";
Expand Down Expand Up @@ -140,6 +141,28 @@ export default function LoopRunsSection({ instanceId }: { instanceId: string })
setBusy(false);
};

/**
* Carry a stopped run's objective onto a fresh run (#806 item 3(c)).
*
* No "how many more iterations" prompt here on purpose: the server defaults to the stopped
* run's own ceiling, so an empty body means "another run of the same size", which is the
* reading that cannot surprise an account's spend. A number belongs with the other loop
* controls, not behind a button whose whole appeal is that it takes one click.
*/
const resume = async (runId: string) => {
setBusy(true);
setMsg("");
try {
await api(`/v1/instances/${instanceId}/loop/${runId}/continue`, { method: "POST", body: "{}" });
await load();
} catch (e) {
// The server's refusals NAME what to do instead ("read its outcome first", "run `pags
// up`"), so they are shown verbatim rather than replaced with a generic failure.
setMsg(e instanceof Error ? e.message : String(e));
}
setBusy(false);
};

if (runs.length === 0) return null;

return (
Expand All @@ -166,8 +189,25 @@ export default function LoopRunsSection({ instanceId }: { instanceId: string })
{ctl.actionLabel}
</Button>
) : (
<span className={`text-xs font-semibold whitespace-nowrap ${TONE[r.status] ?? "text-muted"}`}>
{REASON_LABEL[r.stopReason ?? ""] ?? r.status}
<span className="flex items-center gap-2 whitespace-nowrap">
<span className={`text-xs font-semibold ${TONE[r.status] ?? "text-muted"}`}>
{REASON_LABEL[r.stopReason ?? ""] ?? r.status}
</span>
{/*
* Offered only after an ending with no verdict (#806). The reason label stays
* beside it: "Hit the step limit · Continue" is the sentence the owner needs,
* and a button alone would not say what it is continuing FROM.
*/}
{canContinueRun(r) && (
<Button
size="sm"
disabled={busy}
onClick={() => resume(r.runId)}
title="Start a new run on this objective. It is told what the stopped run already landed."
>
Continue
</Button>
)}
</span>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion store/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ That response means the private instance path is working and correctly refusing

## Main MCP Tool Groups

The server registers 196 tools; 172 are always present and 24 are gated to the `apply`, `repo`, and `coding` surfaces. The lists below are the commonly used subset, not the full surface — call `tools/list` for the authoritative set, and see the tool table in `workers/mcp/README.md` for scopes, `dry_run` support, and confirmation values.
The server registers 197 tools; 173 are always present and 24 are gated to the `apply`, `repo`, and `coding` surfaces. The lists below are the commonly used subset, not the full surface — call `tools/list` for the authoritative set, and see the tool table in `workers/mcp/README.md` for scopes, `dry_run` support, and confirmation values.

Creator tools:

Expand Down
Loading
Loading