Openchamber Session Focus
Session Focus tools and system-prompt block for OpenCode — the agent half of the OpenChamber Session Focus panel.
0
0
20.0
生态多维模型
17 小时前
2026-10-20
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@richkershaw/openchamber-session-focus-plugin@0.2.0"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@richkershaw/openchamber-session-focus-plugin@0.2.0"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D @richkershaw/openchamber-session-focus-pluginOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
Important: This extension needs its companion plugin (see Install below).
A per-session focus card for OpenChamber. It sits in the chat's Work Status panel and answers one question at a glance: what is this session doing, and what did we agree to do about it?
- A focus line — the task and the approach, not the subject area.
- A status: what is happening right now, with a glyph that moves only while it really is.
- The agreed actions, in the order they will be implemented.
- Ticked off as they complete and kept, so the session's history stays visible.
- Links to the ticket, ADR or PR behind an action, where one exists.
- Kept current by the agent, through tools it is told to use at named points in the work.
Wire the focus panel to the plugin tools and prove a round-trip.
◐ Implementing: the Status field and its icons · 4m
3 / 5 done
████████████░░░░░░░░
▸ first action ← next to implement, top of the list
▸ second action
▸ third action (#42) (ADR-0007)
────────────── done ──────────────
✓ define the per-session store format (ADR-0006)
✓ panel shell reads the store
✓ revision-guarded writes from the panel
Why two halves
An OpenChamber extension is a sandboxed page. It can draw UI and read and write files, but it cannot register tools for the agent. So this repository is two cooperating pieces over one file:
status/ |
the OpenChamber extension — the card in the Work Status panel |
agent/plugin/ |
an OpenCode v2 plugin — the focus_* tools, and the system-prompt block |
shared/store.ts |
the file format both bundle, so the two writers cannot drift |
The plugin is also what makes the agent reliably keep it current: it appends a short block
to the system instructions on every model call, so the agent always knows the focus and
when to update it. (Set options.instructions: false to turn that off — see
Configuration.)
Install
Two steps. The extension is the card; the plugin is what the agent talks to.
1. The extension
In OpenChamber: Settings → Extensions, then paste the path to this folder (or a git URL for it) and choose Add. Approve the files permission — the card reads and writes the focus file inside your project.
The card appears in the chat's Work Status panel, as a section titled Focus. If you do not see it, open that panel's section chooser and enable it. It hides itself when no project is open, because the store lives in the project.
2. The plugin
Add it to plugins in your OpenCode configuration — the project's opencode.json(c), or the
global one under ~/.config/opencode:
{
"plugins": ["@richkershaw/openchamber-session-focus-plugin"]
}
Or let OpenCode install it for you:
opencode plugin add @richkershaw/openchamber-session-focus-plugin
OpenChamber's Settings → OpenCode → Plugins manages both scopes if you would rather do it there. Point it at the project or the global config depending on whether you want the focus card in one repository or everywhere.
Installing from this repository instead (developing it)
Point plugins at the built package in this folder, or at the local path if you prefer to run
from source:
{ "plugins": ["/absolute/path/to/OpenChamber-SessionFocusPanel/agent/plugin"] }
That path works either way: agent/plugin/package.json resolves to dist/index.js, which is
built and committed, and the bundle carries its own copy of the store contract. Run
npm run build:plugin after editing agent/plugin/index.ts or shared/store.ts.
Tell your projects not to commit the store
.openchamber/session-focus/
The store
One JSON file per session, inside the project:
.openchamber/session-focus/<sessionId>.json
{
"version": 2,
"sessionId": "ses_...",
"revision": 7,
"focus": "Wire the focus panel to the plugin tools and prove a round-trip.",
"lifecycle": "active",
"status": {
"state": "implementing",
"detail": "the Status field and its icons",
"since": 1792400100000
},
"actions": [
{
"id": "a1",
"text": "Define the per-session store format",
"state": "done",
"refs": [{ "label": "ADR-0006", "url": "https://example.com/adr/6" }],
"createdAt": 1792400000000,
"startedAt": 1792400100000,
"completedAt": 1792400600000
}
],
"updatedAt": 1792400600000,
"updatedBy": "agent"
}
Three fields, and the naming is deliberate:
focus— the high-level aim: what this session is for.status— what is happening right now:statefrom the ten below, an optionaldetail, andsince, when that state began. Absent until someone sets one.sinceonly moves when the state changes, so a stuck status is visible as an age.lifecycle—active,doneordropped: the state of the task itself, set byfocus_set.
Also state on each action is todo, doing, done or dropped, with at most one action
doing; actions is held in implementation order, and the card partitions it for display —
active items first, then a divider, then completed items, so an action added now appears above
the completed ones. updatedBy says whether the last write came from agent or user.
A half-written or hand-mangled file is never fatal: an unreadable document reads as empty rather than throwing.
Upgrading from 0.1.0. Version 1 wrote the task lifecycle into
statusas a string.parseDocmigrates that by shape rather than by version number — a leftover"status": "active"is read as the lifecycle and the activity is left unset — so the two halves can be at different versions without adonetask reverting toactive.
Concurrency
The card and the plugin both write, so shared/store.ts re-reads immediately before
writing, and re-reads immediately after — only contents that came from that call count as
a win, and anything else replays the change on top of the other writer's copy. See
applyMutation.
It is not a compare-and-swap: a write landing in the gap between the pre-write read and the write itself is invisible, and the later of the two wins. That window is sub-millisecond and the card only writes when a human ticks something, so it is left to the file system rather than a lock.
What the agent is told
The plugin appends this to the system instructions on each model call, with the live state below it:
- Focus: one sentence naming the task and the approach. Replace it when either changes.
- Status: what is happening right now, from the ten below. Set it at the start of a phase, move
it as you move between phases, and set
waiting-decisionbefore you ask the question. - Actions: the agreed steps, in implementation order. Add them before starting work.
- Exactly one action in flight at a time. Mark it before starting.
- Mark an action done the moment it is verified. Never delete one.
- New work agreed mid-flight: add it above the completed items.
- Attach the ticket, ADR or PR link as soon as one exists.
- When the whole task is finished, set the status to
doneand stop updating.
The status states it is offered, generated from the same table the card draws, so the two cannot drift:
| State | Means | Motion |
|---|---|---|
planning |
working out the approach; nothing written yet | loop |
implementing |
writing code | loop |
testing |
running tests, checks or a build | loop |
reviewing |
reading a diff, a report, or existing code | loop |
waiting-decision |
blocked on a choice only you can make | attention |
waiting-manual |
blocked on a click, an install or an approval | attention |
waiting-external |
waiting on CI, a deploy or a long job | static |
blocked |
stuck on a failure or a missing dependency | static |
idle |
between turns; nothing in flight | static |
done |
the task is finished | static |
Tools
| Tool | Purpose |
|---|---|
focus_get |
read the focus, status and actions |
focus_set |
set or replace the focus sentence; set the task lifecycle |
focus_status |
say what is happening right now (state, optional detail) |
focus_add |
add an action (position: "next" | "last") |
focus_begin |
mark an action in flight |
focus_complete |
mark an action done |
focus_link |
attach a ticket, ADR or PR link to an action |
focus_drop |
mark an action no longer wanted, keeping it visible |
Ids can be omitted on focus_begin and focus_complete: they take the action in flight,
or else the first outstanding one. Every tool returns the rendered card, so the call reads
usefully in the transcript.
Using the card
The status line is the agent's to keep honest, but yours to correct: click it to open the state list and add or change the detail, or to clear it. A state that claims work is progressing only animates while the session is genuinely busy — a forgotten status stops moving and its age keeps counting, so a stale one looks stale.
Click an action's tick to move it outstanding → in flight → done → outstanding. A new action goes in the field at the bottom and lands above the completed block. The link button on a row attaches a reference. Two buttons sit in the section header: Copy as prompt puts the whole card on the clipboard as Markdown, and Refresh re-reads the file.
The store is plain JSON, so you can edit it by hand, keep it open in an editor, or read it from a script. Nothing depends on OpenChamber being the only writer.
Configuration
{
"plugins": [
{
"package": "@richkershaw/openchamber-session-focus-plugin",
"options": { "instructions": false }
}
]
}
instructions: false stops the plugin adding the block to the system prompt. The tools
still work; the agent just is not reminded to use them each turn.
Development
npm install
npm run check # typecheck, build both halves, run the tests
npm run build # rebuild status/main.js and agent/plugin/dist/index.js
Two built artifacts are committed, because neither host builds at install time: the card's
status/main.js (OpenChamber installs no npm dependencies for extensions) and the plugin's
agent/plugin/dist/index.js (OpenCode imports the package from its own cache, with no access
to this repository).
The tests cover the store contract (tests/store.test.ts), drive the plugin's real setup
against a stand-in host context (tests/plugin.test.ts), validate the manifest with the real
@openchamber/sdk parser (tests/package.test.ts), check that the built plugin package is
self-contained and loadable (tests/plugin-package.test.ts), and drive the card's rendering
against a fixture document in happy-dom (tests/card.test.ts).
That last one is the point of status/card.ts: the card is split into rendering (which knows
nothing about the host and is testable) and status/main.ts (the wiring). Two bugs in the card
were found by eye before it existed, and both would have been caught here.
The build plan and the decisions behind the design are in
docs/session-focus-panel-plan.md.
Two things run in CI: check.yml on every push to main — the same gate, publishing nothing —
and release.yml on a v* tag, which re-runs it and then publishes the plugin. See below.
Releasing
The extension and the plugin ship separately out of this one repository. The plugin is published from GitHub Actions using npm's Trusted Publishing — there is no npm token anywhere, on this machine or in the repo.
# Bump whichever halves changed, and note it under ## Unreleased in CHANGELOG.md.
npm version minor --no-git-tag-version
npm --prefix agent/plugin version minor --no-git-tag-version
npm run check # green before you tag
git commit -am "Release x.y.z"
git tag vX.Y.Z && git push --follow-tags # the tag is what publishes
Pushing the tag runs .github/workflows/release.yml, which re-runs the full gate, proves the
committed bundles match their sources, checks the tag against the version being published, and
then publishes agent/plugin. The extension needs nothing beyond that — it is installed from this
repository or its git URL.
One-time setup on npm, without which the workflow cannot authenticate. Register the workflow as the package's trusted publisher — the CLI is the reliable way, since it cannot mis-type the filename that has to match:
npm trust github @richkershaw/openchamber-session-focus-plugin \
--file release.yml \
--repo richkershaw/OpenChamber-SessionFocusPanel \
--allow-publish
It asks for a one-time password. Then confirm, and keep the id it prints — that is what
npm trust revoke takes:
npm trust list @richkershaw/openchamber-session-focus-plugin
The same thing exists as a form on the package's settings page at
npmjs.com/package/@richkershaw/openchamber-session-focus-plugin/access, but every field has to
match the workflow exactly, and a mismatch comes back from the registry as a 404 rather than an
auth error — worth knowing before you go looking in the wrong place.
npm pack ./agent/plugin still shows exactly what would ship — package.json, README.md and
dist/index.js, with @opencode/plugin declared as a dependency rather than bundled in.
Publishing by hand
Now a fallback rather than the normal path, and it needs a token that npm will keep asking for a
one-time password, because npm publish from a machine has no OIDC identity to present:
npm run publish:plugin
Known limits
- Section-only. There is no rail panel and no full-screen page; the card lives in the Work Status panel and scrolls inside it past 320px.
- The host unloads the frame when the section is collapsed, so the card rebuilds from the file on every mount. It polls every 2.5s while visible, so an agent update appears within a few seconds.
- Motion is the only cue that something is moving, and it is deliberately suppressed under
prefers-reduced-motion: reduce. Every state still carries its glyph, its word and its tone, so nothing is conveyed by animation alone. - A session that moves to another directory does not take its store with it — the file is keyed by session but stored in the project.
- Nothing prunes finished focus files yet.
Changelog
See CHANGELOG.md for release notes.
Licence
MIT.
同类生态推荐
With Claude
opencode-with-claude
OpenCode plugin to use your Claude Max subscription via Meridian proxy
Goal
opencode-goal-plugin
Durable, guarded goal workflows for OpenCode.
With Claude
@suiramdev/opencode-with-claude
OpenCode v2 plugin to use your Claude Max/Pro subscription via the Meridian proxy