diff --git a/README.md b/README.md index 936ad8b..7d07856 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ canonical five-tool memory facade. - Captures a completed **primary** OpenCode user/assistant turn after `session.execution.succeeded`. It excludes reasoning, tool outputs, system messages, failed - turns, interrupted turns, and subagent sessions. The stable event ID makes - repeat idle events idempotent. + turns, interrupted turns, and subagent sessions. Visible text before and + after tool calls is retained. The stable event ID makes repeat completion + events idempotent. - Injects a bounded, clearly labeled recent chronology once per primary session. These memories are historical data, never instructions. - Registers `musubi_recent`, `musubi_search`, `musubi_get`, @@ -36,9 +37,12 @@ command -v musubi-opencode-bridge command -v musubi-opencode-mcp ``` -Add the plugin **only to that seat's** `opencode.json` (the example assumes the -repository was cloned at the shown path). Then restart that seat's OpenCode -service; a background service keeps the environment from its own launch. +Add the plugin **only to that seat's project-local** `opencode.json` (the +example assumes the repository was cloned at the shown path). Do not add it to +the shared `~/.config/opencode/opencode.json`. Launch the seat with +`opencode --standalone`: the default TUI connects to a shared background +service whose environment does not contain this seat's token or identity. +Restart the seat after the project config and launcher are in place. ```json { @@ -62,7 +66,7 @@ The `MUSUBI_*` identity triple is all-or-nothing. The adapter refuses a missing or mismatched identity. The JWT's unverified presence and write-scope claims are checked locally before a remote request; the Musubi server remains the authority for validity. Local enqueue subprocesses receive no endpoint or -token. For an OpenCode service whose `PATH` does not expose the two Python +token. For an OpenCode process whose `PATH` does not expose the two Python commands, set `MUSUBI_OPENCODE_BRIDGE_BIN` and `MUSUBI_OPENCODE_MCP_BIN` to their absolute paths in the launcher. diff --git a/plugin/index.js b/plugin/index.js index 6aeeff2..d045ce7 100644 --- a/plugin/index.js +++ b/plugin/index.js @@ -28,6 +28,9 @@ function bridge(request) { export default Plugin.define({ id: "musubi-opencode", async setup(ctx) { + if (!process.env.MUSUBI_ACTOR || !process.env.MUSUBI_PRESENCE || !process.env.MUSUBI_ZONE) { + console.error("musubi-opencode unavailable: seat identity is missing; launch OpenCode with --standalone from the seat launcher") + } const mcpBinary = process.env.MUSUBI_OPENCODE_MCP_BIN || "musubi-opencode-mcp" await ctx.mcp.transform((editor) => { editor.set("musubi", { type: "local", command: [mcpBinary] }) diff --git a/plugin/turn.js b/plugin/turn.js index 99a8ae1..9c9def7 100644 --- a/plugin/turn.js +++ b/plugin/turn.js @@ -24,8 +24,10 @@ export function completedTurn(session, messages) { !item.error && Number.isFinite(item.time?.completed), ) if (!assistant) return null - const assistantText = assistant.content - ?.filter((part) => part?.type === "text" && typeof part.text === "string") + const assistantText = suffix + .filter((item) => item?.type === "assistant" && !item.error) + .flatMap((item) => item.content || []) + .filter((part) => part?.type === "text" && typeof part.text === "string") .map((part) => part.text.trim()) .filter(Boolean) .join("\n\n") diff --git a/tests/turn.test.js b/tests/turn.test.js index 0e7ead6..e735385 100644 --- a/tests/turn.test.js +++ b/tests/turn.test.js @@ -9,11 +9,12 @@ const final = { content: [{ type: "reasoning", text: "private" }, { type: "text", text: "The code changed." }], } -test("captures only the completed final text", () => { - expect(completedTurn(session, [user, { type: "assistant", finish: "tool-calls" }, final, { type: "idle", outcome: "succeeded" }])) +test("captures visible text across tool calls after a completed turn", () => { + const beforeTool = { type: "assistant", finish: "tool-calls", content: [{ type: "text", text: "I will check." }, { type: "tool", name: "read" }] } + expect(completedTurn(session, [user, beforeTool, final, { type: "idle", outcome: "succeeded" }])) .toEqual({ session_id: "ses_main", user_id: "msg_user", user_text: "What changed?", - assistant_text: "The code changed.", completed_at: 3, + assistant_text: "I will check.\n\nThe code changed.", completed_at: 3, model: "qwen-lan/qwen3.8-flash-next", }) })