@hybridaione/hybridclaw 0.4.2 → 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 (152) hide show
  1. package/AGENTS.md +86 -5
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +25 -2
  4. package/config.example.json +1 -0
  5. package/container/Dockerfile +8 -2
  6. package/container/dist/approval-policy.js +159 -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 +79 -9
  11. package/container/dist/index.js.map +1 -1
  12. package/container/dist/runtime-paths.js +84 -0
  13. package/container/dist/runtime-paths.js.map +1 -1
  14. package/container/dist/tools.js +85 -12
  15. package/container/dist/tools.js.map +1 -1
  16. package/container/dist/web-search.js +1 -7
  17. package/container/dist/web-search.js.map +1 -1
  18. package/container/package-lock.json +313 -2
  19. package/container/package.json +6 -1
  20. package/container/src/approval-policy.ts +200 -4
  21. package/container/src/browser-tools.ts +3 -0
  22. package/container/src/index.ts +85 -9
  23. package/container/src/runtime-paths.ts +96 -0
  24. package/container/src/tools.ts +99 -11
  25. package/container/src/web-search.ts +3 -9
  26. package/dist/agent/agent.d.ts.map +1 -1
  27. package/dist/agent/agent.js +10 -2
  28. package/dist/agent/agent.js.map +1 -1
  29. package/dist/agent/executor.d.ts.map +1 -1
  30. package/dist/agent/executor.js +5 -10
  31. package/dist/agent/executor.js.map +1 -1
  32. package/dist/agent/prompt-hooks.d.ts.map +1 -1
  33. package/dist/agent/prompt-hooks.js +36 -2
  34. package/dist/agent/prompt-hooks.js.map +1 -1
  35. package/dist/auth/codex-auth.d.ts +1 -0
  36. package/dist/auth/codex-auth.d.ts.map +1 -1
  37. package/dist/auth/codex-auth.js +23 -8
  38. package/dist/auth/codex-auth.js.map +1 -1
  39. package/dist/channels/discord/attachments.d.ts.map +1 -1
  40. package/dist/channels/discord/attachments.js +67 -11
  41. package/dist/channels/discord/attachments.js.map +1 -1
  42. package/dist/channels/discord/inbound.d.ts.map +1 -1
  43. package/dist/channels/discord/inbound.js +1 -0
  44. package/dist/channels/discord/inbound.js.map +1 -1
  45. package/dist/channels/discord/runtime.d.ts.map +1 -1
  46. package/dist/channels/discord/runtime.js +49 -0
  47. package/dist/channels/discord/runtime.js.map +1 -1
  48. package/dist/cli.d.ts.map +1 -1
  49. package/dist/cli.js +66 -1
  50. package/dist/cli.js.map +1 -1
  51. package/dist/config/config.d.ts +1 -0
  52. package/dist/config/config.d.ts.map +1 -1
  53. package/dist/config/config.js +2 -0
  54. package/dist/config/config.js.map +1 -1
  55. package/dist/config/runtime-config.d.ts +1 -0
  56. package/dist/config/runtime-config.d.ts.map +1 -1
  57. package/dist/config/runtime-config.js +2 -0
  58. package/dist/config/runtime-config.js.map +1 -1
  59. package/dist/gateway/gateway-service.d.ts.map +1 -1
  60. package/dist/gateway/gateway-service.js +54 -3
  61. package/dist/gateway/gateway-service.js.map +1 -1
  62. package/dist/gateway/gateway.js +20 -6
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/proactive-delivery.d.ts +8 -0
  65. package/dist/gateway/proactive-delivery.d.ts.map +1 -0
  66. package/dist/gateway/proactive-delivery.js +14 -0
  67. package/dist/gateway/proactive-delivery.js.map +1 -0
  68. package/dist/infra/container-runner.d.ts.map +1 -1
  69. package/dist/infra/container-runner.js +42 -16
  70. package/dist/infra/container-runner.js.map +1 -1
  71. package/dist/infra/host-runner.d.ts.map +1 -1
  72. package/dist/infra/host-runner.js +43 -6
  73. package/dist/infra/host-runner.js.map +1 -1
  74. package/dist/infra/stream-debug.d.ts +9 -0
  75. package/dist/infra/stream-debug.d.ts.map +1 -0
  76. package/dist/infra/stream-debug.js +47 -0
  77. package/dist/infra/stream-debug.js.map +1 -0
  78. package/dist/logger-format.d.ts +13 -0
  79. package/dist/logger-format.d.ts.map +1 -0
  80. package/dist/logger-format.js +19 -0
  81. package/dist/logger-format.js.map +1 -0
  82. package/dist/logger.d.ts.map +1 -1
  83. package/dist/logger.js +4 -1
  84. package/dist/logger.js.map +1 -1
  85. package/dist/media/pdf-context.d.ts +8 -0
  86. package/dist/media/pdf-context.d.ts.map +1 -0
  87. package/dist/media/pdf-context.js +395 -0
  88. package/dist/media/pdf-context.js.map +1 -0
  89. package/dist/memory/compaction-archive.d.ts +8 -0
  90. package/dist/memory/compaction-archive.d.ts.map +1 -0
  91. package/dist/memory/compaction-archive.js +82 -0
  92. package/dist/memory/compaction-archive.js.map +1 -0
  93. package/dist/memory/compaction.d.ts +58 -0
  94. package/dist/memory/compaction.d.ts.map +1 -0
  95. package/dist/memory/compaction.js +494 -0
  96. package/dist/memory/compaction.js.map +1 -0
  97. package/dist/memory/db.d.ts +1 -0
  98. package/dist/memory/db.d.ts.map +1 -1
  99. package/dist/memory/db.js +25 -0
  100. package/dist/memory/db.js.map +1 -1
  101. package/dist/memory/memory-service.d.ts +5 -1
  102. package/dist/memory/memory-service.d.ts.map +1 -1
  103. package/dist/memory/memory-service.js +59 -1
  104. package/dist/memory/memory-service.js.map +1 -1
  105. package/dist/security/mount-config.d.ts +14 -0
  106. package/dist/security/mount-config.d.ts.map +1 -0
  107. package/dist/security/mount-config.js +155 -0
  108. package/dist/security/mount-config.js.map +1 -0
  109. package/dist/security/mount-security.d.ts +1 -0
  110. package/dist/security/mount-security.d.ts.map +1 -1
  111. package/dist/security/mount-security.js +7 -4
  112. package/dist/security/mount-security.js.map +1 -1
  113. package/dist/skills/skills-install.d.ts +26 -0
  114. package/dist/skills/skills-install.d.ts.map +1 -0
  115. package/dist/skills/skills-install.js +248 -0
  116. package/dist/skills/skills-install.js.map +1 -0
  117. package/dist/skills/skills.d.ts +39 -0
  118. package/dist/skills/skills.d.ts.map +1 -1
  119. package/dist/skills/skills.js +120 -20
  120. package/dist/skills/skills.js.map +1 -1
  121. package/dist/tui.js +4 -0
  122. package/dist/tui.js.map +1 -1
  123. package/dist/types.d.ts +36 -0
  124. package/dist/types.d.ts.map +1 -1
  125. package/dist/types.js.map +1 -1
  126. package/dist/workspace.d.ts.map +1 -1
  127. package/dist/workspace.js +140 -15
  128. package/dist/workspace.js.map +1 -1
  129. package/docs/development/README.md +15 -0
  130. package/docs/development/architecture.md +44 -0
  131. package/docs/development/releasing.md +70 -0
  132. package/docs/development/runtime.md +136 -0
  133. package/docs/development/skills.md +34 -0
  134. package/docs/development/testing.md +73 -0
  135. package/docs/index.html +14 -14
  136. package/docs/tools/web-search.md +1 -1
  137. package/package.json +9 -1
  138. package/skills/pdf/SKILL.md +215 -0
  139. package/skills/pdf/forms.md +263 -0
  140. package/skills/pdf/reference.md +179 -0
  141. package/skills/pdf/scripts/_pdf_form_runtime.mjs +500 -0
  142. package/skills/pdf/scripts/_pdf_runtime.mjs +212 -0
  143. package/skills/pdf/scripts/check_bounding_boxes.mjs +96 -0
  144. package/skills/pdf/scripts/check_fillable_fields.mjs +29 -0
  145. package/skills/pdf/scripts/create_validation_image.mjs +60 -0
  146. package/skills/pdf/scripts/extract_form_field_info.mjs +62 -0
  147. package/skills/pdf/scripts/extract_form_structure.mjs +135 -0
  148. package/skills/pdf/scripts/extract_pdf_text.mjs +60 -0
  149. package/skills/pdf/scripts/fill_fillable_fields.mjs +70 -0
  150. package/skills/pdf/scripts/fill_pdf_form_with_annotations.mjs +88 -0
  151. package/skills/pdf/scripts/render_pdf_pages.mjs +68 -0
  152. package/templates/README.md +13 -0
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,33 @@
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
+
5
32
  ## [0.4.2](https://github.com/HybridAIOne/hybridclaw/tree/v0.4.2)
6
33
 
7
34
  ### 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,6 +19,9 @@ npm install -g @hybridaione/hybridclaw
11
19
  hybridclaw onboarding
12
20
  ```
13
21
 
22
+ Prerequisites: Node.js 22. Docker is recommended when you want the default
23
+ container sandbox.
24
+
14
25
  ## HybridAI Advantage
15
26
 
16
27
  - Security-focused foundation
@@ -129,11 +140,17 @@ HybridClaw creates `~/.hybridclaw/config.json` on first run and hot-reloads most
129
140
  - Start from `config.example.json` (reference).
130
141
  - Runtime state lives under `~/.hybridclaw/` (`config.json`, `credentials.json`, `data/hybridclaw.db`, audit/session files).
131
142
  - HybridClaw does not keep runtime state in the current working directory. If `./.env` exists, supported secrets are migrated once into `~/.hybridclaw/credentials.json`.
132
- - `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>`.
133
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`.
134
146
  - Trust-model acceptance is stored in `~/.hybridclaw/config.json` under `security.*` and is required before runtime starts.
135
147
  - See [TRUST_MODEL.md](./TRUST_MODEL.md) for onboarding acceptance policy and [SECURITY.md](./SECURITY.md) for technical security guidelines.
136
- - 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.
137
154
 
138
155
  ## Commands
139
156
 
@@ -145,6 +162,7 @@ CLI runtime commands:
145
162
  - `hybridclaw gateway stop` — Stop managed gateway backend process
146
163
  - `hybridclaw gateway status` — Show lifecycle/API status
147
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
148
166
  - `hybridclaw tui` — Start terminal client connected to gateway
149
167
  - `hybridclaw onboarding` — Run HybridAI account/API key onboarding
150
168
  - `hybridclaw hybridai login [--device-code|--browser|--import]` — Store HybridAI API credentials via browser-assisted, headless/manual, or env-import flows
@@ -153,6 +171,8 @@ CLI runtime commands:
153
171
  - `hybridclaw codex login [--device-code|--browser|--import]` — Authenticate OpenAI Codex via OAuth or one-time Codex CLI import
154
172
  - `hybridclaw codex status` — Show stored Codex auth state, token mask, expiry, and source
155
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
156
176
  - `hybridclaw update [status|--check] [--yes]` — Check for updates and upgrade global npm installs (source checkouts get git-based update instructions)
157
177
  - `hybridclaw audit ...` — Verify and inspect structured audit trail (`recent`, `search`, `approvals`, `verify`, `instructions`)
158
178
  - `hybridclaw audit instructions [--sync]` — Compare runtime instruction copies under `~/.hybridclaw/instructions/` against installed sources and restore shipped defaults when needed
@@ -163,6 +183,7 @@ In Discord, use `!claw help` to see all commands. Key ones:
163
183
  - `!claw bot set <id>` — Set chatbot for this channel
164
184
  - `!claw model set <name>` — Set model for this channel
165
185
  - `!claw rag on/off` — Toggle RAG
186
+ - `!claw compact` — Archive older history into session memory and keep a recent working tail
166
187
  - `!claw clear` — Clear conversation history
167
188
  - `!claw audit recent [n]` — Show recent structured audit events
168
189
  - `!claw audit verify [sessionId]` — Verify audit hash chain integrity
@@ -173,3 +194,5 @@ In Discord, use `!claw help` to see all commands. Key ones:
173
194
  - `!claw schedule add "<cron>" <prompt>` — Add cron scheduled task
174
195
  - `!claw schedule add at "<ISO time>" <prompt>` — Add one-shot task
175
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,6 +89,7 @@
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
@@ -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
@@ -1043,6 +1180,7 @@ export class TrustedCoworkerApprovalRuntime {
1043
1180
  const hosts = extractHostsFromUrlLikeText(command);
1044
1181
  const unseenHosts = hosts.filter((host) => !this.seenNetworkHosts.has(host));
1045
1182
  const absPaths = extractAbsolutePaths(command);
1183
+ const likelyWritePaths = extractLikelyWritePaths(command);
1046
1184
  const writeIntent = WRITE_INTENT_RE.test(command) ||
1047
1185
  DELETE_RE.test(command) ||
1048
1186
  INSTALL_RE.test(command) ||
@@ -1063,7 +1201,10 @@ export class TrustedCoworkerApprovalRuntime {
1063
1201
  };
1064
1202
  }
1065
1203
  if (this.loadedPolicy.workspaceFence && writeIntent) {
1066
- 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));
1067
1208
  if (outsideWorkspace) {
1068
1209
  return {
1069
1210
  tier: 'red',
@@ -1171,6 +1312,21 @@ export class TrustedCoworkerApprovalRuntime {
1171
1312
  stickyYellow: false,
1172
1313
  };
1173
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
+ }
1174
1330
  return {
1175
1331
  tier: 'yellow',
1176
1332
  actionKey: 'bash:other',