Clone
1
Architecture
Hermes Agent edited this page 2026-10-21 10:56:58 +00:00
Architecture
Module layout
dsh-vk-group/
├── package.json # dsh.bundle manifest, engines.dsh, peerDependencies
├── cordis.patch.yml # the profile layer this bundle contributes
├── src/
│ ├── index.ts # apply(ctx, config) — the only entry point
│ ├── config.ts # Config interface + Schemastery schema
│ ├── vk/
│ │ ├── client.ts # token bucket, retry, error mapping, execute-batching
│ │ ├── methods.ts # typed wrappers, one per VK method used
│ │ ├── errors.ts # VkError hierarchy + code → typed error
│ │ └── longpoll.ts # session loop, ts recovery, batch assembly
│ ├── tools/
│ │ ├── index.ts # registerAll(ctx, deps) — single registration point
│ │ ├── community.ts
│ │ ├── members.ts
│ │ ├── moderation.ts
│ │ ├── managers.ts
│ │ ├── wall.ts
│ │ ├── comments.ts
│ │ ├── media.ts
│ │ ├── board.ts
│ │ ├── market.ts
│ │ ├── messages.ts
│ │ ├── analytics.ts
│ │ ├── infrastructure.ts
│ │ └── raw.ts
│ ├── events/
│ │ ├── inbound.ts # VK update → agent.followup()
│ │ └── mapping.ts # update-type table
│ ├── policy/
│ │ ├── gate.ts # tools/pre-execute waterfall listener
│ │ ├── guard.ts # ctx.tools.guard() monotonic denial
│ │ └── rules.ts # rule engine
│ ├── audit/
│ │ └── log.ts # append-only audit record on ctx.storage
│ ├── prompt/
│ │ └── section.ts # ctx.systemPrompt.section()
│ ├── skill/
│ │ └── vk-group-ops/SKILL.md
│ └── ui/
│ ├── settings.tsx # settings panel contribution
│ └── cards.tsx # ConversationNodeDefinition for post/moderation cards
└── tests/
DSH extension points used, and why
| Point | Purpose in this plugin |
|---|---|
ctx.tools.register(defineTool(...)) |
every tool; registration is an effect, so HMR and unload clean up automatically |
ctx.jobs.start({kind, label, owner, run}) |
the LongPoll loop and long batch moderation runs |
ctx.effect(fn) |
timers, the HTTP dispatcher, the LongPoll abort controller |
tools/pre-execute |
returns ask for destructive calls, deny when the token lacks the required scope |
ctx.tools.guard() |
monotonic final denial — cannot be undone by a listener registered later |
tools/execute |
wraps dispatch to collect latency/error metrics without touching tool code |
tools/result |
append-only audit of every mutating call |
ctx.agents + agent.followup() |
deliver inbound VK events into the conversation as durable context |
ctx.systemPrompt.section() |
short live summary: community name, member count, current moderation mode |
ctx.storage |
rules, audit log, LongPoll ts, warn counters |
Config (Schemastery) |
all tunables; validated at load, hot-replaced on edit |
Skill (skill/) |
procedural playbooks loaded only when the agent works on the community |
Client plugin (ConversationNodeDefinition) |
rich cards for posts and the moderation queue |
Dependency direction
tools/* → vk/methods.ts → vk/client.ts → fetch
↓ ↓
policy/* vk/errors.ts
↓
audit/log.ts → ctx.storage
Rules:
- No tool imports
fetch. All HTTP lives invk/client.ts. - No tool builds a URL or a query string.
vk/methods.tsowns the method names and parameter names. - No tool reads
Configdirectly —applyresolves config once and passes a frozenVkContextdown. policy/*never calls VK. It returns decisions; the caller acts.
Lifecycle
apply(ctx, config)runs after every injected service is ready.- Config is validated by Schemastery; invalid config fails the load with an actionable error.
- The client is constructed with the resolved token bucket.
- Tools register (each returns a disposer that
ctxowns). - Policy listeners and the guard register.
- The prompt section registers.
- If
longpoll.enabled, a job starts; its abort controller is owned byctx.effect. - On unload/HMR every registration is disposed in reverse order; the LongPoll job is aborted and awaited.
Why one vk_api_raw tool exists
VK ships methods faster than a plugin can wrap them, and a community manager sometimes needs one call the plugin does not model. The escape hatch is a single tool gated by safety.rawApiEnabled (default false), restricted to an allowlist of method prefixes, always audited, and never able to reach a token-scope it does not already have. It is a deliberate hole with a lock, not an oversight.