diff --git a/CHANGES.md b/CHANGES.md index 4fb27e08..55f85957 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,10 @@ Open Pstack lands on green: Mergify merges a ready PR to main once verify and exact-head live-gate pass and no review thread is unresolved, and a workflow resolves Unfret's own threads. A PR that touches `.github/` or the Mergify config also waits for Unfret, and workflow changes require operator queueing. This port applies the Cursor → Claude Code substitutions in skill bodies. Earlier drafts left them flagged; this revision resolves them. A later pass added a Codex build that shares the same skills; see [Codex port](#codex-port) below. +## Unreleased + +**Muse Wonder Full 1.0 lanes.** The model catalog registers `muse:muse-wonder-full-1.0` as an optional family with efforts `low` to `max` and a default of `max`, so `/setup-pstack` can place it in a role or panel. Muse lanes keep the external runner from every parent, and a Muse writer still needs `muse writer sandbox: full-access`. The Muse preflight also passes a signed-in `muse usage` that prints `No usage data (server_sent_no_usage)`, with the evidence `subscription-no-usage-data`; measured with Muse Code 1.4.14, that output proves the login, and the model call still checks the allowance. Default roles and panels are unchanged. + ## 1.21.0 adds Grok as a parent harness Open Pstack 1.21.0 keeps the Cursor pstack 0.15.36 sync. Default roles and panels are unchanged. diff --git a/plugins/pstack/skills/poteto-mode/references/model-catalog.md b/plugins/pstack/skills/poteto-mode/references/model-catalog.md index 5857bcaf..02aaeaeb 100644 --- a/plugins/pstack/skills/poteto-mode/references/model-catalog.md +++ b/plugins/pstack/skills/poteto-mode/references/model-catalog.md @@ -26,6 +26,7 @@ Model and provider facts for [`provider-dispatch.md`](provider-dispatch.md), whi | gemini-3.8-flash | Gemini 3.8 Flash (Antigravity) | agy | gemini-3.8-flash | high | low medium high | - | optional | - | | composer-2.5 | Composer 2.5 (Cursor) | cursor | composer-2.5 | default | default | - | optional | - | | gemini-3.7-flash | Gemini 3.7 Flash (Cursor) | cursor | gemini-3.7-flash | high | low medium high | - | optional | - | +| muse-wonder-full-1.0 | Muse Wonder Full 1.0 | muse | muse-wonder-full-1.0 | max | low medium high xhigh max | - | optional | - | A family is `default` when a role default names it and `optional` otherwise. A Claude-native agent exists for each selectable effort of a Claude family. @@ -224,13 +225,13 @@ The runner refuses an access mode outside a provider's Access modes cell before - Host prerequisites: none. - State paths: `~/.config/muse/` (the login in `auth.json`, which the CLI reads and refreshes, `settings.json` and `trust.json`); `~/.local/share/muse/` (the session index, model catalog cache, plugins, skills and runtime state it writes) and `~/Library/Application Support/Muse/` on macOS. - Child environment: only `PATH` `HOME` `USER` `LOGNAME` `SHELL` `TMPDIR` `LANG` `TERM` `COLORTERM` `TZ` `HTTP_PROXY` `HTTPS_PROXY` `NO_PROXY` `ALL_PROXY` `http_proxy` `https_proxy` `no_proxy` `all_proxy` `SSL_CERT_FILE` `SSL_CERT_DIR` `NODE_EXTRA_CA_CERTS` `META_API_KEY`, and variables starting with `LC_`, `XDG_`. -- Measured with Muse Code 1.4.14 on macOS. Every flag in the argv is in the recorded `muse --help` and `muse exec --help`. A lane runs `exec --json --model --prompt-file --workspace --worktree off --approval-mode never --approval-judge off` with web tools, reminders, `request_user_input`, foreign personal context and the session log turned off. The effort is `--reasoning-effort `, omitted for `default`; Muse also accepts `none` and `minimal`, which the runner does not offer. The parent owns the worktree, so Muse creates none. No Muse family is registered, so a lane names a catalog model, such as `muse-spark-1.3` or `muse-wonder-1.0`. +- Measured with Muse Code 1.4.14 on macOS. Every flag in the argv is in the recorded `muse --help` and `muse exec --help`. A lane runs `exec --json --model --prompt-file --workspace --worktree off --approval-mode never --approval-judge off` with web tools, reminders, `request_user_input`, foreign personal context and the session log turned off. The effort is `--reasoning-effort `, omitted for `default`; Muse also accepts `none` and `minimal`, which the runner does not offer. The parent owns the worktree, so Muse creates none. The registered family is Muse Wonder Full 1.0; a lane may also name any other Muse catalog model, such as `muse-spark-1.3`, as an unregistered pair. - A read-only lane adds `--disable-write --disable-shell`. The file-reading tools remain, and a write request fails with `writes are disabled for this session`. - No Muse writer is confined, so a writer needs the model sheet's opt-in (`isolated-write-full-access`); the runner refuses a bounded `isolated-write` lane before launch. An opted-in writer drops `--disable-write` and `--disable-shell` and keeps Muse's sandbox on. Measured in a linked worktree under the home directory, whose main repository and a sibling directory sat beside it: edits, shell writes and a Python run inside the worktree succeeded. The file tools refused every path outside the workspace (`absolute path is outside the workspace`, `path escapes workspace`) except the system temporary folders. The sandboxed shell failed outside the worktree with `Operation not permitted`, except that it wrote into the main repository's shared `.git`, `/tmp` and `$TMPDIR`. With `--disable-sandbox`, the file tools and the shell wrote into every outside target, so the runner never passes it. - With `--approval-mode never --approval-judge off`, a refused tool call returns its error to the model and the run continues. Muse reports `Agent delegation: auto unavailable: workspace is untrusted` on stderr, and the lane never passes `--trust-workspace`, so the workspace's skills and rules do not load. - Muse reads `~/.config/muse/settings.json` in every lane. Its `endpoint_transport` key reroutes every lane to another endpoint: on the measured host, a value that another tool wrote sent each lane to a proxy, and every turn failed with `still unauthorized after a token refresh`. The runner does not override it. - The environment is the base allowlist plus `META_API_KEY`. The account login is found through `HOME`. `muse login --help` says `META_API_KEY` always takes priority over the account login. -- The preflight runs `muse usage`. It passes when it exits 0 and starts with `Tier: `, with the fixed evidence `authenticated`, because the output names the subscription tier. It also passes when it exits 0 and starts with `No subscription allowance (api_key)`, with the fixed evidence `api-key`, because `META_API_KEY` is the login and the model call checks access. Exit 1 with `muse usage: not logged in:` is `unauthenticated`/77. Any other output is `child-failed`/70. +- The preflight runs `muse usage`. It passes when it exits 0 and starts with `Tier: `, with the fixed evidence `authenticated`, because the output names the subscription tier. It also passes when it exits 0 and starts with `No subscription allowance (api_key)`, with the fixed evidence `api-key`, because `META_API_KEY` is the login and the model call checks access. It also passes when it exits 0 and starts with `No usage data (server_sent_no_usage)`, with the fixed evidence `subscription-no-usage-data`: measured with Muse Code 1.4.14 on Linux, a signed-in account whose window had seen no use printed `No usage data (server_sent_no_usage): the server sent no usage data for this subscription; this can happen before your first use in a new window`, and `muse usage --json` gave `{"allowance":null,"reason":"server_sent_no_usage"}`. The server answered for the subscription, so the login holds and the model call checks access; on that host the model call then returned `API error 429 ... Subscription quota exhausted` as `child-failed`/70, so this output proves the login, not an allowance. Exit 1 with `muse usage: not logged in:` is `unauthenticated`/77. Any other output is `child-failed`/70. - The `--json` stream is one envelope per line, dispatched on `payload_type`. The reply is the text of the last `run.terminal.*` event, accepted only when its `terminal` is `completed` and the text is not empty. Another terminal is `child-failed`/70 with its reason, except that `model `` does not exist or you lack access` is `unavailable-model`/69. SIGINT exits 130 and SIGTERM 143, each with no terminal event, which is `cancelled`/130; a stream with no terminal event otherwise is `malformed-output`/65. The session is the `session` stream ID, and usage sums the `run.usage` event's per-model tokens. No cost is reported. - `run.model.configured` repeats the requested model, and no event was shown to name a different served model, so the model proof is the pinned argv (`pinned-argv`). diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/providers/fixtures/muse/usage-no-usage-data.txt b/plugins/pstack/skills/poteto-mode/scripts/runner/providers/fixtures/muse/usage-no-usage-data.txt new file mode 100644 index 00000000..f1ce1722 --- /dev/null +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/providers/fixtures/muse/usage-no-usage-data.txt @@ -0,0 +1,2 @@ +No usage data (server_sent_no_usage): the server sent no usage data for this subscription; this can happen before your first use in a new window + diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.test.ts index e104fb3b..62e32079 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.test.ts @@ -45,6 +45,15 @@ describe("muse preflight", () => { expect(muse.preflight.classify({ exitCode: 0, stdout: "No subscription allowance (api_key)\n", stderr: "" }, lane())) .toEqual({ kind: "passed", evidence: "api-key" }); }); + + it("passes a signed-in `muse usage` whose server sent no usage data, and keeps any other output inconclusive", () => { + expect(muse.preflight.classify({ exitCode: 0, stdout: fixture("usage-no-usage-data.txt"), stderr: "" }, lane())) + .toEqual({ kind: "passed", evidence: "subscription-no-usage-data" }); + expect(muse.preflight.classify({ exitCode: 1, stdout: fixture("usage-no-usage-data.txt"), stderr: "" }, lane())) + .toEqual({ kind: "inconclusive" }); + expect(muse.preflight.classify({ exitCode: 0, stdout: "No usage data (network_error): offline\n", stderr: "" }, lane())) + .toEqual({ kind: "inconclusive" }); + }); }); describe("muse parse", () => { diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.ts index 6794ce6e..65442ddd 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/providers/muse.ts @@ -5,6 +5,12 @@ import { BASE_ALLOWLIST, BASE_PREFIXES, effortArgs, finiteNumber, nullableString // SIGINT exits 130 and SIGTERM 143, each with no terminal event. Measured with Muse Code 1.4.14. const SIGNAL_EXITS = [130, 143]; +const SIGNED_IN_USAGE: readonly (readonly [prefix: string, evidence: string])[] = [ + ["Tier: ", "authenticated"], + ["No subscription allowance (api_key)", "api-key"], + ["No usage data (server_sent_no_usage)", "subscription-no-usage-data"], +]; + function events(stdout: string): JsonObject[] | null { const parsed: JsonObject[] = []; for (const line of stdout.split("\n")) { @@ -74,8 +80,10 @@ export const muse = adapter({ argv: () => ["usage"], retryAfterUnauthenticatedMs: null, classify(run) { - if (run.exitCode === 0 && run.stdout.startsWith("Tier: ")) return { kind: "passed", evidence: "authenticated" }; - if (run.exitCode === 0 && run.stdout.startsWith("No subscription allowance (api_key)")) return { kind: "passed", evidence: "api-key" }; + if (run.exitCode === 0) { + const signedIn = SIGNED_IN_USAGE.find(([prefix]) => run.stdout.startsWith(prefix)); + if (signedIn !== undefined) return { kind: "passed", evidence: signedIn[1] }; + } return run.exitCode === 1 && /^muse usage: not logged in:/m.test(run.stderr) ? { kind: "unauthenticated" } : { kind: "inconclusive" }; }, }, @@ -119,13 +127,13 @@ export const muse = adapter({ "`~/.local/share/muse/` (the session index, model catalog cache, plugins, skills and runtime state it writes) and `~/Library/Application Support/Muse/` on macOS", ], notes: [ - "Measured with Muse Code 1.4.14 on macOS. Every flag in the argv is in the recorded `muse --help` and `muse exec --help`. A lane runs `exec --json --model --prompt-file --workspace --worktree off --approval-mode never --approval-judge off` with web tools, reminders, `request_user_input`, foreign personal context and the session log turned off. The effort is `--reasoning-effort `, omitted for `default`; Muse also accepts `none` and `minimal`, which the runner does not offer. The parent owns the worktree, so Muse creates none. No Muse family is registered, so a lane names a catalog model, such as `muse-spark-1.3` or `muse-wonder-1.0`.", + "Measured with Muse Code 1.4.14 on macOS. Every flag in the argv is in the recorded `muse --help` and `muse exec --help`. A lane runs `exec --json --model --prompt-file --workspace --worktree off --approval-mode never --approval-judge off` with web tools, reminders, `request_user_input`, foreign personal context and the session log turned off. The effort is `--reasoning-effort `, omitted for `default`; Muse also accepts `none` and `minimal`, which the runner does not offer. The parent owns the worktree, so Muse creates none. The registered family is Muse Wonder Full 1.0; a lane may also name any other Muse catalog model, such as `muse-spark-1.3`, as an unregistered pair.", "A read-only lane adds `--disable-write --disable-shell`. The file-reading tools remain, and a write request fails with `writes are disabled for this session`.", "No Muse writer is confined, so a writer needs the model sheet's opt-in (`isolated-write-full-access`); the runner refuses a bounded `isolated-write` lane before launch. An opted-in writer drops `--disable-write` and `--disable-shell` and keeps Muse's sandbox on. Measured in a linked worktree under the home directory, whose main repository and a sibling directory sat beside it: edits, shell writes and a Python run inside the worktree succeeded. The file tools refused every path outside the workspace (`absolute path is outside the workspace`, `path escapes workspace`) except the system temporary folders. The sandboxed shell failed outside the worktree with `Operation not permitted`, except that it wrote into the main repository's shared `.git`, `/tmp` and `$TMPDIR`. With `--disable-sandbox`, the file tools and the shell wrote into every outside target, so the runner never passes it.", "With `--approval-mode never --approval-judge off`, a refused tool call returns its error to the model and the run continues. Muse reports `Agent delegation: auto unavailable: workspace is untrusted` on stderr, and the lane never passes `--trust-workspace`, so the workspace's skills and rules do not load.", "Muse reads `~/.config/muse/settings.json` in every lane. Its `endpoint_transport` key reroutes every lane to another endpoint: on the measured host, a value that another tool wrote sent each lane to a proxy, and every turn failed with `still unauthorized after a token refresh`. The runner does not override it.", "The environment is the base allowlist plus `META_API_KEY`. The account login is found through `HOME`. `muse login --help` says `META_API_KEY` always takes priority over the account login.", - "The preflight runs `muse usage`. It passes when it exits 0 and starts with `Tier: `, with the fixed evidence `authenticated`, because the output names the subscription tier. It also passes when it exits 0 and starts with `No subscription allowance (api_key)`, with the fixed evidence `api-key`, because `META_API_KEY` is the login and the model call checks access. Exit 1 with `muse usage: not logged in:` is `unauthenticated`/77. Any other output is `child-failed`/70.", + "The preflight runs `muse usage`. It passes when it exits 0 and starts with `Tier: `, with the fixed evidence `authenticated`, because the output names the subscription tier. It also passes when it exits 0 and starts with `No subscription allowance (api_key)`, with the fixed evidence `api-key`, because `META_API_KEY` is the login and the model call checks access. It also passes when it exits 0 and starts with `No usage data (server_sent_no_usage)`, with the fixed evidence `subscription-no-usage-data`: measured with Muse Code 1.4.14 on Linux, a signed-in account whose window had seen no use printed `No usage data (server_sent_no_usage): the server sent no usage data for this subscription; this can happen before your first use in a new window`, and `muse usage --json` gave `{\"allowance\":null,\"reason\":\"server_sent_no_usage\"}`. The server answered for the subscription, so the login holds and the model call checks access; on that host the model call then returned `API error 429 ... Subscription quota exhausted` as `child-failed`/70, so this output proves the login, not an allowance. Exit 1 with `muse usage: not logged in:` is `unauthenticated`/77. Any other output is `child-failed`/70.", "The `--json` stream is one envelope per line, dispatched on `payload_type`. The reply is the text of the last `run.terminal.*` event, accepted only when its `terminal` is `completed` and the text is not empty. Another terminal is `child-failed`/70 with its reason, except that `model `` does not exist or you lack access` is `unavailable-model`/69. SIGINT exits 130 and SIGTERM 143, each with no terminal event, which is `cancelled`/130; a stream with no terminal event otherwise is `malformed-output`/65. The session is the `session` stream ID, and usage sums the `run.usage` event's per-model tokens. No cost is reported.", "`run.model.configured` repeats the requested model, and no event was shown to name a different served model, so the model proof is the pinned argv (`pinned-argv`).", ], diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/registry.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/registry.test.ts index 857b3072..0e4679ac 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/registry.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/registry.test.ts @@ -106,6 +106,7 @@ describe("registry data", () => { ["gemini-3.8-flash", "high"], ["composer-2.5", "default"], ["gemini-3.7-flash", "high"], + ["muse-wonder-full-1.0", "max"], ]); expect(REGISTRY.families.filter((entry) => entry.provider === "claude").map((entry) => [entry.name, entry.model])) .toEqual([["fable", "fable"], ["opus", "opus"], ["sonnet", "sonnet"], ["opus-5-5", "claude-opus-5-5"], ["haiku-5-5", "claude-haiku-5-5"]]); @@ -503,6 +504,18 @@ describe("resolveLane", () => { .toEqual(expect.arrayContaining(["gpt-6-astra", 'model_reasoning_effort="ultra"'])); }); + it("resolves Muse Wonder Full 1.0 at max by default, passes each listed effort as a flag, and rejects ultra", () => { + const muse = (effort = "max") => resolveLane(request({ parent: "claude", provider: "muse", model: "muse-wonder-full-1.0", effort })); + const lane = muse(); + expect([lane.family?.name, lane.family?.label, lane.model, lane.cliModel, lane.effort]) + .toEqual(["muse-wonder-full-1.0", "Muse Wonder Full 1.0", "muse-wonder-full-1.0", "muse-wonder-full-1.0", "max"]); + expect(effortsOf(REGISTRY.families.find((entry) => entry.name === "muse-wonder-full-1.0")!)).toEqual(["low", "medium", "high", "xhigh", "max"]); + expect(muse("high").adapter.launch.argv(muse("high"), { sharedGitDir: null, providerSidecar: null })) + .toEqual(expect.arrayContaining(["--model", "muse-wonder-full-1.0", "--reasoning-effort", "high"])); + expect(() => muse("ultra")).toThrow("effort ultra is not offered for muse:muse-wonder-full-1.0; choose one of: low, medium, high, xhigh, max"); + expect(resolveLane(request({ parent: "claude", provider: "muse", model: "muse-wonder-1.0", effort: "high" })).family).toBeNull(); + }); + it("maps each Devin effort to its CLI model ID and keeps the descriptor's model for the receipt", () => { const devin = (model: string, effort: string, mode = "read-only") => resolveLane(request({ parent: "claude", provider: "devin", model, effort, mode })); diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/registry/models.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/registry/models.ts index 7550be7c..e185fda2 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/registry/models.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/registry/models.ts @@ -174,6 +174,15 @@ export const FAMILIES = [ defaultEffort: "high", upstream: null, }), + family({ + name: "muse-wonder-full-1.0", + label: "Muse Wonder Full 1.0", + provider: "muse", + model: "muse-wonder-full-1.0", + efforts: UP_TO_MAX, + defaultEffort: "max", + upstream: null, + }), ] as const; export type FamilyName = (typeof FAMILIES)[number]["name"]; @@ -219,4 +228,5 @@ export const RELEASED_PAIRS = [ { pair: "cursor:gemini-3.7-flash", family: "gemini-3.7-flash" }, { pair: "claude:claude-haiku-5-5", family: "haiku-5-5" }, { pair: "codex:gpt-6-astra", family: "astra" }, + { pair: "muse:muse-wonder-full-1.0", family: "muse-wonder-full-1.0" }, ] as const satisfies readonly ReleasedPair[];