Commit Graph

8 Commits

Author SHA1 Message Date
1c737bb21c feat(kb): per-agent hf-token via HostAPI.GetAgentSecret
Phase 4c. Each KB tool call now picks the right KBClient based on the
calling agent: per-agent hf-token (read via HostAPI.GetAgentSecret)
takes priority, plugin-level Config.APIKey is the fallback for cases
where the agent has no per-agent token set.

Behaviour:
  - Agent has hf-token in secrets.enc → use that, KB calls run with
    the agent's own HF role
  - Agent has no token but plugin-level apiKey is set → fall back
  - Neither configured → clear "HF KB backend unavailable" error
    surfaces to the model (no silent 401 chain)

  internal/tools/kb.go:
    - KBDeps.AgentClient(ctx, agentID) callback resolves per-agent
      client; nil result means "no per-agent token, try fallback"
    - clientForCall picks the right client per tool invocation
    - List* tools resolve agentID from sdkplugin.AgentIDFromContext
    - Cache tool already had agentID from the dispatch shim

  cmd/.../main.go: wires AgentClient closure — host.GetAgentSecret
    for "hf-token", kbclient.New(BackendURL, token) if non-empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 00:32:41 +01:00
9e24c52ae5 feat(kbblock): fade-out per §9 #3 — seed + RenderFaded + Tick + Refresh
Phase 4b. Wires fade-out for HarborForge kb-block entries using the
new Plexum-sdk-go/fade primitives + RenderDynamicSubblockRequest's
CurrentTurn field.

  internal/kbblock/kbblock.go:
    - Entry gains Seed int64 (json:"seed") for per-entry RNG seed
    - Add() generates a fresh Seed at insert time
    - renderLocked() refactored — Render() unchanged behaviour;
      RenderFaded(currentTurn, params) added — applies sdkfade.Fade
      per entry given (currentTurn - LastRefreshAtTurn) elapsed
    - Tick(currentTurn, params) drops entries whose underscore ratio
      crossed the m% threshold, returns dropped IDs
    - Refresh(ids, currentTurn) resets fade state (regenerates Seed
      + bumps LastRefreshAtTurn) — exposed for a future
      dynamic-kb-refresh tool (not in this commit)

  cmd/plexum-harborforge-plugin/main.go:
    - RenderDynamicSubblock signature now sdkplugin.
      RenderDynamicSubblockRequest (per SDK d6fdb9f)
    - Each turn: Tick → drop crossed-threshold entries (logs IDs
      dropped) → Save (only when something dropped) → RenderFaded
      returned. Uses sdkfade.DefaultFadeParams() (5/10/70 per §9 #3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 00:26:04 +01:00
e801b719a1 chore(kb): downgrade unknown-subblock log to warn; remove per-turn info log
The per-turn 'dynamic-block render: kb-block' INFO line was added for
2e sim verification; with confirmation that the host RPC fires every
turn for every agent, the line becomes noise. Trimmed to keep the
plugin's logs focused on actionable events. The 'unknown name' branch
is now a warn (defensive — shouldn't fire if manifest matches).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
2026-06-05 23:23:07 +01:00
9e43021ba5 feat(kb): dynamic-kb-* tool family + <kb-block> subblock provider
Implements the HF side of Plexum DESIGN-DYNAMIC-BLOCK.md Phase 2 — the
HarborForge knowledge-base block agents cache facts into per session.
Cross-runtime aligned with the ClawSkills workflow text (same tool
names + input schemas + return shapes will land on openclaw side later).

manifest.json:
  - 5 new dynamic-kb-* tool contracts (list-kbs / list-topics /
    list-facts / cache / evict)
  - dynamicSubblocks contract entry declaring this plugin owns
    the "kb-block" <dynamic-block> subblock

internal/kbblock/ (new):
  - Per-session storage at <PLEXUM_PROFILE_ROOT>/agents/<id>/sessions/
    <sid>/plugins/harbor-forge/kb-block.json
  - Entry carries ID (HF backend DB primary key) + KBCode + SourceTopic
    + Content + InsertSeq for §9 #4 cache-insertion-order rendering
  - Render emits <kb-fact id=N kb=<code> source=topic:<slug>>...
    </kb-fact> (no title/description per §9 #8; source attr omitted
    when empty)
  - Fade NOT applied in v1 — §9 #3 lock has fade params shared with
    memory but implementation deferred until prod data informs whether
    KB needs it; agent dynamic-kb-evict is the only eviction path
  - 11 unit tests

internal/kbclient/ (new):
  - Typed HTTP client for HarborForge.Backend KB routes verified
    against app/api/routers/knowledge.py
  - GET /knowledge-bases[?project=<code>] (list KBs)
  - GET /knowledge-bases/{kb_code}/topics (list topics)
  - GET /knowledge-bases/{kb_code}/tree (full hierarchy — ListFacts
    flattens this client-side filtered by topic ids; backend has no
    flat list-facts-in-topic route)
  - GET /knowledge-facts/{id} per fact (GetFacts batch loop)
  - Auth: plugin-level Bearer APIKey. Per-agent hf-token resolution
    is a TODO when SDK exposes secret-mgr access.

internal/tools/kb.go (new) + tools.go:
  - 5 tool functions hooked into Dispatch
  - KBDeps struct bundles Client + ProfileRoot + SessionFor + Turn
  - Cache/evict use SessionFor lookup populated by main.go's
    RenderDynamicSubblock (called per turn by host; carries sessionID)

cmd/plexum-harborforge-plugin/main.go:
  - kbClient field initialized when BackendURL + APIKey present
  - profileRoot cached for kbblock path resolution
  - agentSession sync.Map tracks agentID → sessionID; populated by
    RenderDynamicSubblock so subsequent tool calls in the same turn
    can resolve the per-session kb-block.json path
  - Implements sdkplugin.DynamicBlockProvider.RenderDynamicSubblock:
    opens kbblock for (agentID, sessionID) and returns its Render()
    body; host wraps in <kb-block>...</kb-block>

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 20:15:34 +01:00
6e3ad669f8 feat(monitor): active push loop replacing standalone monitor
Adds a periodic POST loop to <backend>/monitor/server/heartbeat so
HF plugin can take over the standalone harborforge-monitor daemon's
job — same X-API-Key header, same flat telemetry shape (cpu_pct /
mem_pct / disk_pct / swap_pct / load_avg / uptime_seconds /
plugin_version / agents[]). HF backend stays unchanged.

Config: monitor_push_enabled (default false; opt-in to avoid surprise
heartbeats from existing deployments), monitor_push_interval_seconds
(default 30), reuses apiKey for the X-API-Key header. Lift the
container's HF_MONITER_API_KEY into config.apiKey, flip
monitor_push_enabled true, then docker rm -f the container — DB
last_seen_at keeps advancing under the plugin's loop.

Collector grew swap + cpu sampling (two reads of /proc/stat over a
1-second window when SampleCPU=true). Bridge endpoint stays cheap
(SampleCPU=false on demand); push loop is the only caller paying the
sampling cost.

E2E in sim: monitor_push_enabled=true + apiKey from injected
MonitoredServer row → server_states.last_seen_at advances exactly
every interval_seconds (10s configured, 10s observed). cpu/mem/disk/
swap_pct all populate correctly.
2026-06-03 13:04:51 +01:00
472cecd771 feat: read agent_id from ctx (SDK now plumbs it)
Plexum-sdk-go now propagates the caller agent id via
`_meta.agent_id` on tools/call. AgentIDFromCtx prefers
plugin.AgentIDFromContext(ctx); falls back to the
single-active-calendar-slot heuristic for host-driven dispatch
paths (channel manager, CLI plugin-call) that lack ctx.

Drops bestEffortAgentID — the inline closure does the same thing
without the dead-Slot-iterate noise.
2026-06-03 12:54:57 +01:00
78b1ec5181 fix: align calendar API with actual HarborForge.Backend contract
Initial drop guessed the heartbeat shape; sim e2e against a running
harborforge-backend revealed the real contract is per-agent with
header auth, not server-wide with bearer:

  POST /calendar/agent/heartbeat
    headers: X-Agent-ID, X-Claw-Identifier
    body:    {claw_identifier, agent_id}
    response: {slots: [Slot], agent_status, message?}

  PATCH /calendar/slots/{id}/agent-update
  PATCH /calendar/slots/virtual/{vid}/agent-update
    body: {status, started_at?, actual_duration?}

  POST /calendar/agent/status
    body: {claw_identifier, agent_id, status}

Refactors:

  - internal/calendar/types.go now mirrors OpenclawPlugin/calendar/
    types.ts 1:1 (SlotStatus camelCase, real vs virtual slot id
    discrimination, event_data shape)
  - internal/calendar/bridge.go: header-based auth, per-agent method
    signatures, separate UpdateRealSlot vs UpdateVirtualSlot
  - internal/calendar/scheduler.go: per-agent heartbeat loop
    (one HTTP call per agent per tick), highest-priority slot
    selection, agent-update PATCH for terminal/non-terminal states
  - SingleActiveAgentID helper for main.bestEffortAgentID

Also fix two bugs found in sim:

  - bgCtx capture: AgentLister closures were capturing Init's ctx
    which dies the moment MCP initialize returns; switched to
    bgCtx (lifetime = plugin process)
  - tools.toolRestartStatus referenced a non-existent
    sch.RestartPending — HF backend has no restart endpoint per
    /openapi.json, so the tool now reports last_heartbeats freshness

Scheduler logs each tick + each heartbeat outcome at info so
operators can see backend connectivity without enabling debug.

E2E against http://harborforge-backend:8000 in sim:
  daemon → heartbeat → 404 "Agent not found"
  (= correct endpoint, correct headers, correct body — agent just
   isn't registered yet, which is expected for an untenanted
   plugin)
2026-06-03 11:28:05 +01:00
754e5183f7 initial: HarborForge plugin for Plexum (port of OpenclawPlugin)
Plugin id `harbor-forge` mirrors the OpenClaw counterpart's runtime
surface on top of the Plexum SDK:

  * eager activation — Monitor bridge + Calendar scheduler boot at
    host start, before any agent turn fires
  * monitor bridge: HTTP 127.0.0.1:<monitor_port> serving /telemetry
    + /health for HarborForge.Monitor
  * calendar scheduler: heartbeats <backendUrl>/calendar/agent/
    heartbeat, dispatches returned slots via HostAPI.WakeAgent
    (state-aware queue, depth-1 replace-newest), tracks active slot
    state in-memory, terminal status pushed back to backend
  * 9 harborforge_* tools (status / telemetry / monitor_telemetry /
    calendar_{status,complete,abort,pause,resume} / restart_status)

Key differences from OpenClaw equivalent:
  * api.spawn → HostAPI.WakeAgent (new SDK primitive)
  * api.getAgentStatus → HostAPI.ReadAgentState (existing)
  * --install-monitor / --install-cli not included; Monitor + hf CLI
    deploy via the HangmanLab.Server.T3 docker compose layer

Initial drop. TODO before v1 ship:
  * tool ctx → calling-agent-id: SDK doesn't currently expose; v1
    falls back to a single-active-slot heuristic in
    main.bestEffortAgentID
  * tests for the bridge + scheduler
2026-06-03 11:11:36 +01:00