@prismer/runtime 1.9.0 → 1.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +363 -229
  3. package/dist/cli.cjs +10269 -0
  4. package/dist/cli.cjs.map +1 -0
  5. package/dist/cli.js +10257 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.cjs +10603 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.cts +1643 -0
  10. package/dist/index.d.ts +1496 -1916
  11. package/dist/index.js +9702 -9177
  12. package/dist/index.js.map +1 -1
  13. package/package.json +31 -33
  14. package/dist/adapter-registry-EMZFKFYK.mjs +0 -8
  15. package/dist/adapter-registry-EMZFKFYK.mjs.map +0 -1
  16. package/dist/artifacts-uploader-N2HNBSTW.mjs +0 -159
  17. package/dist/artifacts-uploader-N2HNBSTW.mjs.map +0 -1
  18. package/dist/auto-register-M4YSPJS6.mjs +0 -204
  19. package/dist/auto-register-M4YSPJS6.mjs.map +0 -1
  20. package/dist/bin/prismer.js +0 -14980
  21. package/dist/chunk-BJTO5JO5.mjs +0 -11
  22. package/dist/chunk-BJTO5JO5.mjs.map +0 -1
  23. package/dist/chunk-JIEDFDVI.mjs +0 -148
  24. package/dist/chunk-JIEDFDVI.mjs.map +0 -1
  25. package/dist/chunk-NDNX2G6O.mjs +0 -68
  26. package/dist/chunk-NDNX2G6O.mjs.map +0 -1
  27. package/dist/chunk-POWV475F.mjs +0 -45
  28. package/dist/chunk-POWV475F.mjs.map +0 -1
  29. package/dist/chunk-VTFKZAUY.mjs +0 -68
  30. package/dist/chunk-VTFKZAUY.mjs.map +0 -1
  31. package/dist/dispatch-mux-MW4HHICS.mjs +0 -8
  32. package/dist/dispatch-mux-MW4HHICS.mjs.map +0 -1
  33. package/dist/dispatch-rpc-J7H6KC2V.mjs +0 -78
  34. package/dist/dispatch-rpc-J7H6KC2V.mjs.map +0 -1
  35. package/dist/fs-rpc-OSHJZYK6.mjs +0 -58
  36. package/dist/fs-rpc-OSHJZYK6.mjs.map +0 -1
  37. package/dist/heartbeat-loop-H2LAI3V5.mjs +0 -95
  38. package/dist/heartbeat-loop-H2LAI3V5.mjs.map +0 -1
  39. package/dist/index.d.mts +0 -2063
  40. package/dist/index.mjs +0 -8807
  41. package/dist/index.mjs.map +0 -1
  42. package/dist/mode-b-DXJ7FJAL.mjs +0 -152
  43. package/dist/mode-b-DXJ7FJAL.mjs.map +0 -1
  44. package/dist/registry-ZYU2HDFL.mjs +0 -12
  45. package/dist/registry-ZYU2HDFL.mjs.map +0 -1
  46. /package/{dist → assets}/icon +0 -0
  47. /package/{dist → assets}/smallicon +0 -0
package/README.md CHANGED
@@ -1,288 +1,422 @@
1
1
  # @prismer/runtime
2
2
 
3
- Prismer Cloud v1.9.0 daemon runtime and `prismer` CLI.
3
+ > Prismer Cloud daemon runtime TS-only adapter host for hosted IM agents.
4
+ > Same binary on macOS (LaunchAgent) and inside container pods (PID 1 of the daemon-first sandbox image).
4
5
 
5
- This package hosts the local Prismer daemon, the HTTP control plane on port 3210, the multi-path transport to Cloud Relay (WebSocket), the agent supervisor and PARA adapter registry, and the `prismer` command-line interface. It is one of the three core v1.9.0 tracks — see [`docs/version190/02-architecture.md`](https://github.com/Prismer-AI/PrismerCloud/blob/main/docs/version190/02-architecture.md) for the full design.
6
+ The runtime opens **one WebSocket** to cloud, declares all hosted agents in a single `agent.host.declare` payload, dispatches per-task work to in-process adapters (Hermes / Claude Code / OpenClaw / Codex), and exposes a tiny loopback HTTP API for `prismer status` / sandbox-controller RPC. Cloud is the source of truth; `~/.prismer/local.db` is a read-only mirror.
7
+
8
+ - **Hard constraints:** TS-only — no Python / Go / Rust subprocesses, no PyPI packages, no `spawn('python', …)`. Hermes is a pure HTTP client of `hermes gateway`. Single npm package — adapters live in `src/adapters/<name>/`. Local SQLite (`better-sqlite3`) mirrors cloud for offline-first.
6
9
 
7
10
  ## Install
8
11
 
9
- ### Bootstrap installer (recommended)
12
+ ### macOS (LaunchAgent autostart)
10
13
 
11
- ```
12
- curl -fsSL https://prismer.cloud/install.sh | sh
14
+ ```sh
15
+ curl -fsSL https://prismer.cloud/install.sh | sh -s -- \
16
+ --component=daemon --adapters=claude-code,codex,hermes
13
17
  ```
14
18
 
15
- Auto-detects Node via `fnm`, installs `@prismer/sdk` and `@prismer/runtime` globally, launches the daemon, and runs first-time setup. macOS and Linux, no sudo, no Homebrew required.
19
+ What the daemon-mode installer does:
16
20
 
17
- ### Manual (npm)
21
+ 1. Detects OS / arch (Darwin or Linux x64/arm64; Windows requires WSL2).
22
+ 2. Installs Node.js via `fnm` under `$HOME/.local/share/fnm` if absent (no Homebrew, no sudo).
23
+ 3. Strict-pins `@prismer/runtime@1.9.3` (no caret) and installs globally via npm.
24
+ 4. Scaffolds `~/.prismer/{adapters,workspace,logs,bin}`.
25
+ 5. Writes `~/Library/LaunchAgents/com.prismer.daemon.plist` (label `com.prismer.daemon`, `KeepAlive=true`, `RunAtLoad=true`, `ProgramArguments` includes `--foreground`), then `bootout` + `bootstrap` to load it.
26
+ 6. Auto-runs `prismer adapter install <name>` for each adapter passed via `--adapters`.
18
27
 
19
- ```
20
- npm install -g @prismer/runtime @prismer/sdk
21
- prismer setup
22
- ```
28
+ After install, run `prismer pair` (or `prismer setup sk-prismer-...`) to write `~/.prismer/config.toml`. The LaunchAgent's polling loop snaps to attention as soon as the file appears.
23
29
 
24
- Requires Node.js 20+. `@prismer/sdk` is required the runtime mounts the SDK's `register / config / token / send / load / search / parse / recall / discover` commands via `@prismer/sdk/cli`.
30
+ Installer flags: `-y/--yes`, `--no-setup`, `-v/--verbose`, `--local <dir>` (install from local tgz pre-publish), `--component=sdk|daemon` (or positional `daemon` shorthand), `--adapters=<csv>`, `--skip-launchagent` (CI), `--uninstall` (rm `~/.prismer`, `~/.local/share/fnm`, LaunchAgent).
25
31
 
26
- ## Quick start
32
+ ### npm (manual)
27
33
 
28
- ```
29
- # 1. Provide an API key (persists to ~/.prismer/config.toml)
30
- prismer setup sk-prismer-live-...
31
-
32
- # 2. Start the daemon (HTTP on 127.0.0.1:3210 + Cloud Relay WS)
34
+ ```sh
35
+ npm install -g @prismer/runtime@1.9.3
36
+ prismer pair --device-name "$(hostname)"
33
37
  prismer daemon start
34
-
35
- # 3. Check health
36
- prismer status
37
-
38
- # 4. Wire an agent
39
- prismer agent install claude-code --install-agent # also installs the upstream CLI if missing
40
- prismer agent list
41
-
42
- # 5. Pair a mobile device
43
- prismer pair show
44
38
  ```
45
39
 
46
- `PRISMER_API_KEY=sk-prismer-... prismer setup` works equivalently for Docker and CI no browser OAuth round-trip is required when the key is supplied out-of-band.
47
-
48
- ## CLI surface
40
+ Without LaunchAgent, you own daemon lifecycle (run under tmux / launchd / systemd / docker).
49
41
 
50
- ### Lifecycle
51
-
52
- | Command | Purpose |
53
- |---|---|
54
- | `prismer setup [api-key]` | First-run setup: write API key, start daemon, scan for agents, print next steps. `--postinstall` for npm lifecycle hooks, `--no-daemon` to skip startup, `--skip-agent-scan` to skip detection. |
55
- | `prismer status` | Daemon + agent + system health overview. `--json` for machine-readable. |
56
- | `prismer daemon start` | Start detached background daemon on port 3210. `--foreground` for Docker/systemd. `--port <n>` to override. |
57
- | `prismer daemon stop` | Graceful SIGTERM with 10s timeout, then SIGKILL. |
58
- | `prismer daemon restart` | Stop then start. Preserves port. |
59
- | `prismer daemon status` | PID file + port binding state. |
60
- | `prismer daemon logs [--tail N] [--follow]` | Tail `~/.prismer/logs/daemon.log`. |
61
- | `prismer daemon reprobe` | Force a transport reprobe pass without restarting. |
42
+ ### Container (daemon-first sandbox image)
62
43
 
63
- The daemon writes its PID to `~/.prismer/daemon.pid` atomically with `O_CREAT | O_EXCL`. A crash-loop guard in `daemon.starts.json` blocks rapid restart storms (3 failures in 10 minutes 5-minute cooldown).
44
+ The runtime is PID 1 of every Prismer sandbox pod. `infra/sandbox-image/daemon-entrypoint.sh` writes a flat `~/.prismer/config.toml` from controller-injected env, then `exec prismer daemon start --port=7878 --foreground`. Required env:
64
45
 
65
- ### Agent control
46
+ - `PRISMER_API_KEY` — required.
47
+ - `PRISMER_DAEMON_ID` — required (else derived from hostname → `container:<podName>`).
48
+ - `PRISMER_BASE_URL` (or `CLOUD_API_BASE`, `PRISMER_CLOUD_API_BASE`) — cloud URL; falls back to `https://prismer.cloud`.
49
+ - `PRISMER_HOSTED_AGENT_FILE` / `PRISMER_HOSTED_AGENT_JSON` — static-binding payload (one-time agent install before first `host.declare`).
50
+ - `PRISMER_STATIC_BINDING_REQUIRED=true` — make missing static binding fatal.
51
+ - `PRISMER_USER_ID`, `PRISMER_TASK_ID`, `PRISMER_CONTAINER_ID` — audit-only.
66
52
 
67
- | Command | Purpose |
68
- |---|---|
69
- | `prismer agent list` | All installed agents with tier + hook status. |
70
- | `prismer agent install <name>` | Install an adapter pack. Two-tier fetch: signed release manifest (GitHub Release) → npm registry. Ed25519 signature verified before hook wiring unless `--skip-verify`. `--install-agent` also installs the upstream CLI (claude-code, codex, …) if missing. `--source cdn\|mirror\|npm`, `--force`, `--non-interactive`, `--accept-defaults`. |
71
- | `prismer agent doctor <name>` | Diagnose binary path, hook config, daemon registration, PARA event emission for a given agent. |
72
- | `prismer agent remove <name>` | Rollback hook config to the pre-install backup and remove the sandbox profile. Keychain entries are preserved. `--yes` to skip the confirmation prompt. |
73
- | `prismer agent update <name>` | Shorthand for `install --force`. |
74
- | `prismer agent publish <name>` | Publish the locally-installed agent so it appears from mobile. Writes to `~/.prismer/published-agents.toml` and calls cloud. |
75
- | `prismer agent unpublish <name>` | Reverse of publish. Cloud deletes within 90s even if the DELETE call fails (background sweep). |
53
+ The container daemon binds `:7878` (controller proxy target) instead of the desktop default `:3210`, and starts `OutboxWatcher` to upload files dropped under `/workspace/_outbox/` as sandbox-output assets.
76
54
 
77
- Shipped adapters: `claude-code`, `codex`, `openclaw`, `hermes`. Each ships via npm with an Ed25519-signed manifest attached to the matching GitHub Release. See [`docs/version190/15-cli-design.md`](https://github.com/Prismer-AI/PrismerCloud/blob/main/docs/version190/15-cli-design.md) §15.6 for pack schema and signing flow.
55
+ ## CLI reference (16 verbs)
78
56
 
79
- ### Pack registry
57
+ `VERSION = '1.9.3'`. Each verb is a `commander` subcommand under `src/cli/commands/`. Output is JSON (`--json`) or human-friendly ANSI by default.
80
58
 
81
- ```
82
- prismer pack list
83
- prismer pack search <query>
84
- prismer pack verify <name> <signature>
85
- ```
86
-
87
- Reads the signed pack index from the current GitHub Release — useful to inspect available adapters without installing.
88
-
89
- ### Device pairing
90
-
91
- ```
92
- prismer pair show [--ttl 300] # generate QR code (5 min TTL, single-use)
93
- prismer pair list # list paired devices
94
- prismer pair revoke <deviceId> # remove from local registry
95
- ```
59
+ ### Lifecycle
96
60
 
97
- Pairing offers use 5-minute TTL tokens exchanged via Cloud Relay. The CLI polls the daemon for pair completion and writes the resulting device record to `~/.prismer/data/paired-devices.json`.
61
+ | Verb | Purpose |
62
+ | ---- | ------- |
63
+ | `banner [--compact]` | Print the Prismer Cloud banner. `--compact` collapses to one line. |
64
+ | `setup [api-key] [--token <jwt>] [--pair] [--cloud <url>] [--device-name <n>] [--force] [--start] [--no-start] [--as-user <email>] [--json]` | First-run onboarding. Three paths: direct API key, mint a daemon-scoped key from a user JWT (`POST /api/keys`, label `Daemon: <hostname> <YYYY-MM-DD>`), or delegate to QR/local-only pair. `--force` overwrites config and archives `local.db` → `local.db.<iso>.bak` if `api_key` / `cloud` / `daemon_id` changed. Auto-prints next-step tips (`daemon start`, `adapter list`, `agent register`, `profile create`, `task create`). |
65
+ | `pair [--cloud <url>] [--device-name <n>] [--force] [--as-user <email>]` | QR/JWT pair flow. `--as-user` requires `LOCAL_ONLY=1` (LAN dev bypass). |
66
+ | `daemon start [--port <n>] [--no-local-server] [--foreground] [--json]` | Daemonize itself by spawning `daemon run` and waiting up to 5s for the child to write a live PID. **`--foreground`** is required by the LaunchAgent plist (KeepAlive=true would respawn-loop on graceful exit). |
67
+ | `daemon run [--port <n>] [--no-local-server]` | Internal foreground worker. Polls every 5s for `~/.prismer/config.toml` to appear before booting `Runner` — so LaunchAgent KeepAlive can launch pre-pair without burning CPU. |
68
+ | `daemon stop [--timeout <ms>]` | SIGTERM the PID in `daemon.pid`; default timeout 5000ms. Clears stale PID files. |
69
+ | `daemon restart [--foreground]` | Chained `stop` then `start`. |
70
+ | `daemon status` | `{running, pid, paths.config}`; if alive, merges loopback `/healthz` (`pid`, `wsConnected`, `hostedAgents[]`). |
71
+ | `daemon logs --tail <n> [--follow]` | Read `~/.prismer/logs/daemon.log`; tail-N + `tail -f`-style follower. |
72
+ | `status [--json]` | Composite report: reads `config.toml`, hits `127.0.0.1:3210/healthz`, calls `GET /api/im/me`, counts `local.db` rows (`agents`, `agent_profiles WHERE deleted_at IS NULL`, `running_tasks`). Pretty banner by default. |
73
+
74
+ ### Hosted agents
75
+
76
+ | Verb | Purpose |
77
+ | ---- | ------- |
78
+ | `adapter list` | Built-in adapters + per-adapter `health()` + recorded version from `[adapters.<name>]`. |
79
+ | `adapter install <name> [--package-version <v>] [--with-hooks] [--dry-run]` | Run the underlying package manager (`npm i -g`, `pipx install`), probe binary version, record `{package_manager, package_name, binary, version, installed_at}` into `config.toml`. `--with-hooks` writes a hook marker. |
80
+ | `adapter remove <name>` | Run uninstall (best-effort) and clear `[adapters.<name>]`. Alias: `uninstall`. |
81
+ | `adapter info <name>` | Print install spec + recorded version. |
82
+ | `adapter doctor <name>` | 5-check matrix: `downstream_binary`, `adapter_health`, `recorded_config`, `auth_env` (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `~/.hermes/.env`, `~/.openclaw/openclaw.json`), `hook_config`. Returns ordered fixes. |
83
+ | `adapter hooks <name> [--check] [--dry-run]` | Install / verify a `prismer-daemon-runtime` marker into the downstream tool's hook file (`~/.claude/hooks.json`, `~/.codex/hooks.json`, `~/.hermes/hooks.json`, `~/.openclaw/hooks/prismer.json`). JSON files are deep-merged; existing files are backed up to `<file>.bak.<iso>`. |
84
+ | `agent list [--local]` | Merge local mirror with cloud `GET /api/im/agents`. Emits `{localCount, cloudCount, agents[].{imUserId, name, adapter, local, cloud}}`. |
85
+ | `agent register --adapter <n> --display-name <n> [--username <n>] [--im-user-id <id>] [--capabilities <csv>] [--agent-type <t>] [--workspace-id <id>]` | `POST /api/im/register {type:'agent',username,displayName,agentType,capabilities,metadata.adapter}`. Auto-derives a slug if `--username` omitted. Mirrors into `local.db.agents`. |
86
+ | `agent rename <imUserId> <newName>` | `PATCH /api/im/agents/:id` + local update. |
87
+ | `agent remove <imUserId> [--cloud]` | Local-only by default; `--cloud` also `DELETE`s the cloud row. |
88
+ | `agent doctor [target]` | 6-check matrix: `binary`, `adapterHealth`, `localHosting` (DB row), `cloudVisibility`, `profileExistence` (local + cloud), `daemonLocalServerHealth` (`127.0.0.1:3210/healthz` + `/agents`). |
89
+ | `profile templates` | List built-in role templates (`product-manager`, `engineer`, `ceo`, `researcher`); each carries `applicableAdapters: ['hermes','openclaw','claude-code']` and a `configSchema` (`model`, `systemPrompt`, `allowedTools`, `maxTokens`). |
90
+ | `profile list --agent <imUserId>` | `GET /api/im/agent_profiles?agentId=…`. |
91
+ | `profile create --agent <id> --name <n> [--adapter <n>] [--config <jsonOr@file>] [--from-template <n>] [--workspace-id <id>]` | Template config merges with inline JSON. Default workspace via `/api/im/workspaces` if omitted. |
92
+ | `profile edit <profileId>` | Open current `config` in `$EDITOR` (default `vi`); PATCH on save. |
93
+ | `profile remove <profileId>` | Soft-delete (DELETE). |
94
+
95
+ ### Tasks, memory, assets, events
96
+
97
+ | Verb | Purpose |
98
+ | ---- | ------- |
99
+ | `task create --agent <imUserId> --prompt <text> [--profile <id>] [--capability <n>] [--title <t>] [--timeout-ms <ms>] [--no-wait]` | `POST /api/im/tasks` then poll `GET /api/im/tasks/:id` until terminal. Tolerant of `{task:{...}}` and flat envelopes; reads `output` from `task.output` or `task.result.output`. |
100
+ | `task list [--limit <n>]` | Default limit 20. |
101
+ | `task get <taskId>` | Single task fetch. |
102
+ | `task cancel <taskId>` | `PATCH /api/im/tasks/:id {status:'cancelled'}`. |
103
+ | `memory stats` | Daemon Memory Gateway first (`/memory/stats`, `/api/memory/stats` on `PRISMER_DAEMON_URL` or `127.0.0.1:3210`); falls back to read-only `~/.prismer/local.db` snapshot of `cached_assets` + `workspace_files_mirror`. |
104
+ | `memory list [--limit <n>]` | Same fallback chain. |
105
+ | `memory search [query] [--query <text>] [--limit <n>]` | Local-cache substring filter when gateway unavailable. |
106
+ | `memory delete <id>` | Gateway-only. Exits 1 if unavailable. |
107
+ | `memory sync` | Gateway-only. Exits 1 if unavailable. |
108
+ | `asset list [--workspace-id <id>] [--task-id <id>]` | `GET /api/im/assets`. |
109
+ | `asset upload <file> --workspace-id <id> [--kind <k>] [--task-id <id>] [--agent-id <id>] [--container-id <id>] [--metadata <json>] [--mime <type>]` | Multipart `POST /api/im/assets`. |
110
+ | `asset download <assetId> --out <path>` | Fetches bytes from `/api/im/assets/:id` and writes to disk. |
111
+ | `asset get <assetId>` | `GET /api/im/assets/:id/detail`; pretty-prints kind/mime/size/storageUri/url/s3Url/expiresIn/photoRefs count. |
112
+ | `asset by-hash <sha256> [--workspace-id <id>]` | Content-hash dedupe lookup (`?wsId=…`). |
113
+ | `events [--limit <n>] [--agent-id <id>] [--session-id <id>] [--family <n>] [--type <n>]` | Read `~/.prismer/para/events.jsonl`. Default limit 50, hard max 10 000. Returns reverse-chronological array. Exits 1 with `events_unavailable` when the file does not exist. |
114
+ | `events:stats [--limit <n>] [--agent-id …]` | Aggregate `byFamily / byType / byAgentId / bySessionId` over the same filtered set. |
115
+
116
+ ### Interaction
117
+
118
+ | Verb | Purpose |
119
+ | ---- | ------- |
120
+ | `chat me` | `GET /api/im/me`. |
121
+ | `chat direct <targetUserId> --message <text>` | `POST /api/im/direct/:id/messages {type:'text',content}`. |
122
+ | `chat messages <conversationId> [--limit <n>] [--before <id>]` | Pagination via `?limit=&before=`. |
123
+ | `chat group create --name <n> --members <csv>` | `POST /api/im/groups`. Members may be IM user IDs, usernames, or cloud user IDs. |
124
+ | `chat group send <groupId> --message <text>` | `POST /api/im/groups/:id/messages`. |
125
+ | `chat group messages <groupId> [--limit <n>] [--before <id>]` | History fetch. |
126
+ | `chat group remove-member <groupId> <memberId>` | `DELETE /api/im/groups/:id/members/:memberId`. |
127
+
128
+ All `chat` subcommands sanitize `sk-prismer-*` from error messages and tolerate both `{ok,data}` and `{success,data}` envelopes.
129
+
130
+ ### Cookbook (54release MVP regression)
131
+
132
+ | Verb | Purpose |
133
+ | ---- | ------- |
134
+ | `cookbook run --suite <csv> [--workspace-id …] [--agent-id …] [--group-id …] [--sandbox-id …] [--prompt …] [--timeout-ms <ms>] [--strict] [--json]` | Run one or more smoke suites: `status` (`/api/health` + `/api/im/me`), `im` (`/workspaces`, `/me/agents`, `/groups`), `task` (list + optional create+poll if `--agent-id` and `--prompt` provided), `group` (list + optional history), `asset` (list), `sandbox` (list + status). `--strict` treats skips as failure. Exits non-zero on fail. |
135
+
136
+ ### Sandbox
137
+
138
+ | Verb | Purpose |
139
+ | ---- | ------- |
140
+ | `sandbox list --workspace-id <id> [--status <s>] [--limit <n>]` | `GET /api/sandboxes?workspaceId=…`. |
141
+ | `sandbox create --workspace-id <id> [--agent-id …] [--task-id …] [--image …] [--cpu-request …] [--cpu-limit …] [--memory-request …] [--memory-limit …]` | `POST /api/sandboxes`. |
142
+ | `sandbox status <id>` | Detail + live status. |
143
+ | `sandbox start <id>` / `stop <id>` | Lifecycle endpoints. |
144
+ | `sandbox snapshot <id>` | `POST /api/sandboxes/:id/snapshot`. |
145
+ | `sandbox runCmd <id> -- <command...> [--timeout-ms <ms>]` | `POST /api/sandboxes/:id/runCmd {command, timeoutMs}`. |
146
+ | `sandbox logs <id>` | Streams `/logs` to stdout (`ReadableStream` decode loop). |
147
+
148
+ ### Workspace
149
+
150
+ | Verb | Purpose |
151
+ | ---- | ------- |
152
+ | `workspace list` / `get <id>` | `GET /api/im/workspaces[/:id]`. |
153
+ | `workspace create --name <n> [--description <text>]` | Slug derived from name; `POST /api/im/workspaces {name, slug, metadata.description}`. |
154
+ | `workspace runtime <workspaceId> [--events] [--json]` | `GET /api/im/workspaces/:id/runtime` (devices + agents + heartbeats). `--events` opens an SSE stream at `…/runtime/events`, decoding `snapshot` / `agent.heartbeat` events into pretty heartbeats or `--json` lines. |
155
+ | `workspace files <workspaceId>` | `GET /api/im/workspaces/:id/files`. |
156
+
157
+ ## Adapter taxonomy
158
+
159
+ The contract lives in `src/adapters/contract.ts`. Two `AdapterKind` values:
160
+
161
+ - **`'long-running'`** — adapter exposes `ensureService(profile)` returning an `AdapterService` (HTTP client) that the daemon's `ServicePool` reuses across tasks per `AgentProfile.id`. Re-creates on `healthy() === false` or `'crash'` event.
162
+ - **`'interactive'`** — adapter exposes `dispatch(profile, task)` and spawns one subprocess per task.
163
+
164
+ All four official adapters ship in-process via `src/adapters/registry.ts` (`Map<name, AdapterDef>` with `findByCapability(capability)` supporting wildcard tags like `code.*`).
165
+
166
+ | Adapter | Kind | Capabilities | Wraps |
167
+ | ------- | ---- | ------------ | ----- |
168
+ | `hermes` | long-running | `shell, code, mcp, long-context` | `hermes gateway` HTTP API on `127.0.0.1:<port>` |
169
+ | `claude-code` | interactive | `shell, code, mcp, edit` | `claude --print --model … <prompt>` (Claude CLI 2.x) |
170
+ | `codex` | interactive | `code, shell, openai` | `codex exec --model … --cd … --sandbox … --ephemeral --json <prompt>` |
171
+ | `openclaw` | long-running | `chat, code, multi-channel` | `openclaw gateway` (`POST /v1/chat/completions`, OpenAI-compatible) |
172
+
173
+ ### `hermes`
174
+
175
+ Pure HTTP client of `hermes gateway` (Hermes 0.10+). All calls are HTTP — no `spawn('python', …)`, no PyPI dependency. `HermesProfileConfigSchema` (zod):
176
+
177
+ - `port` (1..65535, default `8642`), `apiKey` (required, from `~/.hermes/.env API_SERVER_KEY`).
178
+ - `hermesProfileName` (default = first 8 chars of `profile.id`), `autoStart` (default `false`), `startupTimeoutMs` (default 30 000).
179
+ - `configurePrismerProvider` (default `true`) — writes `custom_providers` + `model.provider='custom:prismer'` + `mcp_servers['prismer-tasks']` block into `~/.hermes/profiles/<name>/config.yaml`. The MCP server path is auto-resolved via `createRequire('@prismer/mcp-server')` first, then a repo-relative dev fallback.
180
+ - `installPrismerMcpServer` (default `true`), `prismerMcpServerPath` (override).
181
+ - `model` (default `us-kimi-k2.5`), `prismerProviderName` (default `prismer`), `prismerProviderBaseUrl` (default `$PRISMER_BASE_URL/api/v1`), `prismerApiKeyEnv` (default `PRISMER_API_KEY`, regex `^[A-Za-z_][A-Za-z0-9_]*$`).
182
+ - `mirrorNativeKanban` (default `true`) — mirrors Prismer `work_item` projections into Hermes Kanban as triage cards via `hermes -p <profile> kanban create … --triage --idempotency-key prismer:<parentTaskId>`.
183
+ - `mirrorNativeGoals` (default `true`) — writes Prismer standing objectives into Hermes' SQLite `state_meta["goal:<sessionId>"]` directly.
184
+ - `nativeMirrorTimeoutMs` (default 2000).
185
+
186
+ Dispatch flow:
187
+
188
+ 1. (autostart only) spawn `hermes -p <profile> gateway run` with `API_SERVER_ENABLED=true`, `API_SERVER_KEY`, `API_SERVER_PORT`, `API_SERVER_HOST=127.0.0.1`.
189
+ 2. Pre-bridge native Kanban + native Goals (best-effort, `nativeMirrorTimeoutMs` cap).
190
+ 3. `POST /v1/runs {input, instructions, session_id, stream:true}` → `{run_id}`.
191
+ 4. Stream `GET /v1/runs/:id/events` (SSE). Events: `message.delta` (concat to deltas), `run.completed` (final output), `run.failed` (throw), `tool.started` / `tool.completed` (forward to `task.onProgress` with `{kind:'tool', event, tool, summary, preview, duration, error, arguments, result}`), `reasoning.available` (forward as `{kind:'reasoning'}` without bumping progress).
192
+ 5. On abort, `POST /v1/runs/:id/stop`.
193
+ 6. Result includes `metadata.hermes.{status, lastSyncedAt, kanban, goals, runId, baseUrl, model}` for cloud-side bridge persistence (PATCH-merged onto `IMTask.metadata.bridge.hermes`).
98
194
 
99
- ### Domain commands (via SDK)
195
+ ### `claude-code`
100
196
 
101
- Mounted at runtime via `@prismer/sdk/cli`:
197
+ `claude --print --model <m> [--system-prompt …] [--allowed-tools …] <prompt>` per dispatch. `--cwd` removed (uses `child_process.spawn` cwd); prompt is positional; `--headless` was renamed to `--print` in claude CLI 2.x. Config: `cwd` (required), `model` (default `'sonnet'`), `systemPrompt`, `envVars`, `mcpServers`, `allowedTools`, `maxTurns` (default 20), `baseURL`, `apiKeyRef` (`env:NAME` or `keychain:NAME` — keychain only on darwin via `security find-generic-password`), `route` (`'default'|'prismer'|'omniroute'`). `stdio: ['ignore', 'pipe', 'pipe']` to close child stdin (claude ≥ 2.1.128 prints "no stdin data received in 3s, proceeding without it" otherwise). `task.signal` → `child.kill('SIGTERM')`; `task.timeoutMs` → SIGTERM via `setTimeout`.
102
198
 
103
- ```
104
- prismer register <username> # create a Prismer identity
105
- prismer config show|set
106
- prismer token refresh
199
+ ### `codex`
107
200
 
108
- prismer send / load / search # IM + context shortcuts
109
- prismer parse / parse-status / parse-result
110
- prismer recall / discover # memory + agent discovery
111
- ```
201
+ `codex exec --model <m> --cd <cwd> --sandbox <level> --ephemeral --json <prompt>`. Config: `cwd` (required), `model` (default `codex-mini-latest`), `sandbox` (`'read-only'|'workspace-write'|'danger-full-access'`, default `workspace-write`), `systemPrompt` (prepended to user prompt — Codex CLI has no `--system-prompt` flag as of 2026-05), `envVars`, `apiKeyEnv` (default `OPENAI_API_KEY`). `parseCodexOutput` reads JSONL stdout backwards looking for `{"type":"message"|"assistant"|"output", "content"|"message"|"text": string}`; falls back to raw stdout. Outputs are capped at 64 KiB with `\n…[truncated]` suffix.
112
202
 
113
- ### Domain commands (native)
203
+ ### `openclaw`
114
204
 
115
- ```
116
- prismer task ... # Task Router (v1.9.0 cross-agent task routing)
117
- prismer memory ... # Memory Gateway (hexagonal cloud/local ports)
118
- prismer memory key-backup / key-recover / key-fingerprint # Shamir recovery
119
- prismer evolution ... # Evolution Gateway
120
- prismer tier:set <agent-id> <tier> # 1..7
121
- prismer tier:get <agent-id>
122
- prismer session export <sessionId> # copy ~/.prismer/trace/<id>.jsonl.zst
123
- prismer events [--agent-id --session-id --family --type]
124
- prismer events:stats
125
- prismer permissions:test --tier N # inspect permission evaluation
126
- ```
205
+ HTTP-only client of `openclaw gateway`. `POST /v1/chat/completions` (OpenAI-compatible, non-streaming). Config: `port` (default `18789`), `apiKey` (bearer token from `gateway.auth.bearerTokens`), `model` (default `'openclaw'` — OpenClaw uses this to pick which agent answers, NOT the underlying LLM; primary LLM is set in `openclaw.json`). Includes a defensive strip of OpenClaw 2026.4.x's `Cannot read properties of undefined (reading '...')` stderr-bleed prefix that contaminates assistant content.
127
206
 
128
- ### Migration
207
+ ## Daemon architecture
129
208
 
130
209
  ```
131
- prismer migrate # v1.8 → v1.9 upgrade (api key → keychain, hooks → daemon)
132
- prismer migrate luminclaw-memory # import luminclaw local memory to Memory Gateway
133
- prismer migrate-secrets # sweep config.toml plaintext → keychain
210
+ ┌──────────────────────────────────────────────────────────┐
211
+ │ Cloud (REST + WS multiplexed across hosted agents) │
212
+ └─────────────┬──────────────────────────┬─────────────────┘
213
+ │ WS ?token=<sk-prismer> │ REST CloudClient
214
+ │ │
215
+ ┌─────▼─────┐ ┌──────────┐ ┌───▼─────────────────┐
216
+ │ WsClient ├──▶ dispatch ├──▶ adapter.dispatch / │
217
+ │ (1 conn) │ │ router │ │ ServicePool.ensure │
218
+ └─────┬─────┘ └─────┬────┘ └─────────┬───────────┘
219
+ │ │ │
220
+ host.acked runtimeRoute long-running
221
+ host.declare ='shell' → adapters reused
222
+ task.dispatch shell-executor per AgentProfile.id
223
+
224
+ ┌──────▼─────────────────────────────────────┐
225
+ │ Runner (main loop, ~960 LOC) │
226
+ │ - LocalDb (better-sqlite3 ~/.prismer/local.db) │
227
+ │ - AssetCache (LRU, 5 GiB default) │
228
+ │ - UriResolver (prismer:// → file://) │
229
+ │ - SyncQueue + SyncWorker │
230
+ │ - LocalServer (127.0.0.1:3210 / :7878) │
231
+ │ - OutboxWatcher (container only) │
232
+ │ - 30s heartbeat re-declare │
233
+ │ - 60s stuck-task reaper │
234
+ └────────────────────────────────────────────┘
134
235
  ```
135
236
 
136
- `migrate-secrets --dry-run` previews without touching files or the keychain.
137
-
138
- ## Global flags
139
-
140
- Applied before Commander parses the command tree all subcommands honour them:
141
-
142
- | Flag | Effect |
143
- |---|---|
144
- | `--json` | Machine-readable single-line JSON output. Mutually exclusive with `--follow` on log streaming. |
145
- | `--quiet` | Suppress non-error output. |
146
- | `--color` / `--no-color` | Force colour on / off. `NO_COLOR` env is also honoured. |
147
-
148
- All error-path JSON conforms to the Prismer API envelope: `{ success: false, error: { code, message }, ... }`.
149
-
150
- ## Configuration
151
-
152
- Config lives at `~/.prismer/config.toml`:
153
-
154
- ```toml
155
- [default]
156
- api_key = "sk-prismer-live-..."
157
- environment = "production"
158
- base_url = "https://prismer.cloud"
159
-
160
- [daemon]
161
- id = "daemon:abc123..."
162
-
163
- [user]
164
- id = "user:abc123..."
237
+ ### `Runner` boot sequence (`src/daemon/runner.ts`)
238
+
239
+ 1. Load `config.toml`; export `PRISMER_BASE_URL` + `PRISMER_API_KEY` so adapter children inherit the same provider.
240
+ 2. Open SQLite, init asset cache, URI resolver, adapter registry, `ServicePool`.
241
+ 3. **Static-binding preload** `installStaticHostedAgentFromEnv` reads `PRISMER_HOSTED_AGENT_FILE` / `PRISMER_HOSTED_AGENT_JSON` and seeds one `agents` + `agent_profiles` row before the first `agent.host.declare`. `PRISMER_STATIC_BINDING_REQUIRED=1` makes a missing payload fatal.
242
+ 4. Load remaining locally-registered agents from DB.
243
+ 5. Spin `SyncWorker` (local-write → cloud-flush queue: PATCH/POST/DELETE per resource type, 4xx-other dropped, 5xx/408/429/0 retried).
244
+ 6. Open WS; wire handlers.
245
+ 7. (Container only `PRISMER_CONTAINER_ID` set or `PRISMER_RUNTIME_MODE=container`) start `OutboxWatcher` on `/workspace/_outbox/` (uploads as sandbox-output assets, marks staged in `_uploaded/`).
246
+ 8. Bind `LocalServer` (unless `--no-local-server`).
247
+ 9. Start a 30s heartbeat that re-sends `agent.host.declare` so the cloud's 90s `sweepTimedOut()` doesn't flip status back offline.
248
+ 10. Start a 60s **stuck-task reaper**: any `runningTasks` entry past `max(timeoutMs, 5min)` gets `ctrl.abort()` + a daemon-side `task.dispatch.reply {ok:false, error.code:'daemon_task_timeout'}` sent over WS — safety valve for upstream LLM gateway half-closes.
249
+
250
+ ### Dispatch (`src/daemon/dispatch.ts`)
251
+
252
+ Pure function that runs a single task:
253
+
254
+ 1. Resolve `AgentProfile` (cloud REST — by `profileId` or, if blank, the most-recently-created profile for `agentImUserId`).
255
+ 2. Look up adapter; reply `{ok:false, error.code:'adapter_unhealthy'}` if missing.
256
+ 3. Rewrite `prismer://` URIs in `prompt` and `context[].content` via `UriResolver`, pinning resolved hashes.
257
+ 4. `composePrompt(...)` — concat context history + current message; trim oldest first when total chars exceed `contextMaxChars` (default 8000); always keep at least one entry.
258
+ 5. **Memory context** — `GET /api/im/memory/digest?maxLines=120&maxBytes=4000` → prepend `[Memory Context]` block when non-empty.
259
+ 6. **Goal context** — `GET /api/im/tasks?workspaceId=<ws>&limit=100`, filter for `metadata.kind==='goal' || intent==='standing_objective'`, dedupe by assignment, sort by `updatedAt`, take top 4 → prepend `[Active Goals]` block.
260
+ 7. Resolve `profile.config.systemPrompt` (warn loudly if non-string — without this, role-template-driven agents silently regress to defaults).
261
+ 8. Build `TaskInput { taskId, prompt, metadata: {...payload.metadata, conversationId, prismerGoals, systemPrompt?, prismerObservability}, timeoutMs, signal, onProgress }`. `onProgress` re-emits as `task.dispatch.progress` over WS.
262
+ 9. Long-running: `servicePool.ensureService(profile, adapter).dispatch(taskInput)`. Interactive: `adapter.dispatch(profile, taskInput)`.
263
+ 10. Build `task.dispatch.reply { taskId, ok, output, error, metrics }`.
264
+ 11. **Bridge metadata writeback** — if `result.metadata.hermes` present, PATCH `/api/im/tasks/:id` merging onto `metadata.bridge.hermes` (with `lastSyncedAt`).
265
+ 12. **Observability writeback** — PATCH `metadata.observability` with `{identity, memory, goals, lastSyncedAt}`.
266
+ 13. Always unpin all resolved hashes.
267
+
268
+ ### Daemon-local shell (`src/daemon/shell-executor.ts`)
269
+
270
+ `runtimeRoute='shell'` (or `metadata.execution.kind === 'shell'`) routes the dispatch to `executeShellDispatch` instead of an adapter. `ShellExecutionConfig` (resolved from `[shell]` block in `config.toml`):
271
+
272
+ - `enabled` (default `false`; `PRISMER_SHELL_ENABLED=true` env override).
273
+ - `defaultCwd` (default `process.cwd()`).
274
+ - `maxTimeoutMs` (default 60s, hard max 30 min).
275
+ - `maxOutputBytes` (default 256 KiB, hard max 5 MiB).
276
+ - `allowedWorkspaces?` (allow-list).
277
+ - `shell` (`bash|zsh|sh`, default `bash`).
278
+
279
+ Spawns `<shell> -lc <command>` with `payload.metadata.execution.cwd|env|shell` overrides; streams stdout/stderr → `task.dispatch.progress` events with `detail.{stream, chunk, sequence, truncated}`. Output (capped) is rendered as `$ <cmd>\ncwd: …\nshell: …\nexitCode: N\n[stdout]\n…\n[stderr]\n…`. Structured error codes: `shell_disabled`, `shell_workspace_not_allowed`, `shell_command_required`, `shell_cwd_missing`, `shell_spawn_failed`, `shell_timeout`, `task_cancelled`, `shell_exit_nonzero`. SIGTERM → 2s grace → SIGKILL on timeout/abort.
280
+
281
+ ### `WsClient` (`src/daemon/ws-client.ts`)
282
+
283
+ WS to cloud, `?token=<apiKey>`. Exponential backoff 1s → 60s, capped at 30 attempts before 5min cooldown ("degraded"). Special close codes: `1000` normal (no reconnect), `4001` AUTH (no reconnect, emit `auth-failed`). Events: `open|message|close|error|drop|auth-failed|degraded|reconnect-scheduled`.
284
+
285
+ ## WS protocol (cloud ↔ daemon)
286
+
287
+ Mirror types in `src/types/im-events.ts` (canonical chain: `src/im/types/im-events.ts` → SDK → runtime).
288
+
289
+ ### Daemon → cloud
290
+
291
+ | Event | Payload |
292
+ | ----- | ------- |
293
+ | `agent.host.declare` | `{daemonId, daemonVersion, platform: 'darwin'|'linux'|'win32', agents: [{imUserId, name, adapterName, capabilities, profiles:[{id,version}]}]}`. Sent on `'authenticated'` ack and every 30s. Cloud stamps `daemonId` into `IMAgentCard.metadata` so the workspace runtime view groups daemon-declared agents under the right device; cloud also writes a Redis presence record at `runtime:device:<wsId>:<daemonId>` (TTL 90s). |
294
+ | `task.dispatch.reply` | `{taskId, ok, output?, error?, metrics?}`; `requestId` echoes the original request envelope. |
295
+ | `task.dispatch.progress` | `{taskId, progress: 0..1, message?, detail?}` — incremental. |
296
+ | `agent.status.changed` | `{agentImUserId, status, activeProfileId?, runningTaskIds?}` — emitted on agent state shifts. |
297
+
298
+ ### Cloud → daemon
299
+
300
+ | Event | Payload |
301
+ | ----- | ------- |
302
+ | `host.acked` | `{workspaceId, syncCursor: {workspaces, agent_profiles}, profilesToSync: string[]}` — first reply post-declare; cloud also fires `redispatchPending(userId)` once per WS connection so any tasks accumulated while offline come back through `task.dispatch.request`. |
303
+ | `task.dispatch.request` | `{taskId, agentImUserId?, targetDaemonId?, profileId, capability, prompt, runtimeRoute?, metadata?, timeoutMs?, context?: TaskDispatchContextEntry[], conversationId?}`. `runtimeRoute` accepts `'agent' | 'sandbox' | 'shell'`. Per-`taskId` dedupe in `Runner.runningTasks` is what makes redispatch resilient to cloud's heartbeat-redeclare loop. |
304
+ | `task.cancel` | `{taskId, reason?}` → `runningTasks.get(taskId)?.ctrl.abort()`. |
305
+ | `agent.changed` | `{agentImUserId, fields: {displayName?, capabilities?}}` → patch in-memory. |
306
+ | `agent_profile.changed` | `{profileId, version}` → re-fetch via REST + redeclare. |
307
+ | `workspace.changed` | `{workspaceId, updatedAt}` → re-fetch. |
308
+ | `workspace_file.changed` | `{workspaceId, path, operation: 'create'|'update'|'delete', assetId?, contentHash?, version}` → upsert/delete `workspace_files_mirror`. |
309
+
310
+ The server-side `'authenticated'` ack from cloud triggers the first `agent.host.declare` (declaring on `open` is racy because cloud auth is an async DB lookup).
311
+
312
+ ## Hermes long-running setup
313
+
314
+ Operators run `hermes` in their own Python venv. Sample `~/.hermes/profiles/<name>/config.yaml` (auto-written by the adapter when `configurePrismerProvider: true`):
315
+
316
+ ```yaml
317
+ custom_providers:
318
+ prismer:
319
+ type: openai
320
+ base_url: https://prismer.cloud/api/v1
321
+ api_key_env: PRISMER_API_KEY
322
+ model:
323
+ provider: custom:prismer
324
+ name: us-kimi-k2.5
325
+ mcp_servers:
326
+ prismer-tasks:
327
+ command: node
328
+ args:
329
+ - /usr/local/lib/node_modules/@prismer/mcp-server/dist/index.js
165
330
  ```
166
331
 
167
- Resolution order for any value: **explicit flag > environment variable > config.toml > keychain placeholder > default**. `sk-prismer-...` API keys may also live in the keychain under service `prismer-config/default.api_key` and be referenced from the TOML via `$KEYRING:prismer-config/default.api_key`.
168
-
169
- Relevant environment variables:
170
-
171
- | Var | Default | Purpose |
172
- |---|---|---|
173
- | `PRISMER_API_KEY` | — | Seeds `prismer setup` and daemon identity. |
174
- | `PRISMER_BASE_URL` | `https://prismer.cloud` | Cloud API base for all daemon + CLI calls. |
175
- | `PRISMER_API_BASE` | `https://prismer.cloud/api/im` | IM / PARA endpoints. |
176
- | `PRISMER_MASTER_PASSPHRASE` | — | Enables the encrypted-file keychain backend when no system keychain is available. |
177
- | `PRISMER_SKIP_POSTINSTALL` | — | Set to `1` to suppress `npm install` postinstall setup. |
178
- | `PRISMER_FORCE_POSTINSTALL` | — | Set to `1` to run postinstall even on non-global installs. |
179
- | `NO_COLOR` | — | Disable ANSI colour. |
180
-
181
- ### Keychain backends
182
-
183
- The runtime probes backends in order:
184
-
185
- 1. **macOS Keychain** (`security` binary). Values written via `-X <hex>` so plaintext never appears in `ps aux`.
186
- 2. **libsecret** via `secret-tool` (Linux GNOME/KDE).
187
- 3. **pass** (`pass` store, optional GPG-backed).
188
- 4. **Encrypted-file fallback** — XChaCha20-Poly1305 with a key derived from `PRISMER_MASTER_PASSPHRASE`. Only enabled when the env var is set.
189
-
190
- ## Architecture
332
+ Then start the gateway with the API server platform enabled:
191
333
 
334
+ ```sh
335
+ API_SERVER_ENABLED=true \
336
+ API_SERVER_KEY=<apiKey> \
337
+ API_SERVER_PORT=8642 \
338
+ API_SERVER_HOST=127.0.0.1 \
339
+ hermes -p <profile> gateway run
192
340
  ```
193
- prismer CLI (bin/prismer.ts)
194
-
195
- HTTP 127.0.0.1:3210
196
-
197
- ┌────────────────▼─────────────────┐
198
- │ Daemon (daemon/runner.ts) │
199
- │ ├─ HTTP server (daemon-http.ts) │
200
- │ ├─ Multi-path transport │
201
- │ │ ├─ WS to Cloud Relay │
202
- │ │ └─ LAN probe │
203
- │ ├─ Agent supervisor │
204
- │ ├─ Events tailer (PARA → cloud) │
205
- │ ├─ Outbox (SQLite WAL) │
206
- │ └─ Memory Gateway (SQLite FTS5) │
207
- └────────────────┬─────────────────┘
208
-
209
- Installed agents (hooks)
210
- claude-code, codex, openclaw, hermes
211
- ```
212
-
213
- ### Data directory layout (`~/.prismer/`)
214
-
215
- | Path | Contents |
216
- |---|---|
217
- | `config.toml` | User config (see above). |
218
- | `daemon.pid` | Atomic PID file. Owned by the running daemon. |
219
- | `daemon.port` | Bound port (default 3210). |
220
- | `daemon.starts.json` | Start-attempt log for crash-loop detection. |
221
- | `logs/daemon.log` | Plain-text log output; tailed by `prismer daemon logs`. |
222
- | `outbox.sqlite` | Ordered event buffer (WAL mode) for Cloud Relay. |
223
- | `memory.sqlite` | Local Memory Gateway store with FTS5 indices. |
224
- | `trace/<sessionId>.jsonl.zst` | Per-session PARA traces emitted by L8 adapters. |
225
- | `para/events.jsonl` | Live PARA event stream tailed by `events-tailer`. |
226
- | `sandbox/<agent>.sb` | macOS seatbelt sandbox profiles for installed agents. |
227
- | `data/paired-devices.json` | Local device pairing registry. |
228
- | `published-agents.toml` | Mirror of cloud-published agents. |
229
341
 
230
- ## Security
342
+ A plain `hermes gateway` without these env vars starts only configured messaging platforms and will never bind the `/v1/runs` HTTP API. Set `autoStart: true` on the AgentProfile to let the daemon spawn this command on first dispatch.
231
343
 
232
- - **Ed25519 signed packs** Adapter manifests are attached to the matching GitHub Release and carry detached Ed25519 signatures. `install-agent.ts` verifies before wiring hooks; npm-source fallback is treated as lower trust and signalled to the user. `--skip-verify` is allowed for offline / dev.
233
- - **API key hygiene** — Keys are passed to child processes via named FDs or keychain reads, not via argv. macOS `security` uses `-X` hex encoding. Config writes should be `chmod 0o600` (see Known Issues).
234
- - **Sandbox profiles** — macOS seatbelt / Linux AppArmor / bwrap. Generated from adapter manifests during install, removed on uninstall.
235
- - **Device pairing** — Offer tokens are short-lived (5 min), single-use, exchanged over TLS to Cloud Relay. Revocation currently only prunes the local registry (see Known Issues).
344
+ The adapter mirrors Prismer projections into Hermes' native surfaces:
236
345
 
237
- ## Known issues
346
+ - **Kanban** — `hermes -p <profile> kanban create … --triage --idempotency-key prismer:<parentTaskId>`. Triage avoids duplicate execution; Prismer's `agent_run` remains the executable source of truth.
347
+ - **Goals** — direct write into Hermes' SQLite `state_meta["goal:<sessionId>"]` (Hermes has no public goals REST/CLI surface).
238
348
 
239
- The CLI path is under active hardening. Tracked items:
349
+ Bridge results are persisted on `IMTask.metadata.bridge.hermes` via the daemon's PATCH-merge writeback step.
240
350
 
241
- - `install-agent.ts` catches Ed25519 signature failure in the CDN branch and falls through to the npm tier — a tampered pack can be silently downgraded. **Fixing imminently.**
242
- - `prismer agent remove` does not prompt for confirmation; `--yes` is declared but unread.
243
- - `prismer migrate` runs destructive steps on TTYs with no confirmation.
244
- - `prismer pair revoke` is local-only; the server-side binding is not invalidated.
245
- - `getAuthToken()` in `migrate-luminclaw-memory.ts` returns empty — the import flow requires authentication to be wired.
246
- - Version strings in `install-agent.ts` success/already-installed paths are hardcoded to `1.9.0` instead of being read from the pack manifest.
247
- - libsecret backend collapses "not found" and transient failures into `null` — the macOS backend distinguishes these.
248
- - `daemon start` poll timeout of 2s can cause false-positive "failed to start" entries on slow machines and trigger the crash-loop guard.
351
+ ## LocalServer HTTP API (loopback only)
249
352
 
250
- Fixes land in v1.9.1. Open an issue or PR at [github.com/Prismer-AI/PrismerCloud](https://github.com/Prismer-AI/PrismerCloud).
353
+ `src/daemon/local-server.ts` binds `127.0.0.1:<port>`. Desktop default `:3210`, container `:7878`. CORS allows `*` / `GET,POST,OPTIONS` / `Content-Type,Authorization`.
251
354
 
252
- ## Development
355
+ | Method | Path | Purpose |
356
+ | ------ | ---- | ------- |
357
+ | OPTIONS | `*` | CORS preflight |
358
+ | GET | `/healthz` | `{status:'ok', daemonId, cloudBaseUrl, workspaceId, pid, startedAt, wsConnected, hostedAgents, observability}` |
359
+ | GET | `/agents` | `{agents: [{imUserId, name, adapterName}]}` |
360
+ | GET | `/tasks/running` | `{taskIds: string[]}` |
361
+ | POST | `/v1/runs` | Sandbox controller daemon-dispatch ack — `202 {runId, status:'accepted', taskId}`. Optional `payload.shellCommand` triggers async `bash -c <cmd>` with `cwd=/workspace` (escape hatch). |
362
+ | POST | `/v1/agents/install` | **Cloud → daemon agent install RPC**: validates body via `validateInstallAgentPayload`, calls `runner.installHostedAgent` → upsert `agents` + `agent_profiles` rows, reload, send `agent.host.declare`, return `{ok, daemonId, installedAgent, hostedAgents}`. |
363
+ | POST | `/v1/snapshot` | FS-manifest snapshot — walks `snapshotRoot` (default `/workspace`), computes per-file `{path, sha256, sizeBytes, mtime}`. Skips dotfiles + `_outbox/_uploaded`. 404 if root missing. |
253
364
 
254
- ```
255
- # Build
256
- npm run build # tsup bundle + copy icon/smallicon
365
+ The sandbox-controller's `installAgent` proxy at `POST /:id/installAgent` validates body via `InstallAgentSchema` (zod), resolves pod IP via `orchestrator.getContainerIp(id)`, and forwards to `http://<podIP>:7878/v1/agents/install`. The earlier `daemonDispatch` proxy at `POST /:id/daemonDispatch` forwards `{taskId, adapter?, prompt?, env?}` to `:7878/v1/runs`.
257
366
 
258
- # Type check only
259
- npm run typecheck
367
+ ## Configuration
260
368
 
261
- # Run the vitest suite
262
- npm test
369
+ | Env var | Default | Purpose |
370
+ | ------- | ------- | ------- |
371
+ | `PRISMER_HOME` | `~/.prismer` | Daemon home (config, db, logs, adapter installs) |
372
+ | `PRISMER_BASE_URL` | from `config.toml` `cloud_api_base` | Cloud URL (LAN dev override) |
373
+ | `PRISMER_API_KEY` | from `config.toml` `api_key` | Cloud API key (also exported to adapter children) |
374
+ | `PRISMER_HOSTED_AGENT_FILE` | (empty) | Static-binding payload path (k8s/container) |
375
+ | `PRISMER_HOSTED_AGENT_JSON` | (empty) | Inline JSON variant |
376
+ | `PRISMER_STATIC_BINDING_REQUIRED` | `false` | Treat missing static binding as fatal |
377
+ | `PRISMER_CONTAINER_ID` | (empty) | Activates `OutboxWatcher` |
378
+ | `PRISMER_RUNTIME_MODE` | (empty) | `container` activates `OutboxWatcher` (alt to `PRISMER_CONTAINER_ID`) |
379
+ | `PRISMER_DAEMON_URL` | `http://127.0.0.1:3210` | Loopback URL used by `prismer memory` and `agent doctor` |
380
+ | `PRISMER_SHELL_ENABLED` | `false` | Override `[shell].enabled` in `config.toml` |
381
+ | `LOCAL_ONLY` | (empty) | `=1` enables `--as-user` pair bypass |
382
+ | `PRISMER_AUTH_TOKEN` | (empty) | Fallback for `prismer setup --token <jwt>` |
383
+ | `EDITOR` | `vi` | Used by `prismer profile edit` |
384
+
385
+ `config.toml` schema (TOML, written by `setup` / `pair`):
263
386
 
264
- # Clean dist
265
- npm run clean
387
+ ```toml
388
+ api_key = "sk-prismer-..."
389
+ cloud_api_base = "https://cloud.prismer.dev"
390
+ daemon_id = "..."
391
+
392
+ [adapters.claude-code]
393
+ package_manager = "npm"
394
+ package_name = "@anthropic-ai/claude-code"
395
+ binary = "/usr/local/bin/claude"
396
+ version = "2.1.128"
397
+ installed_at = "2026-05-07T00:00:00.000Z"
398
+
399
+ [shell]
400
+ enabled = false
401
+ maxTimeoutMs = 60000
402
+ maxOutputBytes = 262144
403
+ shell = "bash"
266
404
  ```
267
405
 
268
- `@prismer/*` dependencies resolve from the npm registry. Fresh clones of the closed-source repo cannot `npm install` until upstream packages publish — use the monorepo build scripts instead:
269
-
270
- ```
271
- sdk/build/pack.sh --scope all --clean # tarball all SDKs
272
- sdk/build/verify.sh --scope all # version parity + compile
273
- public/install.sh --local-artifacts # install from local tarballs
274
- ```
406
+ ## Troubleshooting
275
407
 
276
- Release is driven from the source repo via `sdk/build/release.sh --scope prismer-cloud`, which syncs into the open-source mirror before tagging and publish. Hotfixes use `sdk/build/hotfix.sh @prismer/runtime 1.9.0.N` and do **not** bump the root `/VERSION`.
408
+ - **LaunchAgent respawn loop after install** — the plist must include `--foreground` in `ProgramArguments`. v1.9.3 installs do this automatically; if you're upgrading from 1.8.x re-run `curl -fsSL https://prismer.cloud/install.sh | sh -s -- --component=daemon` (the installer does `bootout` + `bootstrap` to reload).
409
+ - **Daemon shows "waiting for ~/.prismer/config.toml"** — that's expected behavior pre-pair. Run `prismer pair` (or `prismer setup sk-prismer-...`) and the daemon snaps to attention within 5s.
410
+ - **Cloud unreachable** — `prismer status` will print the failing base URL. Common causes: stale `cloud_api_base` after a tenant move (run `prismer setup --force <new-key> --cloud <url>`), or the API key was rotated (mint a new one with `prismer setup --token <jwt>`).
411
+ - **Agent shows under `__unbound__` device in workspace runtime view** — the daemon's `agent.host.declare` is not stamping `daemonId`. Confirm you're on `@prismer/runtime@1.9.3` (the `daemonId` payload field landed in 1.9.3).
412
+ - **`claude --print` runs report "no stdin data received in 3s"** — fixed in 1.9.3 (`stdio: ['ignore', 'pipe', 'pipe']`). Older runtimes will surface the warning but the dispatch still completes.
413
+ - **Tasks dispatch but `@`-mention messages never reach the agent** — cloud-side `SHADOW_JOIN_FAILED` rollback. The runtime's `agent.host.declare` requires every declared agent to auto-join all its conversation rooms; check daemon logs for the rollback line and re-register the agent (`prismer agent register …`) to fix the cloud-side row.
414
+ - **Stuck task that never replies** — the 60s reaper aborts and replies `{ok:false, error.code:'daemon_task_timeout'}` after `max(timeoutMs, 5min)`. Cloud will mark the task `failed` accordingly. Inspect via `prismer task get <id>`.
277
415
 
278
- ## Links
416
+ ## Versioning
279
417
 
280
- - Design: [`docs/version190/02-architecture.md`](https://github.com/Prismer-AI/PrismerCloud/blob/main/docs/version190/02-architecture.md), [`docs/version190/15-cli-design.md`](https://github.com/Prismer-AI/PrismerCloud/blob/main/docs/version190/15-cli-design.md)
281
- - Cloud dashboard: https://prismer.cloud
282
- - Issues: https://github.com/Prismer-AI/PrismerCloud/issues
283
- - SDK: [`@prismer/sdk`](https://www.npmjs.com/package/@prismer/sdk)
284
- - Plugins: [`@prismer/claude-code-plugin`](https://www.npmjs.com/package/@prismer/claude-code-plugin), [`@prismer/openclaw-channel`](https://www.npmjs.com/package/@prismer/openclaw-channel)
418
+ > See [CHANGELOG.md](./CHANGELOG.md). Current: **v1.9.3** (2026-05-07).
285
419
 
286
420
  ## License
287
421
 
288
- MIT
422
+ MIT.