@hybridaione/hybridclaw 0.4.0 → 0.4.3

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 (180) hide show
  1. package/AGENTS.md +86 -5
  2. package/CHANGELOG.md +49 -0
  3. package/README.md +43 -5
  4. package/config.example.json +11 -0
  5. package/container/Dockerfile +8 -2
  6. package/container/dist/approval-policy.js +200 -3
  7. package/container/dist/approval-policy.js.map +1 -1
  8. package/container/dist/browser-tools.js +2 -0
  9. package/container/dist/browser-tools.js.map +1 -1
  10. package/container/dist/index.js +86 -10
  11. package/container/dist/index.js.map +1 -1
  12. package/container/dist/model-client.js.map +1 -1
  13. package/container/dist/providers/openai-codex.js +22 -1
  14. package/container/dist/providers/openai-codex.js.map +1 -1
  15. package/container/dist/runtime-paths.js +84 -0
  16. package/container/dist/runtime-paths.js.map +1 -1
  17. package/container/dist/tools.js +141 -11
  18. package/container/dist/tools.js.map +1 -1
  19. package/container/dist/web-search.js +917 -0
  20. package/container/dist/web-search.js.map +1 -0
  21. package/container/package-lock.json +313 -2
  22. package/container/package.json +6 -1
  23. package/container/src/approval-policy.ts +248 -4
  24. package/container/src/browser-tools.ts +3 -0
  25. package/container/src/index.ts +98 -9
  26. package/container/src/model-client.ts +0 -1
  27. package/container/src/providers/openai-codex.ts +42 -1
  28. package/container/src/runtime-paths.ts +96 -0
  29. package/container/src/tools.ts +163 -11
  30. package/container/src/types.ts +20 -0
  31. package/container/src/web-search.ts +1176 -0
  32. package/dist/agent/agent.d.ts.map +1 -1
  33. package/dist/agent/agent.js +10 -2
  34. package/dist/agent/agent.js.map +1 -1
  35. package/dist/agent/executor.d.ts.map +1 -1
  36. package/dist/agent/executor.js +5 -10
  37. package/dist/agent/executor.js.map +1 -1
  38. package/dist/agent/prompt-hooks.d.ts.map +1 -1
  39. package/dist/agent/prompt-hooks.js +38 -4
  40. package/dist/agent/prompt-hooks.js.map +1 -1
  41. package/dist/agent/tool-summary.js +1 -1
  42. package/dist/agent/tool-summary.js.map +1 -1
  43. package/dist/auth/codex-auth.d.ts +1 -0
  44. package/dist/auth/codex-auth.d.ts.map +1 -1
  45. package/dist/auth/codex-auth.js +23 -8
  46. package/dist/auth/codex-auth.js.map +1 -1
  47. package/dist/auth/hybridai-auth.d.ts +15 -0
  48. package/dist/auth/hybridai-auth.d.ts.map +1 -1
  49. package/dist/auth/hybridai-auth.js +267 -2
  50. package/dist/auth/hybridai-auth.js.map +1 -1
  51. package/dist/channels/discord/attachments.d.ts.map +1 -1
  52. package/dist/channels/discord/attachments.js +67 -11
  53. package/dist/channels/discord/attachments.js.map +1 -1
  54. package/dist/channels/discord/inbound.d.ts.map +1 -1
  55. package/dist/channels/discord/inbound.js +3 -2
  56. package/dist/channels/discord/inbound.js.map +1 -1
  57. package/dist/channels/discord/runtime.d.ts.map +1 -1
  58. package/dist/channels/discord/runtime.js +134 -5
  59. package/dist/channels/discord/runtime.js.map +1 -1
  60. package/dist/cli.d.ts +2 -1
  61. package/dist/cli.d.ts.map +1 -1
  62. package/dist/cli.js +178 -29
  63. package/dist/cli.js.map +1 -1
  64. package/dist/config/cli-flags.d.ts +3 -1
  65. package/dist/config/cli-flags.d.ts.map +1 -1
  66. package/dist/config/cli-flags.js +14 -1
  67. package/dist/config/cli-flags.js.map +1 -1
  68. package/dist/config/config.d.ts +8 -0
  69. package/dist/config/config.d.ts.map +1 -1
  70. package/dist/config/config.js +21 -4
  71. package/dist/config/config.js.map +1 -1
  72. package/dist/config/runtime-config.d.ts +13 -0
  73. package/dist/config/runtime-config.d.ts.map +1 -1
  74. package/dist/config/runtime-config.js +66 -0
  75. package/dist/config/runtime-config.js.map +1 -1
  76. package/dist/gateway/gateway-service.d.ts.map +1 -1
  77. package/dist/gateway/gateway-service.js +152 -13
  78. package/dist/gateway/gateway-service.js.map +1 -1
  79. package/dist/gateway/gateway.js +20 -6
  80. package/dist/gateway/gateway.js.map +1 -1
  81. package/dist/gateway/health.d.ts.map +1 -1
  82. package/dist/gateway/health.js +8 -0
  83. package/dist/gateway/health.js.map +1 -1
  84. package/dist/gateway/proactive-delivery.d.ts +8 -0
  85. package/dist/gateway/proactive-delivery.d.ts.map +1 -0
  86. package/dist/gateway/proactive-delivery.js +14 -0
  87. package/dist/gateway/proactive-delivery.js.map +1 -0
  88. package/dist/infra/container-runner.d.ts.map +1 -1
  89. package/dist/infra/container-runner.js +71 -16
  90. package/dist/infra/container-runner.js.map +1 -1
  91. package/dist/infra/container-setup.d.ts.map +1 -1
  92. package/dist/infra/container-setup.js +24 -6
  93. package/dist/infra/container-setup.js.map +1 -1
  94. package/dist/infra/host-runner.d.ts.map +1 -1
  95. package/dist/infra/host-runner.js +61 -7
  96. package/dist/infra/host-runner.js.map +1 -1
  97. package/dist/infra/stream-debug.d.ts +9 -0
  98. package/dist/infra/stream-debug.d.ts.map +1 -0
  99. package/dist/infra/stream-debug.js +47 -0
  100. package/dist/infra/stream-debug.js.map +1 -0
  101. package/dist/logger-format.d.ts +13 -0
  102. package/dist/logger-format.d.ts.map +1 -0
  103. package/dist/logger-format.js +19 -0
  104. package/dist/logger-format.js.map +1 -0
  105. package/dist/logger.d.ts.map +1 -1
  106. package/dist/logger.js +35 -2
  107. package/dist/logger.js.map +1 -1
  108. package/dist/media/pdf-context.d.ts +8 -0
  109. package/dist/media/pdf-context.d.ts.map +1 -0
  110. package/dist/media/pdf-context.js +395 -0
  111. package/dist/media/pdf-context.js.map +1 -0
  112. package/dist/memory/compaction-archive.d.ts +8 -0
  113. package/dist/memory/compaction-archive.d.ts.map +1 -0
  114. package/dist/memory/compaction-archive.js +82 -0
  115. package/dist/memory/compaction-archive.js.map +1 -0
  116. package/dist/memory/compaction.d.ts +58 -0
  117. package/dist/memory/compaction.d.ts.map +1 -0
  118. package/dist/memory/compaction.js +494 -0
  119. package/dist/memory/compaction.js.map +1 -0
  120. package/dist/memory/db.d.ts +1 -0
  121. package/dist/memory/db.d.ts.map +1 -1
  122. package/dist/memory/db.js +25 -0
  123. package/dist/memory/db.js.map +1 -1
  124. package/dist/memory/memory-service.d.ts +5 -1
  125. package/dist/memory/memory-service.d.ts.map +1 -1
  126. package/dist/memory/memory-service.js +59 -1
  127. package/dist/memory/memory-service.js.map +1 -1
  128. package/dist/scheduler/heartbeat.d.ts.map +1 -1
  129. package/dist/scheduler/heartbeat.js +1 -0
  130. package/dist/scheduler/heartbeat.js.map +1 -1
  131. package/dist/security/mount-config.d.ts +14 -0
  132. package/dist/security/mount-config.d.ts.map +1 -0
  133. package/dist/security/mount-config.js +155 -0
  134. package/dist/security/mount-config.js.map +1 -0
  135. package/dist/security/mount-security.d.ts +1 -0
  136. package/dist/security/mount-security.d.ts.map +1 -1
  137. package/dist/security/mount-security.js +7 -4
  138. package/dist/security/mount-security.js.map +1 -1
  139. package/dist/skills/skills-install.d.ts +26 -0
  140. package/dist/skills/skills-install.d.ts.map +1 -0
  141. package/dist/skills/skills-install.js +248 -0
  142. package/dist/skills/skills-install.js.map +1 -0
  143. package/dist/skills/skills.d.ts +39 -0
  144. package/dist/skills/skills.d.ts.map +1 -1
  145. package/dist/skills/skills.js +120 -20
  146. package/dist/skills/skills.js.map +1 -1
  147. package/dist/tui.js +7 -3
  148. package/dist/tui.js.map +1 -1
  149. package/dist/types.d.ts +44 -0
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/types.js.map +1 -1
  152. package/dist/workspace.d.ts.map +1 -1
  153. package/dist/workspace.js +143 -15
  154. package/dist/workspace.js.map +1 -1
  155. package/docs/development/README.md +15 -0
  156. package/docs/development/architecture.md +44 -0
  157. package/docs/development/releasing.md +70 -0
  158. package/docs/development/runtime.md +136 -0
  159. package/docs/development/skills.md +34 -0
  160. package/docs/development/testing.md +73 -0
  161. package/docs/index.html +14 -14
  162. package/docs/tools/web-search.md +74 -0
  163. package/package.json +9 -1
  164. package/skills/pdf/SKILL.md +215 -0
  165. package/skills/pdf/forms.md +263 -0
  166. package/skills/pdf/reference.md +179 -0
  167. package/skills/pdf/scripts/_pdf_form_runtime.mjs +500 -0
  168. package/skills/pdf/scripts/_pdf_runtime.mjs +212 -0
  169. package/skills/pdf/scripts/check_bounding_boxes.mjs +96 -0
  170. package/skills/pdf/scripts/check_fillable_fields.mjs +29 -0
  171. package/skills/pdf/scripts/create_validation_image.mjs +60 -0
  172. package/skills/pdf/scripts/extract_form_field_info.mjs +62 -0
  173. package/skills/pdf/scripts/extract_form_structure.mjs +135 -0
  174. package/skills/pdf/scripts/extract_pdf_text.mjs +60 -0
  175. package/skills/pdf/scripts/fill_fillable_fields.mjs +70 -0
  176. package/skills/pdf/scripts/fill_pdf_form_with_annotations.mjs +88 -0
  177. package/skills/pdf/scripts/render_pdf_pages.mjs +68 -0
  178. package/templates/AGENTS.md +4 -3
  179. package/templates/README.md +13 -0
  180. package/templates/SOUL.md +1 -1
package/AGENTS.md CHANGED
@@ -1,5 +1,84 @@
1
1
  # AGENTS.md
2
2
 
3
+ ## Scope
4
+
5
+ This file is the canonical repo-level instruction set for coding agents working
6
+ in HybridClaw.
7
+
8
+ - Follow this file first.
9
+ - If a deeper directory contains its own `AGENTS.md`, that file overrides this
10
+ one for its subtree.
11
+ - Keep `CLAUDE.md` aligned with this file. `CLAUDE.md` should only carry
12
+ tool-specific deltas.
13
+
14
+ ## Project Map
15
+
16
+ - `src/` core CLI, gateway, providers, auth, audit, scheduler, and runtime
17
+ wiring
18
+ - `container/` sandboxed runtime, tool executor, provider adapters, and
19
+ container build inputs
20
+ - `skills/` bundled `SKILL.md` skills plus any supporting scripts or reference
21
+ material
22
+ - `templates/` runtime workspace bootstrap files seeded into agent workspaces
23
+ - `tests/` Vitest suites across unit, integration, e2e, and live coverage
24
+ - `docs/` static site assets and maintainer/development reference docs
25
+
26
+ ## Working Rules
27
+
28
+ - Keep changes focused. Prefer targeted fixes over broad refactors unless the
29
+ task requires wider movement.
30
+ - Match the existing TypeScript + ESM patterns already used in the touched area.
31
+ - Update tests and docs when behavior, commands, or repo workflows change.
32
+ - Treat existing uncommitted changes as user work unless you created them.
33
+ - Do not rename or relocate files in `templates/` without updating
34
+ `src/workspace.ts` and the workspace bootstrap tests.
35
+
36
+ ## Setup And Commands
37
+
38
+ Prerequisites:
39
+
40
+ - Node.js 22 (matches CI)
41
+ - npm
42
+ - Docker when working on container-mode behavior or image builds
43
+
44
+ Common commands:
45
+
46
+ ```bash
47
+ npm install
48
+ npm run setup
49
+ npm run build
50
+ npm run typecheck
51
+ npm run lint
52
+ npm run check
53
+ npm run test:unit
54
+ npm run test:integration
55
+ npm run test:e2e
56
+ npm run test:live
57
+ npm run release:check
58
+ npm --prefix container run lint
59
+ npm --prefix container run release:check
60
+ ```
61
+
62
+ ## Testing Expectations
63
+
64
+ - Docs-only changes: keep links and commands accurate; runtime tests are usually
65
+ unnecessary.
66
+ - `src/` changes: run `npm run typecheck`, `npm run lint`, and the relevant
67
+ Vitest suites.
68
+ - `container/` changes: run `npm --prefix container run lint`, `npm run build`,
69
+ and targeted tests that exercise the runtime boundary.
70
+ - Release or packaging changes: run both release checks and verify versioned
71
+ docs stay aligned.
72
+ - If you skip a relevant check, state that explicitly in your handoff.
73
+
74
+ ## Documentation Hierarchy
75
+
76
+ - `README.md` is the end-user and product entry point.
77
+ - `CONTRIBUTING.md` is the human contributor quickstart.
78
+ - `docs/development/` holds deeper maintainer and runtime reference docs.
79
+ - `templates/*.md` are product runtime workspace seed files, not repo
80
+ contributor onboarding docs.
81
+
3
82
  ## Bump Release
4
83
 
5
84
  When the user says "bump release":
@@ -10,10 +89,12 @@ When the user says "bump release":
10
89
  - `package-lock.json` (root `version` and `packages[""]`)
11
90
  - `container/package.json`
12
91
  - `container/package-lock.json` (root `version` and `packages[""]`)
13
- - any user-facing version text (for example `src/tui.ts` banner).
14
- 3. Move `CHANGELOG.md` release notes from `Unreleased` to the new version heading (or create one).
92
+ - any user-facing version text (for example `src/tui.ts` banner)
93
+ 3. Move `CHANGELOG.md` release notes from `Unreleased` to the new version
94
+ heading (or create one).
15
95
  4. Update `README.md` "latest tag" link/text if present.
16
96
  5. Commit with a release chore message (for example `chore: release vX.Y.Z`).
17
- 6. Create annotated git tag `vX.Y.Z`.
18
- 7. Push commit and tag.
19
- 8. Always create/publish a GitHub Release entry for the tag (tags alone do not update the Releases list).
97
+ 6. Create an annotated git tag `vX.Y.Z`.
98
+ 7. Push the commit and tag.
99
+ 8. Always create or publish a GitHub Release entry for the tag. Tags alone do
100
+ not update the Releases list.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,55 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.3](https://github.com/HybridAIOne/hybridclaw/tree/v0.4.3)
6
+
7
+ ### Added
8
+
9
+ - **Manual session compaction command**: Added built-in `/compact` support across gateway, TUI, and Discord to archive older transcript history, summarize it into high-confidence session memory, and preserve a recent conversation tail for active context.
10
+ - **Bundled PDF workflow support**: Added a built-in `pdf` skill plus Node-based PDF tooling for text extraction, page rendering, fillable form inspection/filling, and non-fillable overlay workflows, with current-turn PDF context injection for explicit file paths and Discord attachments.
11
+ - **Skill installer commands**: Added `hybridclaw skill list` and `hybridclaw skill install <skill> [install-id]` so bundled skills can advertise optional dependency installers.
12
+ - **Container bind path config**: Added `container.binds` support alongside validated host/container path aliasing so configured external directories can be used safely from sandboxed tools and PDF workflows.
13
+ - **Published coverage badge**: CI now generates and publishes a coverage badge JSON artifact for the README badge and release-health visibility.
14
+
15
+ ### Changed
16
+
17
+ - **Attachment and media routing**: Gateway/media prompt assembly now distinguishes image attachments from document attachments, prefers current-turn local files for PDFs, and limits native vision injection to actual image inputs.
18
+ - **Contributor documentation structure**: Promoted `AGENTS.md` to the canonical repo-level agent guide, slimmed `CONTRIBUTING.md` into a contributor quickstart, and moved deeper maintainer/runtime references into `docs/development/`.
19
+ - **Host runtime workspace setup**: Host-mode agent workspaces now link package `node_modules`, while runtime path handling and workspace globbing understand configured extra mounts and local scratch paths more reliably.
20
+ - **Release metadata and docs alignment**: The published package now declares `Node 22.x`, README badges point at maintained badge sources, and the docs landing page tracks the current tagged release/version requirements.
21
+ - **Regression coverage**: Added focused unit coverage for memory chunking, gateway startup/health flows, Discord delivery chunking, PDF context handling, and compaction paths.
22
+
23
+ ### Fixed
24
+
25
+ - **Compaction archive path exposure**: `/compact` responses now show a safe archive reference instead of leaking absolute host filesystem paths in user-facing output.
26
+ - **Workspace bootstrap lifecycle**: `BOOTSTRAP.md` is now removed once onboarding is effectively complete and is not recreated on subsequent starts.
27
+ - **Codex device-code activation flow**: Device-code login now falls back to the default activation URL and tolerates nested pending/authorization error payloads from the auth service.
28
+ - **Runtime-home migration false positive**: Launching HybridClaw from `~/.hybridclaw` no longer treats the runtime `data/` directory as a legacy current-working-directory migration target.
29
+ - **Heartbeat proactive queue cleanup**: Local proactive delivery now drops orphaned heartbeat queue rows instead of trying to route them as real outbound messages.
30
+ - **Coverage badge publishing permissions**: CI now has the repository permissions needed to update the published coverage badge without failing the main workflow.
31
+
32
+ ## [0.4.2](https://github.com/HybridAIOne/hybridclaw/tree/v0.4.2)
33
+
34
+ ### Added
35
+
36
+ - **Gateway debug tracing**: Added `hybridclaw gateway start|restart --debug` to force debug logging and emit request-stage traces across Discord intake, gateway chat handling, container model calls, and Codex streaming transport.
37
+
38
+ ### Changed
39
+
40
+ - **Unified configured model catalog**: Discord slash commands, gateway model commands, and TUI model selection now all consume the same deduplicated configured model list derived from runtime config.
41
+ - **Startup path reliability**: TUI now attaches to a reachable gateway without redundant local runtime preflight, and the CLI resolves symlinked installs correctly so globally linked `hybridclaw` commands no longer exit silently.
42
+
43
+ ### Fixed
44
+
45
+ - **Discord DM trigger suppression**: Greeting-only direct messages are no longer dropped by the guild-oriented auto-suppress filter before they reach the model pipeline.
46
+ - **Container refresh fallback**: Gateway restart now keeps using an existing local image if a stale-image rebuild attempt fails, instead of aborting despite a usable runtime image.
47
+
48
+ ## [0.4.1](https://github.com/HybridAIOne/hybridclaw/tree/v0.4.1)
49
+
50
+ ### Added
51
+
52
+ - **HybridAI auth commands**: Added `hybridclaw hybridai login`, `status`, and `logout` commands with browser-assisted, headless/manual, and env-import flows backed by the existing `~/.hybridclaw/credentials.json` secrets store.
53
+
5
54
  ## [0.4.0](https://github.com/HybridAIOne/hybridclaw/tree/v0.4.0)
6
55
 
7
56
  ### Added
package/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # HybridClaw
2
2
 
3
+ [![CI](https://github.com/HybridAIOne/hybridclaw/actions/workflows/ci.yml/badge.svg)](https://github.com/HybridAIOne/hybridclaw/actions/workflows/ci.yml)
4
+ [![coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/HybridAIOne/hybridclaw/gh-pages/badge/coverage.json)](https://github.com/HybridAIOne/hybridclaw/actions/workflows/ci.yml)
5
+ [![npm](https://img.shields.io/npm/v/@hybridaione/hybridclaw)](https://www.npmjs.com/package/@hybridaione/hybridclaw)
6
+ [![Node](https://img.shields.io/badge/node-22.x-5FA04E?logo=node.js&logoColor=white)](https://nodejs.org/en/download)
7
+ [![License](https://img.shields.io/github/license/HybridAIOne/hybridclaw)](https://github.com/HybridAIOne/hybridclaw/blob/main/LICENSE)
8
+ [![Docs](https://img.shields.io/badge/docs-github%20pages-blue)](https://hybridaione.github.io/hybridclaw/)
9
+ [![Powered by HybridAI](https://img.shields.io/badge/powered%20by-HybridAI-blueviolet)](https://hybridai.one)
10
+
3
11
  <img width="540" height="511" alt="image" src="docs/hero.png" />
4
12
 
5
13
  Personal AI assistant bot for Discord, powered by [HybridAI](https://hybridai.one).
@@ -11,7 +19,8 @@ npm install -g @hybridaione/hybridclaw
11
19
  hybridclaw onboarding
12
20
  ```
13
21
 
14
- Latest release: [v0.4.0](https://github.com/HybridAIOne/hybridclaw/releases/tag/v0.4.0)
22
+ Prerequisites: Node.js 22. Docker is recommended when you want the default
23
+ container sandbox.
15
24
 
16
25
  ## HybridAI Advantage
17
26
 
@@ -65,9 +74,24 @@ hybridclaw tui
65
74
 
66
75
  HybridClaw supports two auth paths:
67
76
 
68
- - `HybridAI API key` via `hybridclaw onboarding`
77
+ - `HybridAI API key` via `hybridclaw hybridai ...` or `hybridclaw onboarding`
69
78
  - `OpenAI Codex OAuth` via `hybridclaw codex ...`
70
79
 
80
+ HybridAI commands:
81
+
82
+ ```bash
83
+ hybridclaw hybridai login
84
+ hybridclaw hybridai login --device-code
85
+ hybridclaw hybridai login --browser
86
+ hybridclaw hybridai login --import
87
+ hybridclaw hybridai status
88
+ hybridclaw hybridai logout
89
+ ```
90
+
91
+ - `hybridclaw hybridai login` auto-selects browser login on local GUI machines and a manual/headless API-key flow on SSH, CI, and container shells.
92
+ - `hybridclaw hybridai login --import` copies the current `HYBRIDAI_API_KEY` from your shell into `~/.hybridclaw/credentials.json`.
93
+ - HybridAI secrets are stored in `~/.hybridclaw/credentials.json`.
94
+
71
95
  Codex commands:
72
96
 
73
97
  ```bash
@@ -81,7 +105,6 @@ hybridclaw codex logout
81
105
 
82
106
  - `hybridclaw codex login` auto-selects browser PKCE on local GUI machines and device code on headless or remote shells.
83
107
  - Codex credentials are stored separately in `~/.hybridclaw/codex-auth.json`.
84
- - HybridAI secrets remain in `~/.hybridclaw/credentials.json`.
85
108
 
86
109
  ## Model Selection
87
110
 
@@ -117,11 +140,17 @@ HybridClaw creates `~/.hybridclaw/config.json` on first run and hot-reloads most
117
140
  - Start from `config.example.json` (reference).
118
141
  - Runtime state lives under `~/.hybridclaw/` (`config.json`, `credentials.json`, `data/hybridclaw.db`, audit/session files).
119
142
  - HybridClaw does not keep runtime state in the current working directory. If `./.env` exists, supported secrets are migrated once into `~/.hybridclaw/credentials.json`.
120
- - `container.*` controls execution isolation, including `sandboxMode`, `memory`, `memorySwap`, `cpus`, `network`, and additional mounts.
143
+ - `container.*` controls execution isolation, including `sandboxMode`, `memory`, `memorySwap`, `cpus`, `network`, `binds`, and additional mounts.
144
+ - Use `container.binds` for explicit host-to-container mounts in `host:container[:ro|rw]` format. Mounted paths appear inside the sandbox under `/workspace/extra/<container>`.
121
145
  - Keep HybridAI secrets in `~/.hybridclaw/credentials.json` (`HYBRIDAI_API_KEY` required for HybridAI models, `DISCORD_TOKEN` optional). Codex OAuth sessions are stored separately in `~/.hybridclaw/codex-auth.json`.
122
146
  - Trust-model acceptance is stored in `~/.hybridclaw/config.json` under `security.*` and is required before runtime starts.
123
147
  - See [TRUST_MODEL.md](./TRUST_MODEL.md) for onboarding acceptance policy and [SECURITY.md](./SECURITY.md) for technical security guidelines.
124
- - For advanced configuration, audit/observability details, skills internals, agent tools, and developer docs, see [CONTRIBUTING.md](./CONTRIBUTING.md).
148
+ - For contributor workflow, see [CONTRIBUTING.md](./CONTRIBUTING.md). For deeper runtime, skills, release, and maintainer reference docs, see [docs/development/README.md](./docs/development/README.md).
149
+
150
+ ## Bundled Skills
151
+
152
+ - `pdf` is bundled and supports text extraction, page rendering, fillable form inspection/filling, and non-fillable overlay workflows.
153
+ - Use `hybridclaw skill list` to inspect available installers and `hybridclaw skill install pdf [install-id]` when a bundled skill advertises optional setup helpers.
125
154
 
126
155
  ## Commands
127
156
 
@@ -133,11 +162,17 @@ CLI runtime commands:
133
162
  - `hybridclaw gateway stop` — Stop managed gateway backend process
134
163
  - `hybridclaw gateway status` — Show lifecycle/API status
135
164
  - `hybridclaw gateway <command...>` — Send a command to a running gateway (for example `sessions`, `bot info`)
165
+ - `hybridclaw gateway compact` — Archive older session history into semantic memory while preserving a recent active context tail
136
166
  - `hybridclaw tui` — Start terminal client connected to gateway
137
167
  - `hybridclaw onboarding` — Run HybridAI account/API key onboarding
168
+ - `hybridclaw hybridai login [--device-code|--browser|--import]` — Store HybridAI API credentials via browser-assisted, headless/manual, or env-import flows
169
+ - `hybridclaw hybridai status` — Show stored HybridAI auth state, token mask, and source
170
+ - `hybridclaw hybridai logout` — Clear stored HybridAI credentials
138
171
  - `hybridclaw codex login [--device-code|--browser|--import]` — Authenticate OpenAI Codex via OAuth or one-time Codex CLI import
139
172
  - `hybridclaw codex status` — Show stored Codex auth state, token mask, expiry, and source
140
173
  - `hybridclaw codex logout` — Clear stored Codex credentials
174
+ - `hybridclaw skill list` — Show skills and any declared installer options
175
+ - `hybridclaw skill install <skill> [install-id]` — Run a declared skill dependency installer
141
176
  - `hybridclaw update [status|--check] [--yes]` — Check for updates and upgrade global npm installs (source checkouts get git-based update instructions)
142
177
  - `hybridclaw audit ...` — Verify and inspect structured audit trail (`recent`, `search`, `approvals`, `verify`, `instructions`)
143
178
  - `hybridclaw audit instructions [--sync]` — Compare runtime instruction copies under `~/.hybridclaw/instructions/` against installed sources and restore shipped defaults when needed
@@ -148,6 +183,7 @@ In Discord, use `!claw help` to see all commands. Key ones:
148
183
  - `!claw bot set <id>` — Set chatbot for this channel
149
184
  - `!claw model set <name>` — Set model for this channel
150
185
  - `!claw rag on/off` — Toggle RAG
186
+ - `!claw compact` — Archive older history into session memory and keep a recent working tail
151
187
  - `!claw clear` — Clear conversation history
152
188
  - `!claw audit recent [n]` — Show recent structured audit events
153
189
  - `!claw audit verify [sessionId]` — Verify audit hash chain integrity
@@ -158,3 +194,5 @@ In Discord, use `!claw help` to see all commands. Key ones:
158
194
  - `!claw schedule add "<cron>" <prompt>` — Add cron scheduled task
159
195
  - `!claw schedule add at "<ISO time>" <prompt>` — Add one-shot task
160
196
  - `!claw schedule add every <ms> <prompt>` — Add interval task
197
+
198
+ In the TUI, use `/compact` to trigger the same session compaction flow.
@@ -89,10 +89,21 @@
89
89
  "cpus": "1",
90
90
  "network": "bridge",
91
91
  "timeoutMs": 300000,
92
+ "binds": [],
92
93
  "additionalMounts": "",
93
94
  "maxOutputBytes": 10485760,
94
95
  "maxConcurrent": 5
95
96
  },
97
+ "web": {
98
+ "search": {
99
+ "provider": "auto",
100
+ "fallbackProviders": [],
101
+ "defaultCount": 5,
102
+ "cacheTtlMinutes": 5,
103
+ "searxngBaseUrl": "",
104
+ "tavilySearchDepth": "advanced"
105
+ }
106
+ },
96
107
  "heartbeat": {
97
108
  "enabled": true,
98
109
  "intervalMs": 1800000,
@@ -1,10 +1,16 @@
1
1
  FROM node:20-slim
2
2
 
3
3
  RUN apt-get update && apt-get install -y --no-install-recommends \
4
- ripgrep git curl python3 python3-pip \
4
+ ripgrep git curl python3 python3-pip poppler-utils qpdf \
5
5
  && rm -rf /var/lib/apt/lists/*
6
6
 
7
- RUN python3 -m pip install --no-cache-dir --break-system-packages uv
7
+ RUN python3 -m pip install --no-cache-dir --break-system-packages \
8
+ uv \
9
+ pypdf==5.4.0 \
10
+ pdfplumber==0.11.6 \
11
+ pdf2image==1.17.0 \
12
+ reportlab==4.4.4 \
13
+ pillow==11.3.0
8
14
 
9
15
  WORKDIR /app
10
16
 
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
5
  import { URL } from 'node:url';
5
6
  const POLICY_PATH = path.posix.join('/workspace', '.hybridclaw', 'policy.yaml');
@@ -8,6 +9,12 @@ const YELLOW_IMPLICIT_DELAY_MS = 5_000;
8
9
  const YELLOW_IMPLICIT_DELAY_SECS = Math.max(1, Math.round(YELLOW_IMPLICIT_DELAY_MS / 1_000));
9
10
  const MAX_PROMPT_CHARS = 1_200;
10
11
  const MAX_COMMAND_PREVIEW_CHARS = 160;
12
+ const WORKSPACE_ROOT_DISPLAY = '/workspace';
13
+ const WORKSPACE_ROOT_ACTUAL = path.resolve(process.env.HYBRIDCLAW_AGENT_WORKSPACE_ROOT || WORKSPACE_ROOT_DISPLAY);
14
+ const SCRATCH_ROOTS = Array.from(new Set(['/tmp', '/private/tmp', os.tmpdir()]
15
+ .map((value) => value.trim())
16
+ .filter(Boolean)
17
+ .map((value) => path.resolve(value))));
11
18
  const DEFAULT_POLICY = {
12
19
  pinnedRed: [
13
20
  { pattern: 'rm\\s+-rf\\s+/' },
@@ -29,6 +36,7 @@ const WRITE_INTENT_RE = /\b(mkdir|touch|mv|cp|chmod|chown|tee)\b|(^|[^>])>>?[^>]
29
36
  const INSTALL_RE = /\b(npm|pnpm|yarn|bun)\s+(install|add)\b/i;
30
37
  const GIT_WRITE_RE = /\bgit\s+(add|commit|checkout\s+-b|branch|merge|rebase|tag)\b/i;
31
38
  const UNKNOWN_SCRIPT_RE = /(^|\s)(\.[/\\][^\s]+|bash\s+[^\s]+\.sh|zsh\s+[^\s]+\.sh|sh\s+[^\s]+\.sh)(\s|$)/i;
39
+ const READ_ONLY_PDF_SCRIPT_RE = /^\s*node\s+skills\/pdf\/scripts\/(?:extract_pdf_text|check_fillable_fields|extract_form_field_info|extract_form_structure)\.mjs\b/i;
32
40
  const READ_ONLY_BASH_RE = /^\s*(ls|pwd|cat|head|tail|wc|rg|grep|find|git\s+(status|log|diff|show)|npm\s+test|pnpm\s+test|yarn\s+test|vitest|pytest|phpunit|node\s+--version|npm\s+--version|pnpm\s+--version|yarn\s+--version)\b/i;
33
41
  const NETWORK_COMMAND_RE = /\b(curl|wget|http|https|ssh|scp)\b/i;
34
42
  const ABS_PATH_RE = /(^|\s)(\/[^\s"'`;,|&()<>]+)/g;
@@ -411,6 +419,104 @@ function extractAbsolutePaths(input) {
411
419
  }
412
420
  return [...paths];
413
421
  }
422
+ function splitCommandSegments(command) {
423
+ const segments = [];
424
+ let current = '';
425
+ let quote = null;
426
+ for (let index = 0; index < command.length; index += 1) {
427
+ const char = command[index];
428
+ const next = command[index + 1];
429
+ if (char === "'" && quote !== '"') {
430
+ quote = quote === "'" ? null : "'";
431
+ current += char;
432
+ continue;
433
+ }
434
+ if (char === '"' && quote !== "'") {
435
+ quote = quote === '"' ? null : '"';
436
+ current += char;
437
+ continue;
438
+ }
439
+ if (!quote) {
440
+ if (char === ';') {
441
+ if (current.trim())
442
+ segments.push(current.trim());
443
+ current = '';
444
+ continue;
445
+ }
446
+ if ((char === '&' || char === '|') && next === char) {
447
+ if (current.trim())
448
+ segments.push(current.trim());
449
+ current = '';
450
+ index += 1;
451
+ continue;
452
+ }
453
+ if (char === '|') {
454
+ if (current.trim())
455
+ segments.push(current.trim());
456
+ current = '';
457
+ continue;
458
+ }
459
+ }
460
+ current += char;
461
+ }
462
+ if (current.trim())
463
+ segments.push(current.trim());
464
+ return segments;
465
+ }
466
+ function unquotePathToken(rawValue) {
467
+ const trimmed = rawValue.trim();
468
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
469
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
470
+ return trimmed.slice(1, -1);
471
+ }
472
+ return trimmed;
473
+ }
474
+ function pushAbsolutePath(output, rawValue) {
475
+ const candidate = unquotePathToken(String(rawValue || ''));
476
+ if (!candidate.startsWith('/'))
477
+ return;
478
+ output.add(candidate);
479
+ }
480
+ function extractLikelyWritePaths(command) {
481
+ const paths = new Set();
482
+ const segments = splitCommandSegments(command);
483
+ for (const segment of segments) {
484
+ const segmentAbsPaths = extractAbsolutePaths(segment);
485
+ for (const match of segment.matchAll(/(?:^|\s)(?:--out|-o)\s+("[^"]+"|'[^']+'|\/[^\s"'`;,|&()<>]+)/g)) {
486
+ pushAbsolutePath(paths, match[1]);
487
+ }
488
+ for (const match of segment.matchAll(/(?:^|[^>])>>?\s*("[^"]+"|'[^']+'|\/[^\s"'`;,|&()<>]+)/g)) {
489
+ pushAbsolutePath(paths, match[1]);
490
+ }
491
+ for (const match of segment.matchAll(/(?:^|\s)tee(?:\s+-a)?\s+("[^"]+"|'[^']+'|\/[^\s"'`;,|&()<>]+)/g)) {
492
+ pushAbsolutePath(paths, match[1]);
493
+ }
494
+ if (/^\s*(mkdir|touch|chmod|chown)\b/i.test(segment)) {
495
+ for (const candidate of segmentAbsPaths) {
496
+ paths.add(candidate);
497
+ }
498
+ }
499
+ if (/^\s*(cp|mv)\b/i.test(segment)) {
500
+ const destination = segmentAbsPaths.at(-1);
501
+ if (destination)
502
+ paths.add(destination);
503
+ }
504
+ }
505
+ return [...paths];
506
+ }
507
+ function isWithinResolvedRoot(candidate, root) {
508
+ const resolvedCandidate = path.resolve(candidate);
509
+ const resolvedRoot = path.resolve(root);
510
+ return (resolvedCandidate === resolvedRoot ||
511
+ resolvedCandidate.startsWith(`${resolvedRoot}${path.sep}`));
512
+ }
513
+ function isWorkspacePath(rawPath) {
514
+ return (isWithinResolvedRoot(rawPath, WORKSPACE_ROOT_DISPLAY) ||
515
+ isWithinResolvedRoot(rawPath, WORKSPACE_ROOT_ACTUAL));
516
+ }
517
+ function isScratchPath(rawPath) {
518
+ return SCRATCH_ROOTS.some((root) => isWithinResolvedRoot(rawPath, root));
519
+ }
414
520
  function primaryPathKey(rawPath) {
415
521
  const normalized = normalizePathValue(rawPath);
416
522
  if (!normalized)
@@ -664,7 +770,13 @@ export class TrustedCoworkerApprovalRuntime {
664
770
  : mode === 'agent'
665
771
  ? 'agent trust'
666
772
  : 'once';
667
- const replayPrompt = normalizePrompt(target.originalPrompt);
773
+ const replayPrompt = normalizePrompt([
774
+ '[Approval already granted]',
775
+ `The action "${target.intent}" is approved (${modeSummary}). Continue with it now.`,
776
+ 'Do not ask for approval again unless a new blocked action appears.',
777
+ '',
778
+ `Original user request: ${target.originalPrompt}`,
779
+ ].join('\n'));
668
780
  return {
669
781
  replayPrompt: replayPrompt || undefined,
670
782
  approvalMode: mode,
@@ -790,7 +902,9 @@ export class TrustedCoworkerApprovalRuntime {
790
902
  reason: classified.reason,
791
903
  commandPreview: classified.commandPreview,
792
904
  pinned: pinnedByPolicy,
793
- implicitDelayMs: tier === 'yellow' ? YELLOW_IMPLICIT_DELAY_MS : undefined,
905
+ implicitDelayMs: tier === 'yellow' && decision === 'implicit'
906
+ ? YELLOW_IMPLICIT_DELAY_MS
907
+ : undefined,
794
908
  hostHints: classified.hostHints,
795
909
  };
796
910
  }
@@ -896,6 +1010,29 @@ export class TrustedCoworkerApprovalRuntime {
896
1010
  stickyYellow: false,
897
1011
  };
898
1012
  }
1013
+ if (lowerTool === 'message') {
1014
+ const action = normalizeText(args.action).toLowerCase();
1015
+ const readonlyAction = action === 'read' ||
1016
+ action === 'member-info' ||
1017
+ action === 'channel-info';
1018
+ return {
1019
+ tier: readonlyAction ? 'green' : 'yellow',
1020
+ actionKey: action ? `message:${action}` : 'message',
1021
+ intent: `run message${action ? ` ${action}` : ''}`,
1022
+ consequenceIfDenied: action === 'send'
1023
+ ? 'no message will be sent.'
1024
+ : 'I will continue without this message lookup.',
1025
+ reason: readonlyAction
1026
+ ? 'this is a read-only channel operation'
1027
+ : 'this action may change channel state',
1028
+ commandPreview: normalizePreview(JSON.stringify(args)),
1029
+ pathHints: [],
1030
+ hostHints: [],
1031
+ writeIntent: action === 'send',
1032
+ promotableRed: false,
1033
+ stickyYellow: false,
1034
+ };
1035
+ }
899
1036
  if (lowerTool === 'delete') {
900
1037
  const rawPath = normalizeText(args.path);
901
1038
  const key = rawPath
@@ -941,6 +1078,47 @@ export class TrustedCoworkerApprovalRuntime {
941
1078
  stickyYellow: false,
942
1079
  };
943
1080
  }
1081
+ if (lowerTool === 'web_search') {
1082
+ const provider = normalizeText(args.provider).toLowerCase();
1083
+ const providerHosts = (() => {
1084
+ switch (provider) {
1085
+ case 'brave':
1086
+ return ['api.search.brave.com'];
1087
+ case 'perplexity':
1088
+ return ['api.perplexity.ai'];
1089
+ case 'tavily':
1090
+ return ['api.tavily.com'];
1091
+ case 'duckduckgo':
1092
+ return ['html.duckduckgo.com'];
1093
+ case 'searxng':
1094
+ return extractHostScopes(extractHostsFromUrlLikeText(process.env.SEARXNG_BASE_URL || ''));
1095
+ default:
1096
+ return [
1097
+ 'api.search.brave.com',
1098
+ 'api.perplexity.ai',
1099
+ 'api.tavily.com',
1100
+ 'html.duckduckgo.com',
1101
+ ];
1102
+ }
1103
+ })();
1104
+ const primaryHost = providerHosts[0] || 'web-search';
1105
+ const unseen = providerHosts.filter((host) => !this.seenNetworkHosts.has(host));
1106
+ return {
1107
+ tier: unseen.length > 0 ? 'red' : 'yellow',
1108
+ actionKey: `network:${primaryHost}`,
1109
+ intent: `search the web via ${provider || 'configured providers'}`,
1110
+ consequenceIfDenied: 'I will avoid external search providers and continue with local context only.',
1111
+ reason: unseen.length > 0
1112
+ ? 'this would contact a new external host'
1113
+ : 'this is an external network action',
1114
+ commandPreview: normalizePreview(JSON.stringify(args)),
1115
+ pathHints: [],
1116
+ hostHints: providerHosts,
1117
+ writeIntent: false,
1118
+ promotableRed: unseen.length > 0,
1119
+ stickyYellow: true,
1120
+ };
1121
+ }
944
1122
  if (lowerTool === 'web_fetch' || lowerTool === 'browser_navigate') {
945
1123
  const rawUrl = normalizeText(args.url);
946
1124
  const hostScopes = extractHostScopes(extractHostsFromUrlLikeText(rawUrl));
@@ -1002,6 +1180,7 @@ export class TrustedCoworkerApprovalRuntime {
1002
1180
  const hosts = extractHostsFromUrlLikeText(command);
1003
1181
  const unseenHosts = hosts.filter((host) => !this.seenNetworkHosts.has(host));
1004
1182
  const absPaths = extractAbsolutePaths(command);
1183
+ const likelyWritePaths = extractLikelyWritePaths(command);
1005
1184
  const writeIntent = WRITE_INTENT_RE.test(command) ||
1006
1185
  DELETE_RE.test(command) ||
1007
1186
  INSTALL_RE.test(command) ||
@@ -1022,7 +1201,10 @@ export class TrustedCoworkerApprovalRuntime {
1022
1201
  };
1023
1202
  }
1024
1203
  if (this.loadedPolicy.workspaceFence && writeIntent) {
1025
- const outsideWorkspace = absPaths.find((entry) => !entry.startsWith('/workspace') && !entry.startsWith('/dev/null'));
1204
+ const workspaceFencePaths = likelyWritePaths.length > 0 ? likelyWritePaths : absPaths;
1205
+ const outsideWorkspace = workspaceFencePaths.find((entry) => !isWorkspacePath(entry) &&
1206
+ !entry.startsWith('/dev/null') &&
1207
+ !isScratchPath(entry));
1026
1208
  if (outsideWorkspace) {
1027
1209
  return {
1028
1210
  tier: 'red',
@@ -1130,6 +1312,21 @@ export class TrustedCoworkerApprovalRuntime {
1130
1312
  stickyYellow: false,
1131
1313
  };
1132
1314
  }
1315
+ if (READ_ONLY_PDF_SCRIPT_RE.test(command)) {
1316
+ return {
1317
+ tier: 'green',
1318
+ actionKey: 'bash:pdf-read-only',
1319
+ intent: `run read-only PDF command \`${normalizePreview(command)}\``,
1320
+ consequenceIfDenied: 'I will continue without that PDF check.',
1321
+ reason: 'this command only reads PDF content',
1322
+ commandPreview: normalizePreview(command),
1323
+ pathHints: absPaths,
1324
+ hostHints: hosts,
1325
+ writeIntent: false,
1326
+ promotableRed: false,
1327
+ stickyYellow: false,
1328
+ };
1329
+ }
1133
1330
  return {
1134
1331
  tier: 'yellow',
1135
1332
  actionKey: 'bash:other',