@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.
- package/AGENTS.md +86 -5
- package/CHANGELOG.md +49 -0
- package/README.md +43 -5
- package/config.example.json +11 -0
- package/container/Dockerfile +8 -2
- package/container/dist/approval-policy.js +200 -3
- package/container/dist/approval-policy.js.map +1 -1
- package/container/dist/browser-tools.js +2 -0
- package/container/dist/browser-tools.js.map +1 -1
- package/container/dist/index.js +86 -10
- package/container/dist/index.js.map +1 -1
- package/container/dist/model-client.js.map +1 -1
- package/container/dist/providers/openai-codex.js +22 -1
- package/container/dist/providers/openai-codex.js.map +1 -1
- package/container/dist/runtime-paths.js +84 -0
- package/container/dist/runtime-paths.js.map +1 -1
- package/container/dist/tools.js +141 -11
- package/container/dist/tools.js.map +1 -1
- package/container/dist/web-search.js +917 -0
- package/container/dist/web-search.js.map +1 -0
- package/container/package-lock.json +313 -2
- package/container/package.json +6 -1
- package/container/src/approval-policy.ts +248 -4
- package/container/src/browser-tools.ts +3 -0
- package/container/src/index.ts +98 -9
- package/container/src/model-client.ts +0 -1
- package/container/src/providers/openai-codex.ts +42 -1
- package/container/src/runtime-paths.ts +96 -0
- package/container/src/tools.ts +163 -11
- package/container/src/types.ts +20 -0
- package/container/src/web-search.ts +1176 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +10 -2
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/executor.d.ts.map +1 -1
- package/dist/agent/executor.js +5 -10
- package/dist/agent/executor.js.map +1 -1
- package/dist/agent/prompt-hooks.d.ts.map +1 -1
- package/dist/agent/prompt-hooks.js +38 -4
- package/dist/agent/prompt-hooks.js.map +1 -1
- package/dist/agent/tool-summary.js +1 -1
- package/dist/agent/tool-summary.js.map +1 -1
- package/dist/auth/codex-auth.d.ts +1 -0
- package/dist/auth/codex-auth.d.ts.map +1 -1
- package/dist/auth/codex-auth.js +23 -8
- package/dist/auth/codex-auth.js.map +1 -1
- package/dist/auth/hybridai-auth.d.ts +15 -0
- package/dist/auth/hybridai-auth.d.ts.map +1 -1
- package/dist/auth/hybridai-auth.js +267 -2
- package/dist/auth/hybridai-auth.js.map +1 -1
- package/dist/channels/discord/attachments.d.ts.map +1 -1
- package/dist/channels/discord/attachments.js +67 -11
- package/dist/channels/discord/attachments.js.map +1 -1
- package/dist/channels/discord/inbound.d.ts.map +1 -1
- package/dist/channels/discord/inbound.js +3 -2
- package/dist/channels/discord/inbound.js.map +1 -1
- package/dist/channels/discord/runtime.d.ts.map +1 -1
- package/dist/channels/discord/runtime.js +134 -5
- package/dist/channels/discord/runtime.js.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +178 -29
- package/dist/cli.js.map +1 -1
- package/dist/config/cli-flags.d.ts +3 -1
- package/dist/config/cli-flags.d.ts.map +1 -1
- package/dist/config/cli-flags.js +14 -1
- package/dist/config/cli-flags.js.map +1 -1
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +21 -4
- package/dist/config/config.js.map +1 -1
- package/dist/config/runtime-config.d.ts +13 -0
- package/dist/config/runtime-config.d.ts.map +1 -1
- package/dist/config/runtime-config.js +66 -0
- package/dist/config/runtime-config.js.map +1 -1
- package/dist/gateway/gateway-service.d.ts.map +1 -1
- package/dist/gateway/gateway-service.js +152 -13
- package/dist/gateway/gateway-service.js.map +1 -1
- package/dist/gateway/gateway.js +20 -6
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/health.d.ts.map +1 -1
- package/dist/gateway/health.js +8 -0
- package/dist/gateway/health.js.map +1 -1
- package/dist/gateway/proactive-delivery.d.ts +8 -0
- package/dist/gateway/proactive-delivery.d.ts.map +1 -0
- package/dist/gateway/proactive-delivery.js +14 -0
- package/dist/gateway/proactive-delivery.js.map +1 -0
- package/dist/infra/container-runner.d.ts.map +1 -1
- package/dist/infra/container-runner.js +71 -16
- package/dist/infra/container-runner.js.map +1 -1
- package/dist/infra/container-setup.d.ts.map +1 -1
- package/dist/infra/container-setup.js +24 -6
- package/dist/infra/container-setup.js.map +1 -1
- package/dist/infra/host-runner.d.ts.map +1 -1
- package/dist/infra/host-runner.js +61 -7
- package/dist/infra/host-runner.js.map +1 -1
- package/dist/infra/stream-debug.d.ts +9 -0
- package/dist/infra/stream-debug.d.ts.map +1 -0
- package/dist/infra/stream-debug.js +47 -0
- package/dist/infra/stream-debug.js.map +1 -0
- package/dist/logger-format.d.ts +13 -0
- package/dist/logger-format.d.ts.map +1 -0
- package/dist/logger-format.js +19 -0
- package/dist/logger-format.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +35 -2
- package/dist/logger.js.map +1 -1
- package/dist/media/pdf-context.d.ts +8 -0
- package/dist/media/pdf-context.d.ts.map +1 -0
- package/dist/media/pdf-context.js +395 -0
- package/dist/media/pdf-context.js.map +1 -0
- package/dist/memory/compaction-archive.d.ts +8 -0
- package/dist/memory/compaction-archive.d.ts.map +1 -0
- package/dist/memory/compaction-archive.js +82 -0
- package/dist/memory/compaction-archive.js.map +1 -0
- package/dist/memory/compaction.d.ts +58 -0
- package/dist/memory/compaction.d.ts.map +1 -0
- package/dist/memory/compaction.js +494 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/db.d.ts +1 -0
- package/dist/memory/db.d.ts.map +1 -1
- package/dist/memory/db.js +25 -0
- package/dist/memory/db.js.map +1 -1
- package/dist/memory/memory-service.d.ts +5 -1
- package/dist/memory/memory-service.d.ts.map +1 -1
- package/dist/memory/memory-service.js +59 -1
- package/dist/memory/memory-service.js.map +1 -1
- package/dist/scheduler/heartbeat.d.ts.map +1 -1
- package/dist/scheduler/heartbeat.js +1 -0
- package/dist/scheduler/heartbeat.js.map +1 -1
- package/dist/security/mount-config.d.ts +14 -0
- package/dist/security/mount-config.d.ts.map +1 -0
- package/dist/security/mount-config.js +155 -0
- package/dist/security/mount-config.js.map +1 -0
- package/dist/security/mount-security.d.ts +1 -0
- package/dist/security/mount-security.d.ts.map +1 -1
- package/dist/security/mount-security.js +7 -4
- package/dist/security/mount-security.js.map +1 -1
- package/dist/skills/skills-install.d.ts +26 -0
- package/dist/skills/skills-install.d.ts.map +1 -0
- package/dist/skills/skills-install.js +248 -0
- package/dist/skills/skills-install.js.map +1 -0
- package/dist/skills/skills.d.ts +39 -0
- package/dist/skills/skills.d.ts.map +1 -1
- package/dist/skills/skills.js +120 -20
- package/dist/skills/skills.js.map +1 -1
- package/dist/tui.js +7 -3
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +143 -15
- package/dist/workspace.js.map +1 -1
- package/docs/development/README.md +15 -0
- package/docs/development/architecture.md +44 -0
- package/docs/development/releasing.md +70 -0
- package/docs/development/runtime.md +136 -0
- package/docs/development/skills.md +34 -0
- package/docs/development/testing.md +73 -0
- package/docs/index.html +14 -14
- package/docs/tools/web-search.md +74 -0
- package/package.json +9 -1
- package/skills/pdf/SKILL.md +215 -0
- package/skills/pdf/forms.md +263 -0
- package/skills/pdf/reference.md +179 -0
- package/skills/pdf/scripts/_pdf_form_runtime.mjs +500 -0
- package/skills/pdf/scripts/_pdf_runtime.mjs +212 -0
- package/skills/pdf/scripts/check_bounding_boxes.mjs +96 -0
- package/skills/pdf/scripts/check_fillable_fields.mjs +29 -0
- package/skills/pdf/scripts/create_validation_image.mjs +60 -0
- package/skills/pdf/scripts/extract_form_field_info.mjs +62 -0
- package/skills/pdf/scripts/extract_form_structure.mjs +135 -0
- package/skills/pdf/scripts/extract_pdf_text.mjs +60 -0
- package/skills/pdf/scripts/fill_fillable_fields.mjs +70 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.mjs +88 -0
- package/skills/pdf/scripts/render_pdf_pages.mjs +68 -0
- package/templates/AGENTS.md +4 -3
- package/templates/README.md +13 -0
- 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
|
|
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
|
|
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
|
+
[](https://github.com/HybridAIOne/hybridclaw/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/HybridAIOne/hybridclaw/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@hybridaione/hybridclaw)
|
|
6
|
+
[](https://nodejs.org/en/download)
|
|
7
|
+
[](https://github.com/HybridAIOne/hybridclaw/blob/main/LICENSE)
|
|
8
|
+
[](https://hybridaione.github.io/hybridclaw/)
|
|
9
|
+
[](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
|
-
|
|
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
|
|
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.
|
package/config.example.json
CHANGED
|
@@ -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,
|
package/container/Dockerfile
CHANGED
|
@@ -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
|
|
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(
|
|
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'
|
|
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
|
|
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',
|