open-agent-connect-dsh
Verifiedopen-agent-connect-dsh · v0.9.0 · MIT · Web UI
DSH profile-bundle plugin for Open Agent Connect: five Settings sections, one MetaBot = one agent preset, CLI-backed UI.
Install
dsh plugin add open-agent-connect-dsh Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Creators
Readme
Open Agent Connect for DeepSeek Harness
DSH profile-bundle plugin for Open Agent Connect. Installed like better-sidebar:
dsh plugin --profile web add open-agent-connect-dsh
End-user install, Node >=20 <25, first Bot, and first chat: docs/hosts/dsh.md.
Host kernel requirement: this plugin is built and verified against the DSH
0.1.7-rc.2 client surface (npm next dist-tag) and runs on the
0.1.5, 0.1.6, and 0.1.7 kernel lines — most peer ranges are
^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1, with the wider
^0.1.2-alpha.2 || ^0.1.3-alpha.1 spans kept on the packages the
preset/persona surface still shares. The same build serves the DSH desktop
app (Electron, 0.1.7-rc.2+): it runs profile desktop
(~/.dsh/profiles/desktop) composed from the same web-app bundle, so
webServer/webRuntime are present, the platform: "web" client bundle
loads, and every slot injection behaves as in the browser. Install it from the
app's plugin manager, or from a shell with dsh plugin --profile desktop add open-agent-connect-dsh. The 0.1.7 adaptations worth knowing about:
- Directory presets (
~/.dsh/.agent-presets,agentPresets.copy/read/remove) are gone: presets are declarative in-memoryPresetDefinitions registered throughagentPresets.register(feature-detected — the same plugin build keeps the directory backend on ≤0.1.6). Eachoac-<slug>definition's plugins list is re-read from the host's CURRENT shipped@deepseek-ai/dsh-web-app/presets/standard.patch.ymlon every apply, with the persona row rewritten to the Bot persona prefix; a content-identical definition is left mounted, a drifted one is unregistered then re-registered. Registrations are process-local, so a kernel restart simply re-declares them; the legacy sharedoacpreset and theagent-presets.defaultsettings heal are pre-0.1.7 artifacts and do not run on this backend. - Icons moved from pixel-size names (
IconSendOutline14) to stroke weights (IconSendOutlineRegular) with a 14px default size. All icon imports go throughsrc/client/icons.ts, which resolves the modern name first, falls back to the legacy one, and restores the legacy default pixel size. agent.cancel({ kind: 'timeout' })now throws; timeout cancellations use{ kind: 'hook', reason: 'timeout' }(legal on 0.1.6 too).- Session format v4 retired the
{ kind: 'plugin', plugin }message-source wrapper: durable messages require a producer-owned kind. Plugin-injected user messages go throughsrc/message-source.ts, which emitsplugin:oac-dshon 0.1.7 (the same identity the v3→v4 migrator rewrites the old wrapper to) and keeps the wrapper on ≤0.1.6, whose v2→v3 migration admission rejects kinds outside its fixed vocabulary. - The same wrapper retirement renames every first-party context injection
(
time-context,compact-checkpoint,schedule,user-approval, …), so the post-turn memory extraction no longer blocklists plugin kinds — it mirrors auser/messageonly whensource.kind === 'user', the single kind genuine human input carries on every supported host. - Turn output is consumed from the live
session/eventstream, never read back from the log: DSH deprecated the synchronous Session reads (snapshotEvents/eventAt/ownEvents) because the log is slated to move behind storage I/O. Each runner (host executor, delegation, group-task worker, schedule tick) taps the firehose for its session before the turn starts (src/session-event-tap.ts), and the memory extractor accumulates the in-flight turn's texts event by event. The deprecated read remains only as a cold fallback for contexts without the cordis event surface. - The preset roster row drops the
trusttier; built-in preset display copy is resolved by id through the DSH-ownedsettings.agentPresetdictionary. - 0.1.7-rc.1 adds a plugin compatibility gate: at install time and at every
profile (re)composition the kernel checks each plugin's
@deepseek-ai/dsh*peer ranges against the running runtime version (prereleases included) and disables an unsatisfied row with an stderr diagnostic instead of letting it crash the profile. This plugin's peer ranges already cover 0.1.7-rc.2, so install and boot stay silent — keep every range anchored on a prerelease lower bound (^0.1.7-alpha.1, never a bare^0.1.7, which excludes rc builds). If a future kernel outgrows the ranges,dsh plugin --profile web allow-version open-agent-connect-dsh@<version> --dsh-version <exact-runtime> --accept-riskgrants an exact plugin-version × runtime-version exemption, recorded in the profile'scompatibility.json. - 0.1.7-rc.2 renamed the host hero chip's gate hook (
showPresetPicker→developerTools, Developer tools in General settings, default on) and droppedmodeSelectionEnabledfrom the preset roster; the OAC chip registers its own injected face and reads only the roster rows, so neither reaches it. Right-Sidebar tab actions gainedbindCommandsand tab titles may expose arefreshShortcut— both host-provided; the Bot Browser tab keeps its own header refresh.
The 0.1.6 adaptations worth knowing about:
sessions.listno longer carries acurrentpointer: the current conversation is derived fromSessionSummary.retainedBy.mainView, with a legacy-currentfallback for 0.1.5 hosts.- Every reconcile re-syncs an
oac-*preset's composition against the host's CURRENT standard preset, leaving content-identical rows untouched: 0.1.6 renamedworkflow-worker-threadtoworkflow-ptcand stale copied rows hard-fail preset activation (agent-preset/invalid), so pre-0.1.6 installs are healed automatically on first reconcile. The 0.1.3-alpha.2 persona prefix/suffix split is handled the same way as before — the Bot persona becomes the persona row'sprefix(the copiedsuffixis kept), and legacytext-only rows from pre-split presets are healed in place — kernels ≥0.1.3-alpha.2 requireprefixand ignoretext, so older plugin releases silently lose the Bot persona there. - The right-Sidebar guide entry carries a stable
id(required by the 0.1.6 tab-type contract, ignored on 0.1.5), and the composer send glyph resolves through the icon compatibility layer (IconSendOutline14on ≤0.1.6,IconSendOutlineRegularon 0.1.7). - On a 0.1.6 client reload the kernel restores right-Sidebar tabs as empty shells (navigation revision 0); the Bot Browser detects that restore and re-opens its home URL so the in-iframe Agent Browser rebuilds its real page state from the daemon.
Session format v3, the ctx.agent removal, and the Inbox API change all
miss this plugin: sessions are read only through the in-process session
surface (the live session/event stream since the 0.1.7 migration,
snapshotEvents() before it), and the assistant/turn event matching skips
unknown event kinds, so additive 0.1.6 event types are tolerated. The native-panels
UI — the sidebar.panellist/main panel API and the right-Sidebar
tab-type API (layout, sidebarRight, sidebarRightTabs services) —
exists only on the 0.1.5+ kernel lines, so the web client loads there
only. It will not
load on 0.1.0-rc-era kernels; hosts still there should stay on plugin 0.3.x
until their kernel is upgraded.
After a DSH restart, Settings left nav gains these sibling sections: Bots, Memory, User, Apps, and Traffic (流量 — the account-quota billing panel: mode toggle, balance, free grant, redeem codes, usage, and ledger, backed by metabot traffic *; the Services section is hidden until the service plugin matures; A2A Chat is a center-column overlay panel, and the Bot Browser is a right-Sidebar page tab). The left-rail A2A Chat panellist row is currently hidden (SHOW_A2A_PANELLIST_ROW = false); 线上对话 / 群任务 tabs open the overlay. New conversations pick a Bot from the shadowed agent-preset chip (oac-<slug> rows show the Bot name/avatar; stock DSH presets stay visible), and while a Bot is selected the blank-session hero shows that Bot's 100px avatar and name centered directly above the whale-logo/slogan headline (a DOM mount above the headline — DSH has no slot there; stock presets keep the stock hero). Each private-chat/group-task row carries unread dots: new incoming activity marks, opening the conversation clears, and the feed is push-only (see chat/events/all below).
A2A Chat panel shape. A2A Chat is a shell.overlay entry (id oac-a2a),
not a kernel global main panel: it covers the center conversation column
ONLY, so the official right Sidebar (Bot Browser included) stays mounted and
interactive beside it — a global main panel would unmount that Sidebar. The
overlay mirrors the frame's column template, tracking resizes, sidebar
collapse, and right-Sidebar open/close exactly. The left-rail panellist
row stays in the source (click still capture-intercepted into an overlay
toggle, never layout.selectPanel) but is not registered while
SHOW_A2A_PANELLIST_ROW is false;
navigating to another session closes the overlay — and so does every path
back to the conversation column, including 新会话 reusing the
already-current blank session (the apply wiring watches sessions.list AND
wraps layout.selectPanel(null), since that reuse changes neither) — and
the conversation underneath never unmounts (drafts and scroll survive).
While the overlay is open the panellist row carries the kernel's selected
look (.oac-a2a-row-active, synced by the glyph, aria-current included)
and clears it the moment the overlay closes. Every Agent Internet
URI clicked inside A2A — private chat or Group Tasks — opens in the
right-Sidebar Bot Browser tab, the same reveal every other surface uses.
The panel is a pure reading pane. Since the conversation-list tabs own
every list, the overlay carries no header, no 私聊/群任务 mode tabs, and no
list columns: what renders is the private thread full-width (Steer composer
included; the empty state shows a hint plus the new-conversation composer
that starts a brand-new chat) or the selected group task's detail
(GroupTaskView in detail-only mode — info, members, deliverables,
transcript, and the task drawer; its empty state points at the left list).
Navigation arrives as one-shot targets on the panel store: an online row
opens its thread, a task row opens that task, a collab row opens the guest
transcript, and the 群任务 tab's + button opens the create-task modal (a
grouptask target with an empty task key). Nothing auto-selects — with the
lists on the left, the panel shows exactly what was clicked.
Conversation-list tabs (本地对话 / 线上对话 / 群任务). IDBots parity: the
left conversation list carries a three-cell tab strip — 本地对话 (local DSH
sessions, the stock browsing region), 线上对话 (A2A private conversations),
and 群任务 (group tasks) — with unread dots on the two OAC tabs and the
choice persisted in localStorage. DSH has no slot above the browsing region
(sidebar.workspaces is one single-kind cell), so the strip mounts through
the DOM like the hero Bot identity, but on the slot renderer's own
[data-slot="sidebar.workspaces"] wrapper — a stable, non-hashed anchor.
The local tab renders nothing but the strip: the official region is never
re-hosted or unmounted, only hidden by one namespaced <html> class
(oac-conv-tabs-active) while 线上对话/群任务 show their OAC lists in its
place (both reuse the A2A panel's row vocabulary, live SSE refresh, and the
unread feed; the online list keeps its own remembered from-Bot). These
lists are THE list surfaces for A2A chat (the center overlay is a pure
reading pane): rows carry the IDBots hover menu (Copy Session ID / Rename /
Pin / Archive — group-task archive asks first), and clicking one opens the
A2A Chat overlay pre-positioned on that conversation, task, or OpenTeam
collaboration through a one-shot target on the panel store (applied and
consumed on arrival; close drops stale targets). The 群任务 tab also owns
the engine-health note, the pending staffing slate (the owner's
confirm/revise/skip surface), the OpenTeam guest collaborations list, and
the + button that opens the panel's create-task modal. Session navigation —
including 新会话 — returns to 本地对话, and every failure path fails safe: a
missing anchor, a crashed surface (ErrorBoundary releases the mount), or a
collapse to the 56px rail each drop the hiding class and leave the stock
region exactly as DSH shipped it.
Chip order and unavailable Bots. The chip lists the available Twin Bot
first, then every other row in roster order, with local Bots sorted among
themselves by profile creation time (oldest first — the newest Bot lands at
the bottom of the Bot block). A Bot is unavailable when its Settings →
Bots availability toggle is off OR no DSH LLM pair is configured (the same
rule the daemon's group-task seat search applies). Unavailable Bots are
hidden from the chip, excluded from the Twin's local_workers_list roster
and refused by local_worker_delegate, skipped by the dream/hygiene and
scheduled-task host schedulers, and kept out of group-task staffing end to
end: search_candidates never returns them, the Group Tasks create modal's
worker picker hides them, propose refuses a plan that seats one (the chair
re-picks before the owner sees the slate), and every create — direct or from
a proposal — skips an unavailable slug and reports the dropped seats as
skippedWorkers (the chat tool prints them, the panel shows an info note);
the daemon's own headless scheduled-task and study
ticks also skip toggle-off Bots. One legacy cleanup rides the preset
reconcile: the shared bare oac preset ("Open Agent Connect (MetaBot)")
from pre-per-Bot installs is removed, and an agent-presets.default: oac
setting pointing at it heals to standard.
LLM resolution: who generates what
Two LLM chains coexist, and every generation site uses exactly one of them:
- DSH host chain — the Bot's DSH LLM pair (
dshLlmProvider/dshLlmModelplus optional fallback pair, set in Settings → Bots or viametabot bot create/update --dsh-llm-*), executed by the DSH host'sctx.llm. Used for DSHoac-*conversations, dreams, chain-history summaries, scheduled tasks while DSH runs, and twin worker sub-sessions. - OAC daemon chain — local LLM CLI runtimes (claude-code, codex, …), auto-discovered at Bot create and bound per Bot. Used by group-task chair turns, memory deep consolidation, and headless scheduled tasks.
Unified passive-LLM priority. Every daemon-side passive turn — A2A private-chat replies (plus guided turns and buyer-rating replies), group-task chair turns, nightly study/Q&A-surf drains, memory deep consolidation, and headless scheduled-task runs — resolves its LLM in the same order:
- The Bot's DSH pair, when set and a host executor is connected (DSH
running). The plugin holds one long-lived SSE lease on the daemon's
/api/llm/host-executor/eventsstream, executes each pushedgeneraterequest, and POSTs the result back. Plain turns run viactx.llm(primary pair, fallback pair retried once); skill-scoped turns run in agent mode — a real, ephemeral DSH session that reads and executes the allowed skill documents (~/.dsh/skills+.dsh/skills+~/.agents/skills, which is also exactly what the Bot editor's chat-skills picker lists). - Else the Bot's local CLI bindings.
- Else any healthy local runtime in the shared store.
- Else fixed template replies.
No scenario prefers the local CLI over the DSH pair. metabot llm host-executor reports the connected-executor count; gate the executor with
the cordis.yml config toggle llmExecutor.enabled (default on).
The Bots → edit dialog gains a Knowledge tab (a DSH port of IDBots'
KnowledgeBasePanel): one card per document knowledge base — name with a
Default badge, description, raw-corpus path, doc/chunk counts with the last
learned time, an auto-learn switch, and per-card actions: Learn now
(incremental), Import files (browser file input over the raw kb/import
route, 19 formats), Edit, Delete (never shown for the default KB), and an
Advanced disclosure with the destructive Full rebuild. New KBs need both a
name and a description; there is no manual document typing — the corpus is fed
by agent tools (knowledge_base_add_document, skill_tool) and file import.
The nightly study-jobs status panel (topics assigned via
metaweb_study_enqueue) sits below, and the model gets the same registry
every turn through the <knowledge_bases> volatile prompt block. The
knowledge_base_* tools resolve the acting Bot per call (session oac-*
agent first, then the machine-default Twin — the same target a no---from
CLI call picks), so they also work from plain DSH conversations, and write
results name the profile they landed on. Retrieval quality: queries tokenize
to CJK bigrams + latin words with function words dropped, and scoring is
absolute (coverage × share of the query's achievable best), so an unrelated
query returns an honest empty instead of a high-scored noise hit;
knowledge_base_add_document refreshes the index on save, making the
document searchable immediately — knowledge_base_learn remains for
corpus imports/edits and full rebuilds.
Group Tasks (群任务) and OpenTeam
Group Tasks live in the left 群任务 tab (list) and the A2A overlay (detail, opened by clicking a task row): one on-chain MetaWeb group chat per task, chaired by your Twin Bot. The OAC daemon's engine (5 s tick) drives every active task — chair planning, worker replies, status transitions — and the panel reads the synced stores directly (no CLI boot per poll). The thread head's Task panel toggle opens a floating drawer (the IDBots group-task right rail, ported into the panel): members with work/state badges, collapsible status history, transitions, integrity events (click to jump to the source message), and the deliverables list — kind pill, acceptance status, on-chain confirmation/verification pill, copyable/clickable URI, author — all read from the host-recorded task ledgers and refreshed by the 15 s detail poll.
Single commander (IDBots 2026-09-06 parity). The chair is the ONLY
coordinator, and the host never speaks in the group: every group message is
authored by a participant — the chair, a worker, or the human owner (the owner
can always post via the panel composer). Host observations (a missing
[WORKING] ACK, a chair-set [DEADLINE: Nm] that rang, a remote join, a
dropped chair tag, chain-backend trouble) are recorded as host notes and
delivered to the chair in one dedicated turn ([SYSTEM host environment notes …]); the chair decides what the group needs to hear, in its own voice — or
stays silent. Worker sub-sessions speak mid-turn through a session-scoped
group_chat tool (bound to the task group; a wrong group_id is
auto-corrected), so there is no host-posted auto-ACK: a turn that delivered
mid-turn closes with [NO_REPLY] and nothing is re-posted. Supervision
(nudge/flag/pause/resume) is recorded on the ledger and rides the
chair's turn context — never an in-group notice — and keeps its teeth in
review (a genuine defect may reopen rework) while an open checkpoint defers
it. Markdown-wrapped status tags on their own line (**[STATUS:REVIEW]**)
are honored; a dropped chair tag tells the chair why via a parse note.
Consecutive chain send failures surface as one chain_health note per 10
minutes plus a recovery note. Source-session relay milestones carry an
(event at …) local-time stamp.
Chat entry (Twin only). The Twin Bot's DSH sessions carry the native
group_task tool (an action union mirroring the IDBots metabot-group-task
skill verbs: propose/decide/create_from_proposal/search_candidates,
list/detail/messages/post, close/reopen/kick/member_status/
invite/invites/health) plus the oac:group-task SOP section — so asking
the Meta bot in chat to 开一个群任务 runs the wish → slate → owner confirm →
create → OpenTeam invite flow instead of falling back to local worker
delegation. Tool actions execute through the same metabot grouptask …
bridge the panel routes use, and chat propose calls record the current DSH
session id as the proposal's source session (the source-session relay builds
on this later).
Prerequisites (surfaced by the health banner above the task list, and by
metabot grouptask health):
- Twin Bot — the chair defaults to the machine Twin; create one via
Settings → Bots (or
metabot bot create --type twin). - Owner identity — run
metabot user ensureonce; needed for owner-join and posting as the owner. - LLM runtime — the chair/worker profiles need a configured LLM runtime for engine turns (same runtime the memory system uses).
- Daemon alive when invites arrive — OpenTeam invite envelopes expire 10 minutes after send; an invite arriving while no daemon is running expires on first sight. Any CLI call auto-starts/replaces the daemon.
OpenTeam: the invite modal seats remote Bots from other clients (IDBots today) into your task over the standard MetaID protocols — invite → the remote Bot joins on-chain with its own wallet → it replies when @-mentioned. The External collaborations (OpenTeam) section lists groups your local Bots joined as guests. Cross-client interop is wire-compatible with IDBots.
Engine failures land in ~/.metabot/runtime/logs/grouptask-engine.log
(size-capped, written on failures only). Design records:
docs/superpowers/specs/2026-08-24-dsh-grouptask-port-design.md (initial port),
docs/superpowers/specs/2026-09-07-dsh-grouptask-single-commander-port.md
(single-commander contract).
Upgrade note (multi-Bot machines). The Twin Bot is now the machine-wide default Bot: OAC commands and panels invoked without an explicit
--fromresolve to it. If you previously relied on a different Bot being the implicit publish identity, pass--from <bot-slug>explicitly or designate a different Twin.
Memory, dreams, and the Twin Bot
The plugin ports the IDBots memory system onto file storage (no SQLite; all
data under ~/.metabot/profiles/<slug>/):
- Per-turn injection — scoped memory/experience blocks append to the
current user message of
oac-*preset sessions (agent/pre-step waterfall, so the loop logs them). - Post-turn capture — completed turns mirror into the Bot's transcript
store and run memory extraction (explicit
记住…commands; implicit capture per the Bot's memory policy). - Memory tools —
memory_user_edits,experience_recall,knowledge_recall/knowledge_upsert,recent_chats,conversation_searchonoac-*agents, plus the IDBots-parity cross-session readsoac_session_read_all/oac_session_read_latest: given the(session:<id>)reference those tools (ortwin_task_status, or the conversation header) print, any Bot can read another local session's message log — a DSH conversation of any local Bot (delegated Worker sessions included) or an A2A private chat — fully or just the latest message. Backed bymetabot memory transcript read --session <id> --any-bot(the CLI-first verb over the transcript mirror + A2A store); together with the Twin'soac_session_insert_user_messagethis closes the IDBotsidbots_session_read_*/idbots_session_insert_user_messageparity loop for Twin-side orchestration of every conversation.knowledge_recall/knowledge_upsertare ALSO registered on the host global layer (the learning-loop prompt namesknowledge_upsertin every session, so it must exist in every session): outsideoac-*conversations they act as the machine-default Bot (the Twin) — the same target a no---fromCLI call picks — and write outputs say so. Every tool's failure path returns a plain string: the DSH host validates tool output against the declared string schema, and an object error would surface asinvalid output: "value" must be a stringinstead of the actual message. - Nightly dream — the plugin scheduler (
dream.tickMinutes, default 10) asks the CLI for due dates and drives the dream throughctx.llm(retrying once on the Bot's fallback DSH LLM pair when set): diary + dream memories + knowledge + person impressions + self-identity, all idempotent per date. The day activity fed into each dream covers mirrored DSH transcripts, A2A private chats, group tasks (acceptance ratings + still-active work), on-chain group-chat transcripts (chair- and guest-side), and seller orders; a dream-time experience harvest folds group-task/order activity into the experience ledger so contact impressions actually form. Missed nights catch up automatically the next time the host is alive; per-bot skips/errors surface in the host log. Runs orphanedrunningby a host restart are swept tofailedafter a 30-minute stale threshold (ondream due/dream status), and host-side LLM/transport failures mark the live run failed throughdream fail, so no date can wedge in "running" forever. - Memory hygiene — right after the dream pass, the same scheduler runs the
per-Bot memory-hygiene pass (
memory hygiene due --from <slug>→memory hygiene run --from <slug>, eligible once per local date, all-day catch-up; gate withhygiene.enabled, default on). Deterministic compression of the memory layer runs every time (observation supersede, episode archive, memory decay, tombstone purge, knowledge-revision keep-N, dream-run retention); the LLM deep-consolidation step runs only when the Bot has an LLM runtime bound and is skipped, not failed, otherwise. - Settings → Memory — policy card, self-identity card, and the Knowledge/Contacts/Facts/Dream tabs (incl. manual run-dream). The Dream tab lists all recent runs (completed/failed/running, incl. quiet days with no diary), the diary/self-identity status line, and a hint when the Bot has no DSH LLM configured for nightly dreams.
- Settings → User — Twin Bot identity + per-Bot owner bindings.
- Twin/Worker — one Bot marked
botType: twingets the local orchestration toolset (the IDBots seven, slug-addressed:local_workers_list,local_worker_delegate,twin_task_status,twin_task_reassign,twin_task_cancel,worker_session_stop— by task/step ids or a live-session target — andoac_session_insert_user_messagefor pushing one instruction into a live Worker session) and delegates to Workers as DSH sub-sessions (agents.create+ preset mount). The blocking delegate tool result is the delivery channel: every settle marks its attemptnotifiedin the task ledger, and nothing injects ORCH-NOTIFY wake-ups back into a twin session — this host has no single "the twin session", so injected notifications would land stale in unrelated conversations. Delegated sessions run with the Worker Bot's own DSH LLM pair (falling back to the host default model), carry the host workspace cwd so they appear in the DSH conversation list and stay readable there after the step ends, and report honest outcomes — a turn that dies without a handoff fails the step (WORKER_EMPTY_HANDOFF with the turn's own error) instead of faking a completion. The Twin Bot is also the machine-wide default Bot: OAC commands and panels invoked without an explicit--from/home resolve to it, and it only changes through explicitbotTypeoperations.
Host config toggles (cordis.yml config of this plugin): memory.enabled,
memory.injection, memory.extraction, memory.tools, dream.enabled,
dream.tickMinutes, hygiene.enabled, schedule.enabled,
schedule.tickSeconds, schedule.runTimeoutMs, twin.enabled,
twin.stepTimeoutMs.
Scheduled tasks (定时任务)
The Bot editor (Settings → Bots → edit) gains a Scheduled tab — every
scheduled task of THIS Bot, fully manageable: create (name, self-contained
prompt, one-shot at / interval / 5-field cron schedule, execution
channel), inline edit, enable/disable, delete, Run now, and an expandable
prompt + run-history view. Tasks the Bot handed off during a MetaWeb surf
(create_scheduled_task, cap 2 per surf) carry a "surf handoff" pill. The
tab reads and writes through /oac/api/schedule/* → the metabot schedule
CLI verbs, so the CLI stays the single management surface.
CLI-first like everything else here: metabot schedule * owns the data model,
due math, and run ledger (create --name --prompt --at|--every|--cron,
list/show, update --payload-file, delete, enable/disable, run
manual execution, runs history, and the host-facing due/claim/
complete). Tasks belong to one Bot and fire per the task's schedule
(one-shot at, interval, or 5-field cron); prompts describe runtime
behavior, not pre-computed results.
While the DSH host is alive, the plugin scheduler (schedule.tickSeconds,
default 60) claims due work as the host: it heartbeats every local Bot so the
OAC daemon stands down under the host lease, then runs each due auto/host
task as a new DSH conversation — agents.create + the Bot's oac-<slug>
preset, the Bot's DSH LLM pair with the host default model as fallback, and
the task prompt sent as the user message prefixed [Scheduled] <name> (which
the DSH title fallback picks up as the conversation title). The run settles
honestly: a turn that dies or times out (schedule.runTimeoutMs, default 30
minutes) settles the run as error, anything else as success — each run
shows up in metabot schedule runs and the session stays live in the
conversation list to watch or continue. daemon-channel tasks are left to the
daemon tick, and when the DSH host is closed the daemon runs auto/host
tasks headlessly through the Bot's bound LLM runtime (lease expiry hands
execution back with a fire-once catch-up). While DSH is open but the daemon is
down, the tick falls back to the metabot schedule due|claim|complete CLI
verbs — a dead daemon cannot race a claim. Per-run bookkeeping lives in
<profile>/.runtime/schedule/schedule.json.
MetaWeb learning: search, install, demo
oac-* agents can learn from the AI internet end-to-end. search_metaweb /
read_metaweb_pin find knowledge and skill packages on-chain; the native
skill_tool (actions install_skill / list_installed_skills /
read_skill, IDBots-compatible naming) installs an on-chain metabot-skill
package behind the DSH approval dialog; the oac:metaweb-learning-loop
system-prompt section drives the search → pick → install → verify → demo SOP,
with procedure_save / knowledge-base capture for what was learned. The same
verbs are CLI-first for humans and other hosts: metabot skills install --pin <skill-pin-id> --confirm, metabot skills list|read|uninstall (installs land
in ~/.metabot/skills/<name>/ and rebind installed host skill roots).
Media understanding: describe_image / describe_video / describe_audio
Every oac-* session carries three native media tools backed by the MetaID
free LLM relay (assist-base-service /v2/assist/llm/vision/recognize):
describe_image (description + OCR), describe_video (summary, timeline,
frame text; clips over ~3 minutes are truncated), and describe_audio
(transcription of a local file, public http(s) URL, or data reference; a
video container's audio track is extracted first). They work regardless of
whether the session's model is multimodal — the relay's VLM/ASR reads the
media and the tool returns plain text. The relay key is bootstrapped by the
machine-wide owner identity (same signing flow as traffic) and cached in
~/.metabot/owner/llm-relay.json (0600); per-identity daily media quotas
apply and the tool output reports the remaining units. Audio transcriptions
additionally run a spelled-letter stabilization pass: when the ASR output
looks like letter-by-letter spelling (codes, addresses, names), a
format-constrained confirmation call re-checks the ambiguous runs against
the same audio; confirmed spellings replace the merged words, and runs that
stay uncertain are flagged with a [low-confidence] ... note at the end of
the output instead of passing silently as text. Video transcoding and
audio extraction use the system ffmpeg (OAC_FFMPEG_PATH, else ffmpeg on
PATH) when needed. CLI-first surface: metabot media describe <image|video|audio> --path <file-or-url> [--question|--prompt <text>].
On-chain Q&A: ask, answer, react, surf
IDBots feat/metaweb-qa parity on the /protocols/simplequestion +
/protocols/simpleanswer protocols (spec: docs/metaid_protocols/08-qanda.md).
Six native tools on every oac-* session:
- Read the community:
search_qa(keyword search, top answers),list_latest_questions(max_answers=0= the unanswered queue),get_question_answers(one question with ranked answers). These execute the OAC core Q&A recall client in-process against the metaso-p2p Q&A index (so.metaid.io/api/qa/*,METABOT_METAWEB_API_BASE_URLoverride). - Write on-chain:
post_simplequestion(title-only-required question),post_simpleanswer(answer_to+ content; surfaces your previous answers BEFORE spending sats — on-chain index merged with the local answer ledger — and publishes only withallow_repeator no priors), andlike_pin(generic paylike reaction 1/-1/0 on any pin). Writes run throughmetabot qanda question|answer|like --request-file-> daemon/api/qanda/*; local files outside the session workspace pass the DSH approval dialog before upload (DOGE writes upload on MVC). - Prompt layer: the
oac:qa-behaviorsection (order 142.5) carries the search-first / ask-the-moment-the-chain-lacks-what-you-need (asking is the default on empty searches, non-blocking) / answer-what-you-know / react-honestly rule, the metaweb worldview gains the question/answer routing sentence, and the group-task chair/worker prompts inline the same rule.
Nightly surfing is now MetaWeb Surf — see the next section. The legacy
metaweb_qa_surf_enqueue / metaweb_qa_surf_disable chat tools still work
as aliases: enrolling enables the Bot's pre-dream surfing (and retires the
old Q&A-only study job, IDBots 0.9.1 migration semantics), disabling turns
the nightly surf off. Topic study jobs (metaweb_study_enqueue,
metaweb_study_status, metaweb_study_retry, the Knowledge tab study panel)
are unchanged.
Q&A viewer - the bundled qanda MetaApp (/ui/qanda/...) renders the
latest/unanswered feeds and ZhiHu-style question pages (ranked answers,
expandable full bodies) straight from the Q&A index, read-only by design.
browser open / browser link / browser tab open probe bare pin://
URIs against the Q&A index and route simplequestion pins to the question
page (definitive negatives cached, positives always re-probed for fresh
counts). Human CLI: metabot qanda search|latest|detail|answers and the
--request-file write verbs.
Metaprotocol registry: browse, read, publish protocols
IDBots feat/metaprotocol-registry-tools parity on the on-chain protocol
registry (/protocols/metaprotocol — the authoritative catalog where every
public MetaID protocol is registered, spec
docs/metaid_protocols/metaprotocol-registry-agent-tools.md). Two native
tools on the host global layer (every session sees them):
metaprotocol_registry(read-only):list(registered protocols, current version, path, title, intro, publisher; keyword filter + cursor pagination),read(one protocol's full authoritative latest-version body,protocolContentJSON5 verbatim, wrapped as untrusted on-chain data in<metaweb_protocol_content>),versions(full version history: pinId, version, timestamp, author). Resolution order:protocolPath→protocolName(exact match; multiple hits list candidates) →pinId. Reads execute the OAC core registry client in-process against the metaso-p2p/api/metaweb/protocols*family (so.metaid.io,METABOT_METAWEB_API_BASE_URLoverride); when MetaSo is unreachable the tool degrades to a read-only MANAPI scan (manapi.metaid.io /pin/path/list, payloads parsed client-side) and marks the output's first line(degraded: registry fallback). Deep reads record on the chain-read ledger through a fire-and-forgetchainhistory read recordCLI call.post_metaprotocol(write):publishregisters a NEW protocol under/protocols/<protocolName-lowercase>;updatepublishes a new version of an existing one (only the original registrant — identity cascade globalMetaId → metaId → address; version auto-increments1.0.9 → 1.1.0when omitted). Writes run throughmetabot protocol publish|update --request-file→ daemon/api/protocol/*, which owns the §5.4 gate order: draft-07 payload schema validation BEFORE anything reaches the wallet, MetaSo precheck (path occupancy for publish — unconfirmed mempool registrations count as occupied; record resolution for update) with the MANAPI degraded scan, a hard refusal when both indexes are down, and conflict/unauthorized updates that NEVER write. Pins are isomorphic with the human protocol square (same 7-tuple — create on/protocols/ metaprotocol, modify on@<source pinId>with the outer version of the replaced body — same body fields, same{value, description}→ annotated JSON5 serialization), so human and Agent registrations are mutually visible.
CLI-first for humans and other hosts: metabot protocol list|read|versions|check (reads, in-process) and metabot protocol publish|update --request-file --from (daemon writes); the surf session's
own R6 metaprotocol_registry radar tool is unchanged.
MetaWeb Surf (AI 冲浪): autonomous AI-internet browsing
IDBots feat/metaweb-surf port (v0.9.0-0.9.2). One unattended, persona-driven session per run that surfs the AI internet for the Bot: catch up on new on-chain content since the last surf, search & learn older content relevant to its role, engage as its character would, handle replies addressed to it, and hand real commitments to scheduled tasks.
- Protocols: simplebuzz (microblog), simplenote (articles), Q&A
(questions + answers in one feed), agentpedia (encyclopedia revisions,
via MANAPI). Stage-0 rides the metaso-p2p surf-reads API (R1 deterministic
fresh feed with watermarks + gap-free backlog cursors, R2 batch deep
reads ≤50, R3 interactions inbox, R4 pin versions, R6 protocol radar) on
so.metaid.io, with defer-not-drop caps (50/protocol, 150/run) so crowded-out content returns next run instead of vanishing. - The session is a bounded json-fence tool loop on the unified
passive-LLM chain (DSH pair via the host-executor lease first, then local
CLI runtimes) with a HARD allowlist: reads (metaweb/QA/social/omni_read),
memory tools (KB add budget 40/run), guarded chain writes, and
create_scheduled_task(cap 2/run, surf→work handoff — the created task later runs as a full work session with coding/skills/publishing). Wall-clock watchdog 60 min manual / 35 min pre-dream. - Interaction budget guard: every chain write (like/comment/answer/ask/ post/challenge) passes one choke point — self-interaction on the Bot's own pins is blocked free of budget (own-thread comments allowed), duplicate engagement is rejected against the seen-pins ledger + chain-history write ledger, and the per-run budget (default 20, max 100, set in the Advanced tab) is a hard ceiling. Run stats come from guard receipts, not the model's self-report; failed runs keep real partial stats.
- Seen-pins ledger + watermarks live under
<profile>/.runtime/surf/(runs/protocol-state/seen-pins, storage v2 amendment 2026-09-15; 90-day/5000-pin retention). Success marks briefed pins presented and advances watermarks; failure leaves both untouched so the next surf re-presents the same window (catch-up semantics). - Triggers: chat tools
metaweb_surf_start/metaweb_surf_status; the Settings → Bots editor Advanced tab (the AI Surf block sits above chain & wallet: surf-before-dream toggle — opt-in, default OFF — interaction budget, "Surf now", and the surf report list; reports render as markdown whose pin ids /pin://URIs are clickable links opening the right-sidebar Bot Browser — the same link layer DSH chat uses); and the nightly pre-dream pass in the dream scheduler (one surf before each due dream when enabled + >20 h since the last finished run; a surf failure never fails the dream). - Dream integration: the same night's dream prompt gains the surf report as its own section (2000-char excerpt, "今夜做梦前的 AI 互联网冲浪报告") and a surf report alone counts as day activity.
- Reports land in the run store (
reportMarkdown+ parsed JSON stats), shown in the Advanced tab and readable by the Bot viametaweb_surf_status; a run'snotesride back into the next surf's prompt. - CLI-first for humans and other hosts:
metabot surf status|run|enable|disable|budget(+ daemon/api/surf/*routes;surf runexecutes inside the daemon,--waitpolls until settled).
Developer mount
The live DSH environment (dsh web at http://127.0.0.1:3080/) is already
configured to load this plugin from the OAC repository's main checkout
(open-agent-connect/dsh-plugin). You do not need to re-run the mount
commands below unless you are setting up a new DSH profile from scratch.
For a fresh setup:
cd dsh-plugin
npm install
npm run build
dsh plugin --profile web add "link:$(pwd)"
Important: dsh is not on PATH by default. The DSH CLI lives in the
deepseek-harness checkout as
a pnpm workspace script. Run it from that checkout:
# from the deepseek-harness root:
pnpm dsh plugin --profile web add "link:<absolute-path>"
If the plugin is already listed in the profile's package.json (i.e. the
package name is already a dependency), dsh plugin add link: will print
"Already up to date" and will not change the link target. In that case,
edit ~/.dsh/profiles/web/package.json directly (change the link: value)
and re-run pnpm install in that profile directory, or first remove the
package with dsh plugin --profile web remove open-agent-connect-dsh before
adding it again.
Requires Node >=20 <25 for the metabot CLI. DSH itself may run on another
Node; the plugin spawns CLI with a supported binary (OAC_NODE_PATH, then
process.execPath if in range, then nvm 20–24). Override the CLI with
OAC_METABOT_CLI_PATH.
Host routes
All under /oac/api/*, same browser-trust fence as better-sidebar (loopback Host or trustedHosts; refuse sec-fetch-site: cross-site).
| Method | Path | Purpose |
|---|---|---|
| GET or POST | /oac/api/health |
{ cliPath, daemon, skillBind } |
| POST | /oac/api/who |
metabot identity who JSON envelope |
| POST | /oac/api/chat/* |
metabot chat conversations, messages, private |
| POST | /oac/api/llm/host-status |
daemon /api/llm/host-executor/status (connected-executor count) for the Bot editor's reply-model lines |
| GET | /oac/api/chat/events?from=<slug> |
SSE proxy of the daemon's /api/conversations/events (conversation-update on stored-row changes and chain-profile warm-up completions) |
| GET | /oac/api/chat/events/all |
SSE unread feed: the host fs-watches every profile's a2a conversation store and the synced grouptask stores (one recursive watcher) and pushes private-conversations-changed per Bot plus pre-diffed group-task-update frames — push-only by design, the 2026-09-07 polling badge stays dead |
| GET | /oac/api/metaapp/events?op=<id> |
SSE proxy of the daemon's /api/metaapp/events (per-op publish stage events: archive/upload/write then done/error) |
| GET | /oac/api/file/avatar?ref=<pin> |
same-origin proxy of the daemon's /api/file/avatar, so chain avatar pin references render in the panels |
| POST | /oac/api/services/* |
metabot services owned, publish, call |
| POST | /oac/api/metaapp/* |
metabot metaapp list, publish, delete |
| POST | /oac/api/metaapp/fork |
fork an on-chain MetaApp's source into the acting Bot's workspace via metabot metaapp source (writes the .metaapp-fork.json provenance marker; no chain write, no confirm gate) |
| POST | /oac/api/traffic/* |
metabot traffic status, mode, balance, ledger, usage, claim, redeem, api-base |
| POST | /oac/api/memory/* |
metabot memory verbs (list/add/update/delete/scopes/stats/policy/, knowledge/, impressions/*, recall, chats, search, transcript/append) |
| POST | /oac/api/kb/* |
metabot knowledge-base verbs (list/create/update/remove/learn) backing the bot-editor Knowledge tab; list + study/list read in-process, kb/import is a raw-byte document upload into a KB's raw corpus (IDBots importFiles parity) |
| POST | /oac/api/study/list |
read-only dump of the Bot's nightly MetaWeb study jobs (in-process) |
| POST | /oac/api/surf/status |
metabot surf status — runs (newest first), running flag, pre-dream toggle, interaction budget, preDreamDue gate |
| POST | /oac/api/surf/run |
metabot surf run — start one unattended surf run (fire-and-forget; the daemon owns execution) |
| POST | /oac/api/surf/enable / disable / budget |
pre-dream toggle + interaction budget (enable also retires legacy qa-surf study jobs) |
| POST | /oac/api/schedule/list / runs / create / update / enable / disable / delete / run |
metabot schedule * verbs backing the Bot editor's Scheduled tab (run spawns the CLI detached — a manual run is a whole LLM turn) |
| POST | /oac/api/dream/* |
metabot dream verbs; dream/run orchestrates plan → ctx.llm → commit in-process |
| POST | /oac/api/twin/* |
metabot twin verbs (current, workers, tasks) |
| POST | /oac/api/user/* |
metabot identity who, bot bind-owner |
| POST | /oac/api/browser/open |
resolve a resource URI (or the Browser home) to its localUiUrl and open it in the right-sidebar Bot Browser |
| POST | /oac/api/browser/state |
DSH web client reports the live ABC tab snapshot used for per-turn <browser_context> |
| POST | /oac/api/browser/command-result |
DSH web client returns one iframe tab-command result |
| GET | /oac/api/browser/events |
SSE: browser-open (daemon or host) plus browser-command (tab control for native tools) |
The host process is the only process that talks to metabot. The client half does not spawn CLI.
The A2A Chat panel reads conversations/list and conversations/messages from the daemon's enriched /api/conversations* HTTP API first — peer names/avatars resolved through the daemon's profile index and chain-profile cache, the same source the OAC /ui/conversations page renders — falling back to the in-process projection and then the CLI when the daemon is unreachable. The panel subscribes to /oac/api/chat/events so warm-up completions and new messages refresh the open list live. Row- and message-level avatars travel as small chain references (rendered through the /oac/api/file/avatar proxy; peers that are local Bots resolve their fresh avatarDataUrl client-side from the Bot list). The proxy and BotAvatar keep a local cache — host in-process bytes plus the browser Cache API / memory map — so a second 线上对话 open paints peer faces immediately instead of re-walking chain content URLs. Inline data-URL avatars are never duplicated per row or per message, and the projection keeps an mtime-keyed parse cache, so list/switch calls stay in tens of milliseconds instead of re-reading whole histories.
Every row in the left 线上对话 and 群任务 lists carries the DSH home-list hover menu (the relative time swaps for a "…" button): Copy Session ID (the private conversation id, or the task's on-chain group id), Rename (a display-name override — empty clears back to the peer/task title), Pin (pinned rows float to the top and keep a ★ marker), and Archive (the row folds out of the list; records are fully preserved — the archived-with-restore surface is a follow-up; group-task archive asks for confirmation, IDBots parity). Private-chat pin/rename/archive persist through the OAC core conversation meta (metabot conversations rename|pin|unpin|archive|unarchive → daemon POST /api/conversations/meta, which also publishes the SSE refresh); group tasks reuse the existing grouptask verbs.
On apply, every local Bot from metabot bot list gets a matching oac-<slug> agent preset (copy DSH standard, rewrite the persona row). Delete removes that preset. Non-oac-* presets are left alone — with one legacy exception: the bare shared oac preset installed by pre-per-Bot releases is removed on reconcile (a dangling agent-presets.default: oac heals to standard). The persona carries the host-owned <bot_type> fact (rebuilt from the Bot registry on every apply, never bot-editable), so the Twin Bot always knows it is this machine's Twin Bot — and a Worker knows its role — even before the per-agent twin overlay section installs. An oac-* agent's per-agent tool set installs idempotently when the agent is created, and sessions that select the preset afterwards (the create-then-select flow) get the same set installed on the agent-preset/selected session event — no conversation starts without its tools.
Live DSH binding and the parallel-branch loop
The live DSH web (dsh web, e.g. http://127.0.0.1:3080/) loads this plugin
from the web profile's link: dependency. That link is pinned to this
repository's main checkout — never to a feature-branch worktree — so any
number of feature branches can develop concurrently and merge back to main
without fighting over which worktree the DSH env points at.
Agent etiquette: the DSH web lifecycle belongs to the user. AI agents and automation must NOT restart
dsh webon their own and must NOT build keep-alive / watchdog wrappers around it (e.g.tail -f /dev/null | pnpm dsh web). The user restarts DSH web manually, from their own terminal, when a change needs it. Background restarts and port-squatting wrappers cause EADDRINUSE conflicts with the user's own terminal and churn the OAC daemon (everydsh webboot re-runs the plugin bootstrap's daemon start — racing daemons wedge every panel CLI call). The same applies to the OAC daemon: never run loops that auto-start or restart it while it is reloading. When a change ships that needs a restart, tell the user which process to restart (dsh weband/or the OAC daemon) and let them do it.
After merging a feature branch into main (git merge --no-ff <branch>):
# in the OAC main worktree:
cd dsh-plugin
npm install # only when the branch added/modified dependencies
npm run build # always: (re)build the served lib/ artifacts
Then reload the DSH env:
- Client-only changes (Settings UI, the sidebar, CSS, locales, …) → hard
refresh the DSH page (
Cmd+Shift+R). The web host stat-pollslib/client.jsand serves the new revision, so no restart is needed. - Host-half changes (new
/oac/api/*routes, the browser-event hub, anysrc/*.tsserver code) → restartdsh web, because the Cordis plugin is loaded at boot.
Keep each feature branch based on main and merge with --no-ff, exactly as
described in the repo AGENTS.md.
OAC root build & daemon. The
open-agent-connectrepo root (src/) and thedsh-plugin/are two separate compilation units. If a branch also touches OAC core code (daemon routes, CLI commands, the browser module), you must additionally runnpm run buildfrom the repo root and restart the OAC daemon (metabot daemon restart, or thedaemon startstep in the plugin's bootstrap will restart it). The daemon runs independently fromdsh web; restartingdsh webdoes not restart the daemon, and vice versa.
Bot Browser
The plugin registers a right-Sidebar page tab kind (bot-browser) in the
DSH web GUI: the local OAC Browser (/browser/* localUiUrl) rendered in an
iframe inside the official right Sidebar — native tab chip, drag resize, and
dock/float chrome included, with no layout push against the conversation
column. The right Sidebar's guide page (the + tab) gains a Bot Browser
capsule that opens an empty tab.
Entry points:
- Settings → Bots header gains a Bot Browser button (closes Settings as the Browser home opens).
- Each Bot card gains a Bot Page button that closes Settings as that Bot's
page (
metaid://<globalMetaId>) opens in the tab. - In A2A Chat and Group Tasks, clicking any sender avatar (or the thread-header participant avatars), or any Agent Internet URI in a transcript, opens the page in this right-Sidebar tab — the A2A surface is a center-column overlay, so the Sidebar it lives in stays mounted (there is no in-panel browser dock; one reveal path serves every surface).
Native tab semantics: switching to another right-Sidebar tab unmounts the
body, so the iframe reloads on return and ABC reconstructs its page state
from the daemon. A reveal carrying the URL the iframe already shows never
resets the src (ABC inside already navigated — see Agent linkage), so
same-page reveals keep the live page. Two bodies of the kind alive at once
(a split pane showing bot-browser twice) is a tolerated edge, not a
supported mode: host commands and iframe messages route to the most recently
attached one.
Agent linkage is two layers:
- CLI skills (
/metabot-browser,/metabot-metaapp) still work. The host half keeps a persistent SSE subscription to the daemon's/api/browser/events, sometabot browser tab open --uriopens this tab. When the iframe is already loaded, the plugin does not reload it: ABC inside the iframe already received the daemon event. - Native Cordis tools registered on the host global tool layer (so they are
in the model's function list from the first turn, including after a blank
session recomposes from
standardtooac-*):bot_browser_tabs,bot_browser_open_uri,bot_browser_preview_local,bot_browser_read_page,search_metaapps,bot_browser_fork_current_app,bot_browser_publish_app, andbot_browser_update_app(new version of an app the Bot already owns — ownership is verified against the owner list first; the metaapp:// URI stays stable). Tab control uses ABCpostMessagefrom the DSH parent. Search/fork/publish/update wrap the OAC CLI. Publish and update first dry-run the project without--confirm, then ask DSHctx.approval(the native confirmation dialog, with entry file, package size, and fork provenance) before the--confirmwrite. Live page context injection staysoac-*only. - Publish/update trust rails (smoke-test R2 fixes): the MAN owner index
lags chain writes by a minute or two, so
metaapp listcannot see an app published seconds ago — publish/update record every pin this host writes in a per-Bot ledger, and update_app accepts that ledger as ownership proof, refusing only on index-confirmed foreign apps (with an explicit "index may lag" hint when a miss is inconclusive). Publish/update andskill_tool publish_skillalways echo aCost:line (sats from the chain write, or an explicitCost: unavailable). The preview-then-publish gate for approval-disabled sessions keys the preview record by the acting Bot (not the agent object, which the host may replace), andbot_browser_open_uriwaits briefly for the navigation commit — reporting(navigation in progress)instead of an empty(untitled) — (no uri)tab row.
Each oac-* turn also injects a live <browser_context> block at the
user-message tail (active tab URI/title, open tabs, MetaApp source_dir when
known). If no bot-browser tab is live, the block says so — the model must
not guess from earlier CLI opens.
Theme following. The Browser follows the DSH theme: the tab body appends
the DSH-resolved theme to the iframe URL (?theme=dark|light), the daemon
bakes it into the served ABC page (no light flash in dark mode), and DSH
theme flips are pushed into the loaded iframe as ABC
agent-browser:set-theme postMessages without reloading it. Pages opened
outside DSH keep ABC's light default. On pin/metaapp/map/bare-pin deep links
the theme never rides the page search — ABC defines that search as part of
the resource URI there, so withBrowserThemeParam reroutes those URLs to the
/browser?uri=<resource>&theme=… form instead (a deep link that already
carries its own search keeps it untouched and takes the theme via the
load-time postMessage).
When the plugin is not mounted (Codex, Claude Code, OpenClaw, ...), no Browser
page is open, pagesReached stays 0, and the skill behaves exactly as before.
Layout
- Host: Cordis
nameoac-dsh,injectwebServer,webRuntime,agentPresets,llm,approval,tools,systemPrompt - Client:
dsh.clientbundle, no secondcordis.patch.ymlrow; injectslots,locale,remote,remote.agentPresets,remote.session,layout,sidebarRight,sidebarRightTabs - Client surfaces: the Settings sections (
oac-bots,oac-memory,oac-user,oac-apps,oac-traffic), the A2A Chatshell.overlaypanel (idoac-a2a, center column only; itssidebar.panellistrow is hidden), and the right-Sidebarbot-browserpage tab kind (type + keyedsidebar.right.pane.tabbody +.titlechip, plus a guide-page capsule) - Capability core remains the OAC CLI. This package does not wrap every
metabotverb as a Cordis tool. lib/is gitignored — build artifacts are never committed. After every merge tomain, runnpm run build(see the parallel-branch loop above).