opencode gets the skills and a credential, which it had neither of
an opencode agent could not have used the calendar skill written this morning, and would not have failed usefully either — it had no idea the skill existed and no key to call the platform with. two gaps, one file each. SKILLS: OPENCODE ALREADY HAD THE MECHANISM. it has a `skill` tool, an index compiled into its system prompt, and `skills.paths` in config taking directories of <name>/SKILL.md — the same progressive disclosure claude does over the same layout the platform already stores. so this points it at the SAME FILES rather than compiling a second index in a second format. skills/load.ts says a skill is a path because every harness can read a file; this is the better version of that bet — the harness had the feature all along. verified: `/skill?directory=…` now returns all nine platform skills, "Calendar and contacts" among them. THE CREDENTIAL, AND THE THING TO KNOW ABOUT IT. claude spawns a subprocess per session, so its key is per session and a member's resolves to their own grants. opencode cannot: ONE serve runs every session, so the only environment there is is the serve's own, minted once at sidecar start, belonging to the owner. that is sound today for exactly one reason — opencode is owner-only, enforced at every door in chat.ts because one serve runs as one user. when that lifts, this key becomes a privilege escalation. it is the same blocker as the two already there, not a new one, but it lives in a sidecar where nobody looking at chat.ts would find it, so chat.ts now names it. all three move together. verified end to end: a tool process inside an opencode session ran curl with $OFFICER_AGENT_KEY and got the owner's calendars back. OPENCODE_CONFIG_CONTENT rather than editing ~/.config/opencode/opencode.jsonc — it is inline json, it MERGES into the same chain as the config files (checked in the binary), and it dies with the process. the alternative is the platform editing a dotfile it does not own and leaving state behind that outlives the install. affordances ride the `instructions` key as one composed file, written beside the serve's state. loaded — `/config` shows it — but NOT model-verified: this box has no provider credential in auth.json, so a turn 401s before reaching a model. whoever adds one should check that first. the credential module moved out of sidecar/claude/. it was never claude-shaped; it was just there first, and it had one importer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -82,6 +82,14 @@ export async function chatIdentity(user: { id: number; email: string }): Promise
|
||||
// identity the way `spawnClaudeAsMember` does. Restrict here rather than at the permission layer because
|
||||
// `chat` is one permission covering both harnesses, and splitting it would strand the grants already issued.
|
||||
// The matching refusal on the execution path is in `websocket.ts` → `handleChat`.
|
||||
//
|
||||
// ── A THIRD thing now rides on this restriction (2026-08-27) ──
|
||||
//
|
||||
// The serve holds an Officer API credential minted for the OWNER, in its own environment, because one
|
||||
// process serves every session and there is nowhere else to put one — `sidecar/opencode/serve-env.ts`.
|
||||
// So lifting `isOwner` here without changing that hands a member the owner's API authority as well as the
|
||||
// owner's home and Linux account. It is the same blocker, not a new one, but it is easy to miss because it
|
||||
// lives in a sidecar rather than in this file. Whoever does the identity work: all three move together.
|
||||
import { registerAgentPanelRoutes } from './agent-panels-routes';
|
||||
|
||||
export const chatRouter = createRouter();
|
||||
|
||||
Reference in New Issue
Block a user