@tyvm/knowhow 0.0.109 → 0.0.110
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/autodoc/README.md +324 -0
- package/autodoc/chat-guide.md +268 -365
- package/autodoc/cli-reference.md +399 -473
- package/autodoc/config-reference.md +431 -330
- package/autodoc/embeddings-guide.md +223 -322
- package/autodoc/generate-guide.md +261 -301
- package/autodoc/language-plugin-guide.md +221 -247
- package/autodoc/modules-guide.md +242 -215
- package/autodoc/plugins-guide.md +470 -469
- package/autodoc/quickstart-guide.md +67 -70
- package/autodoc/skills-guide.md +455 -339
- package/autodoc/worker-guide.md +301 -308
- package/package.json +1 -1
- package/scripts/build-for-node.sh +10 -24
- package/src/agents/tools/list.ts +2 -2
- package/src/ai.ts +81 -37
- package/src/chat/CliChatService.ts +1 -1
- package/src/chat/modules/AgentModule.ts +7 -2
- package/src/chat/modules/SessionsModule.ts +40 -1
- package/src/chat/modules/SystemModule.ts +2 -2
- package/src/clients/anthropic.ts +1 -1
- package/src/clients/index.ts +25 -6
- package/src/clients/openai.ts +8 -5
- package/src/clients/types.ts +29 -6
- package/src/clients/withRetry.ts +89 -0
- package/src/commands/agent.ts +30 -0
- package/src/commands/modules.ts +417 -47
- package/src/config.ts +1 -1
- package/src/fileSync.ts +20 -12
- package/src/hashes.ts +43 -22
- package/src/index.ts +4 -2
- package/src/processors/Base64ImageDetector.ts +73 -0
- package/src/services/MediaProcessorService.ts +79 -10
- package/src/services/modules/index.ts +47 -18
- package/tests/processors/Base64ImageDetector.test.ts +160 -0
- package/tests/unit/clients/AIClient.test.ts +446 -0
- package/tests/unit/clients/withRetry.test.ts +319 -0
- package/tests/unit/commands/github-credentials.test.ts +1 -2
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/tools/list.js +2 -2
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/ai.d.ts +3 -3
- package/ts_build/src/ai.js +51 -23
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +1 -1
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +5 -2
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +30 -1
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/modules/SystemModule.js +2 -2
- package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +1 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/index.js +7 -6
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.js +4 -4
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +12 -6
- package/ts_build/src/clients/withRetry.d.ts +2 -0
- package/ts_build/src/clients/withRetry.js +60 -0
- package/ts_build/src/clients/withRetry.js.map +1 -0
- package/ts_build/src/commands/agent.js +25 -0
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/modules.js +359 -32
- package/ts_build/src/commands/modules.js.map +1 -1
- package/ts_build/src/config.js +1 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/fileSync.d.ts +2 -2
- package/ts_build/src/fileSync.js +13 -11
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/hashes.d.ts +2 -2
- package/ts_build/src/hashes.js +40 -16
- package/ts_build/src/hashes.js.map +1 -1
- package/ts_build/src/index.js +1 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.d.ts +3 -0
- package/ts_build/src/processors/Base64ImageDetector.js +42 -0
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/services/MediaProcessorService.d.ts +5 -4
- package/ts_build/src/services/MediaProcessorService.js +53 -8
- package/ts_build/src/services/MediaProcessorService.js.map +1 -1
- package/ts_build/src/services/modules/index.js +35 -12
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/tests/processors/Base64ImageDetector.test.js +111 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/ts_build/tests/unit/clients/AIClient.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/AIClient.test.js +339 -0
- package/ts_build/tests/unit/clients/AIClient.test.js.map +1 -0
- package/ts_build/tests/unit/clients/withRetry.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/withRetry.test.js +225 -0
- package/ts_build/tests/unit/clients/withRetry.test.js.map +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js +1 -2
- package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -1
package/autodoc/worker-guide.md
CHANGED
|
@@ -1,405 +1,369 @@
|
|
|
1
|
-
# Worker System Guide
|
|
1
|
+
# Knowhow Worker System Guide
|
|
2
2
|
|
|
3
|
-
The **Knowhow worker** is
|
|
4
|
-
|
|
5
|
-
A worker runs a local **MCP server** and keeps a persistent **WebSocket connection** to the Knowhow cloud. The cloud can then invoke the MCP tools that you explicitly allow.
|
|
3
|
+
The **Knowhow worker** is the bridge between your **local machine** and the **Knowhow cloud** (`knowhow.tyvm.ai`). It runs a **local MCP server** that exposes selected tools (including local filesystem/code tooling) to AI agents running in the cloud.
|
|
6
4
|
|
|
7
5
|
---
|
|
8
6
|
|
|
9
|
-
## 1
|
|
7
|
+
## 1. What the worker is
|
|
8
|
+
|
|
9
|
+
When you run `knowhow worker`, Knowhow starts a **local worker process** that:
|
|
10
|
+
|
|
11
|
+
- Loads your local Knowhow configuration from `./.knowhow/knowhow.json`
|
|
12
|
+
- Creates an **MCP (Model Context Protocol) server** locally
|
|
13
|
+
- Connects back to the Knowhow cloud via **WebSockets**
|
|
14
|
+
- Exposes a curated set of **tools** to the cloud so agents can call them
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
### How the worker connects
|
|
17
|
+
The worker opens a WebSocket to:
|
|
12
18
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- Connects to **Knowhow cloud** at `knowhow.tyvm.ai` (via a configured API URL).
|
|
16
|
-
- Advertises only the tools allowed by your `knowhow.json` configuration.
|
|
17
|
-
- Optionally enables:
|
|
18
|
-
- **Sharing/visibility controls**
|
|
19
|
-
- **Tunnel-based port forwarding**
|
|
20
|
-
- **Docker sandbox mode**
|
|
21
|
-
- **Passkey-based locking/unlocking**
|
|
19
|
+
- `https://knowhow.tyvm.ai/ws/worker` (exact base URL comes from `KNOWHOW_API_URL`)
|
|
20
|
+
- Sends an `Authorization: Bearer <JWT>` header and other metadata (like a “Root” header)
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
### Tool exposure model (high-level)
|
|
23
|
+
Tools are gathered from:
|
|
24
|
+
- Built-in agent tools (`./agents/tools/...`)
|
|
25
|
+
- Worker-specific tools (`./workers/tools/...`)
|
|
26
|
+
- MCP tools configured in your Knowhow setup (via configured MCP servers)
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
- Connecting to the cloud WebSocket endpoint: `new WebSocket(`${API_URL}/ws/worker`, { headers })`
|
|
27
|
-
- Running the MCP-over-WebSocket transport: `mcpServer.runWsServer(ws)`
|
|
28
|
+
If passkey auth is enabled (see below), the worker may start **locked** and block all tool usage until unlocked.
|
|
28
29
|
|
|
29
30
|
---
|
|
30
31
|
|
|
31
|
-
## 2
|
|
32
|
+
## 2. `knowhow worker` — starting a worker (what happens on startup)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
At a high level, startup does the following:
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
knowhow
|
|
37
|
-
```
|
|
36
|
+
1. **Load config**
|
|
37
|
+
- Reads `./.knowhow/knowhow.json` via `getConfig()`
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- `--
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
- `
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
- `lock`
|
|
71
|
-
7. Connects to the cloud via WebSockets:
|
|
72
|
-
- `API_URL/ws/worker` for the MCP tool channel
|
|
73
|
-
- Optional `API_URL/ws/tunnel` for the tunnel system
|
|
74
|
-
8. Loops forever, pinging every ~5 seconds, and reconnecting on disconnect.
|
|
39
|
+
2. **Optional security setup / reset**
|
|
40
|
+
- If you pass `--passkey` or `--passkey-reset`, worker runs those flows and exits (it does not start the worker loop).
|
|
41
|
+
|
|
42
|
+
3. **Resolve sandbox mode**
|
|
43
|
+
- If `--sandbox` is set, it runs the worker inside a Docker sandbox container (see section 8).
|
|
44
|
+
- If running *already inside Docker* (`process.env.KNOWHOW_DOCKER === "true"`), sandbox is forced off to avoid nested containers.
|
|
45
|
+
|
|
46
|
+
4. **Define tools and MCP transport**
|
|
47
|
+
- In host mode (not Docker sandbox entrypoint), it:
|
|
48
|
+
- defines tools (`Tools.defineTools(...)`)
|
|
49
|
+
- registers them with the MCP system (`await Mcp.addTools(Tools)`)
|
|
50
|
+
- creates an `McpServerService` and associates a client name: `knowhow-worker`
|
|
51
|
+
|
|
52
|
+
5. **Passkey auth gating (optional)**
|
|
53
|
+
- If `config.worker.auth.passkey.publicKey` and `credentialId` exist:
|
|
54
|
+
- worker starts **locked**
|
|
55
|
+
- it registers auth tools: `unlock`, `lock`
|
|
56
|
+
- it wraps each exposed tool to return `WORKER_LOCKED` when locked
|
|
57
|
+
|
|
58
|
+
6. **Register hot reload tool**
|
|
59
|
+
- It registers `reloadConfig`, enabling agents/tools to hot-reload MCP/tool configuration without restarting the process.
|
|
60
|
+
|
|
61
|
+
7. **Tunnel configuration**
|
|
62
|
+
- It evaluates whether `worker.tunnel.enabled` (or forced tunnel mode) is active (see section 7).
|
|
63
|
+
|
|
64
|
+
8. **Connect/reconnect loop**
|
|
65
|
+
- The worker continuously:
|
|
66
|
+
- loads JWT (`loadJwt()`)
|
|
67
|
+
- reconnects WebSocket
|
|
68
|
+
- pings the connection every 5 seconds
|
|
69
|
+
- pauses reconnection if the JWT is unauthorized (WebSocket close code `1008`)
|
|
75
70
|
|
|
76
71
|
---
|
|
77
72
|
|
|
78
|
-
## 3
|
|
73
|
+
## 3. CLI flags
|
|
79
74
|
|
|
80
|
-
These flags are
|
|
75
|
+
These flags are handled by the worker command entrypoint (`src/worker.ts`).
|
|
81
76
|
|
|
82
77
|
### `--share` / `--unshare` (visibility control)
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
When connecting to the cloud, the worker sets a shared header:
|
|
80
|
+
|
|
81
|
+
- `--share` → `headers.Shared = "true"`
|
|
82
|
+
- `--unshare` → `headers.Shared = "false"`
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
If neither is passed, the worker logs:
|
|
88
85
|
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
- “Worker is private (only you can use it)”
|
|
87
|
+
|
|
88
|
+
**What it means:** shared workers may be usable by others in your organization; unshared workers are private.
|
|
89
|
+
|
|
90
|
+
---
|
|
92
91
|
|
|
93
92
|
### `--sandbox` / `--no-sandbox` (Docker sandbox mode)
|
|
94
93
|
|
|
95
|
-
- `--sandbox`
|
|
96
|
-
- `--no-sandbox`
|
|
94
|
+
- `--sandbox` forces sandbox mode **on**
|
|
95
|
+
- `--no-sandbox` forces sandbox mode **off**
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
The chosen preference is persisted to config:
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
```ts
|
|
100
|
+
worker.sandbox = true | false
|
|
101
|
+
```
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
**Default behavior:** if neither is passed, it uses `config.worker?.sandbox ?? false`.
|
|
104
|
+
|
|
105
|
+
> Note: if the process detects it’s already running inside Docker (`KNOWHOW_DOCKER=true`), it forces sandbox mode off to prevent nested containers.
|
|
106
|
+
|
|
107
|
+
---
|
|
109
108
|
|
|
110
109
|
### `--register` (register worker path)
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
If you run:
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
- `knowhow worker --register`
|
|
114
|
+
|
|
115
|
+
the worker calls `registerWorkerPath(process.cwd())` and exits.
|
|
117
116
|
|
|
118
|
-
|
|
117
|
+
**Purpose:** register the current worker directory path for Knowhow worker discovery/management in the ecosystem.
|
|
118
|
+
|
|
119
|
+
---
|
|
119
120
|
|
|
120
121
|
### `--passkey` / `--passkey-reset` (passkey security setup)
|
|
121
122
|
|
|
122
|
-
- `--passkey`
|
|
123
|
-
-
|
|
123
|
+
- `--passkey-reset`
|
|
124
|
+
- calls `PasskeySetupService.reset()`
|
|
125
|
+
- clears passkey data from worker auth config
|
|
126
|
+
- exits
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
128
|
+
- `--passkey`
|
|
129
|
+
- requires you to be logged in (`knowhow login`)
|
|
130
|
+
- calls `PasskeySetupService.setup(jwt)`
|
|
131
|
+
- exits
|
|
132
|
+
|
|
133
|
+
After passkey setup, the worker can start in a **locked** state and require a passkey assertion to unlock tool access.
|
|
129
134
|
|
|
130
135
|
---
|
|
131
136
|
|
|
132
|
-
## 4
|
|
137
|
+
## 4. `worker.allowedTools` — configuring exposed tools
|
|
138
|
+
|
|
139
|
+
This setting controls **which tools the cloud agent is allowed to call**.
|
|
140
|
+
|
|
141
|
+
### Initial list generation (first run)
|
|
142
|
+
On first run, if:
|
|
143
|
+
- you did **not** pass `--allowedTools`, and
|
|
144
|
+
- there is no existing `config.worker.allowedTools`,
|
|
145
|
+
|
|
146
|
+
then the worker auto-generates the initial allowlist:
|
|
133
147
|
|
|
134
|
-
|
|
148
|
+
- `Tools.getToolNames()`
|
|
149
|
+
- saves it into `./.knowhow/knowhow.json` as:
|
|
135
150
|
|
|
136
|
-
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"worker": {
|
|
154
|
+
"allowedTools": [ ... ]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
- auto-generates it as:
|
|
140
|
-
- `allowedTools: Tools.getToolNames()`
|
|
141
|
-
- writes it to `.knowhow/knowhow.json`
|
|
142
|
-
- prints:
|
|
143
|
-
> “Worker tools configured! Update knowhow.json to adjust which tools are allowed by the worker.”
|
|
144
|
-
- then **exits early** (so you can edit the list before actually serving tools)
|
|
159
|
+
The worker then returns early with a message instructing you to update the config.
|
|
145
160
|
|
|
146
|
-
|
|
161
|
+
---
|
|
147
162
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
3. Start worker again
|
|
163
|
+
### MCP tool naming convention
|
|
164
|
+
MCP tools from configured MCP servers are exposed with names like:
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
- `mcp_0_<server>_<toolname>`
|
|
153
167
|
|
|
154
|
-
|
|
168
|
+
Example:
|
|
169
|
+
- If your MCP server is named `browser` and it has a tool `navigate`,
|
|
170
|
+
the exposed tool name may look like:
|
|
171
|
+
- `mcp_0_browser_navigate`
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
- `mcp_0_<server>_<toolname>`
|
|
173
|
+
(Exact numbering/indices depend on how MCP servers are ordered/registered.)
|
|
158
174
|
|
|
159
|
-
|
|
160
|
-
- built-in worker/tools
|
|
161
|
-
- agent tools
|
|
162
|
-
- configured MCP tools (for example browser automation)
|
|
175
|
+
---
|
|
163
176
|
|
|
164
177
|
### Example `allowedTools` list
|
|
165
|
-
|
|
166
|
-
Example (illustrative):
|
|
178
|
+
Here’s a realistic partial example:
|
|
167
179
|
|
|
168
180
|
```json
|
|
169
181
|
{
|
|
170
182
|
"worker": {
|
|
171
183
|
"allowedTools": [
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
184
|
+
"agents_md_search",
|
|
185
|
+
"exec_run",
|
|
186
|
+
"file_read",
|
|
187
|
+
"file_write",
|
|
188
|
+
"mcp_0_browser_newPage",
|
|
176
189
|
"mcp_0_browser_navigate",
|
|
177
|
-
"
|
|
190
|
+
"reloadConfig"
|
|
178
191
|
]
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
```
|
|
182
195
|
|
|
183
|
-
> Tip:
|
|
196
|
+
> Tip: if you enable passkey auth, the worker also injects auth tools (`unlock`, `lock`) into the allowed tool set at runtime.
|
|
184
197
|
|
|
185
198
|
---
|
|
186
199
|
|
|
187
|
-
## 5
|
|
200
|
+
## 5. Connecting to the cloud
|
|
188
201
|
|
|
189
|
-
After
|
|
202
|
+
After `knowhow login`, the worker retrieves a JWT using `loadJwt()` and connects via WebSockets.
|
|
190
203
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
```
|
|
204
|
+
### WebSocket handshake
|
|
205
|
+
The worker connects to:
|
|
194
206
|
|
|
195
|
-
|
|
207
|
+
- `API_URL + "/ws/worker"`
|
|
196
208
|
|
|
197
|
-
|
|
198
|
-
- `ws://${API_URL}/ws/worker` (API URL is derived from `KNOWHOW_API_URL`)
|
|
199
|
-
- Optional **tunnel channel**:
|
|
200
|
-
- `ws://${API_URL}/ws/tunnel`
|
|
209
|
+
with headers:
|
|
201
210
|
|
|
202
|
-
|
|
211
|
+
- `Authorization: Bearer <JWT>`
|
|
212
|
+
- `User-Agent: knowhow-worker/<version>/<hostname>`
|
|
213
|
+
- `Root: <workspace root path in ~ notation>`
|
|
203
214
|
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
- `
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
-
Reconnect behavior:
|
|
210
|
-
- If the worker WebSocket closes, it logs and reconnects.
|
|
211
|
-
- The worker also periodically pings (`await connection.ws.ping()`), and will reconnect if ping fails.
|
|
215
|
+
### Reconnect behavior
|
|
216
|
+
- The worker runs an infinite loop.
|
|
217
|
+
- If the socket closes with code `1008`, it assumes the JWT is expired:
|
|
218
|
+
- it records the failing JWT in `unauthorizedJwt`
|
|
219
|
+
- it waits for the JWT to change (by reloading it) before retrying
|
|
212
220
|
|
|
213
221
|
---
|
|
214
222
|
|
|
215
|
-
## 6
|
|
223
|
+
## 6. Sharing the worker
|
|
216
224
|
|
|
217
|
-
|
|
218
|
-
- the worker is treated as **private**.
|
|
219
|
-
- With `--share`:
|
|
220
|
-
- the worker advertises `Shared: "true"` and is accessible to others in your organization.
|
|
221
|
-
- With `--unshare`:
|
|
222
|
-
- the worker advertises `Shared: "false"` (explicitly private).
|
|
225
|
+
Use:
|
|
223
226
|
|
|
224
|
-
|
|
227
|
+
- `knowhow worker --share`
|
|
228
|
+
to make the worker accessible to others (organization-level sharing)
|
|
225
229
|
|
|
226
|
-
|
|
230
|
+
- `knowhow worker --unshare`
|
|
231
|
+
to force it back to private mode
|
|
227
232
|
|
|
228
|
-
|
|
233
|
+
This affects the `Shared` header sent during WebSocket connection.
|
|
229
234
|
|
|
230
|
-
|
|
235
|
+
---
|
|
231
236
|
|
|
232
|
-
|
|
237
|
+
## 7. Tunnel system (`worker.tunnel`)
|
|
233
238
|
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
"worker": {
|
|
237
|
-
"tunnel": {
|
|
238
|
-
"enabled": true
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### `allowedPorts`
|
|
239
|
+
The tunnel system provides **port forwarding** so cloud agents can reach services on your local machine through controlled port access.
|
|
245
240
|
|
|
246
|
-
|
|
241
|
+
### Enable tunnel
|
|
242
|
+
In config:
|
|
247
243
|
|
|
248
244
|
```json
|
|
249
245
|
{
|
|
250
246
|
"worker": {
|
|
251
247
|
"tunnel": {
|
|
252
248
|
"enabled": true,
|
|
253
|
-
"allowedPorts": [3000,
|
|
249
|
+
"allowedPorts": [3000, 5173]
|
|
254
250
|
}
|
|
255
251
|
}
|
|
256
252
|
}
|
|
257
253
|
```
|
|
258
254
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
> “Tunnel enabled but no allowedPorts configured. Add tunnel.allowedPorts to knowhow.json”
|
|
255
|
+
### `enabled: true`
|
|
256
|
+
When enabled, the worker also opens a **separate tunnel WebSocket** (in addition to the worker WebSocket).
|
|
262
257
|
|
|
263
|
-
###
|
|
258
|
+
### `allowedPorts`
|
|
259
|
+
- The worker warns if tunnel is enabled but `allowedPorts` is empty.
|
|
260
|
+
- Allowed ports are enforced by the tunnel layer (so you don’t accidentally expose all local ports).
|
|
264
261
|
|
|
265
|
-
|
|
262
|
+
### Tunnel mode forcing
|
|
263
|
+
If you use a tunnel-related workflow (e.g. the CLI passes `allowedTools` as an override from tunnel mode), tunnel is **forced on**:
|
|
266
264
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
- otherwise: uses `127.0.0.1`
|
|
271
|
-
- `worker.tunnel.portMapping`
|
|
272
|
-
- Logged as “Container port → Host port”
|
|
273
|
-
- `worker.tunnel.maxConcurrentStreams` (default 50)
|
|
274
|
-
- `worker.tunnel.enableUrlRewriting` (default enabled)
|
|
275
|
-
- `worker.tunnel.enableUrlRewriting !== false` enables URL rewriting
|
|
276
|
-
- Tunnel URL rewriting is based on either a `secret` or `workerId` in tunnel metadata
|
|
265
|
+
```ts
|
|
266
|
+
const tunnelEnabled = options?.allowedTools ? true : config.worker?.tunnel?.enabled ?? false;
|
|
267
|
+
```
|
|
277
268
|
|
|
278
269
|
---
|
|
279
270
|
|
|
280
|
-
## 8
|
|
281
|
-
|
|
282
|
-
Sandbox mode runs the worker in Docker for isolation.
|
|
283
|
-
|
|
284
|
-
### Enable it
|
|
271
|
+
## 8. Docker sandbox mode (security hardening)
|
|
285
272
|
|
|
286
|
-
|
|
273
|
+
Docker sandbox mode runs the worker inside Docker to isolate filesystem/process access.
|
|
287
274
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
275
|
+
### Enable sandbox via config or flags
|
|
276
|
+
- Config:
|
|
277
|
+
- `worker.sandbox: true`
|
|
278
|
+
- Flag:
|
|
279
|
+
- `knowhow worker --sandbox`
|
|
291
280
|
|
|
292
|
-
|
|
281
|
+
### How it runs
|
|
282
|
+
When sandbox is enabled, the worker:
|
|
293
283
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
284
|
+
1. checks Docker availability
|
|
285
|
+
2. builds a worker image using:
|
|
286
|
+
- `.knowhow/Dockerfile.worker`
|
|
287
|
+
3. runs a Docker container with:
|
|
288
|
+
- `workspaceDir: process.cwd()`
|
|
289
|
+
- JWT + API URL + config passed into the container
|
|
290
|
+
- share/unshare flags passed through
|
|
301
291
|
|
|
302
|
-
###
|
|
292
|
+
### `worker.volumes` / `worker.envFile`
|
|
293
|
+
Your configuration can define how the container mounts and environment variables are provided.
|
|
303
294
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
This guide documents the expected config keys passed into the Docker runner:
|
|
295
|
+
> The exact structure for `worker.volumes` and `worker.envFile` is handled by Docker helper services in the repo (not shown in the excerpt), but the worker code passes `config` into the Docker runner, so those settings live under `worker.*`.
|
|
307
296
|
|
|
297
|
+
**Example (template):**
|
|
308
298
|
```json
|
|
309
299
|
{
|
|
310
300
|
"worker": {
|
|
311
301
|
"sandbox": true,
|
|
312
302
|
"volumes": [
|
|
313
|
-
{ "
|
|
314
|
-
]
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
> The worker code passes the entire `config` into `Docker.runWorkerContainer(...)`, so `worker.volumes` is expected to be consumed by the Docker layer.
|
|
320
|
-
|
|
321
|
-
### Configuration: `worker.envFile`
|
|
322
|
-
|
|
323
|
-
Similarly, you can pass environment variables into the sandboxed container using a file path:
|
|
324
|
-
|
|
325
|
-
```json
|
|
326
|
-
{
|
|
327
|
-
"worker": {
|
|
328
|
-
"sandbox": true,
|
|
329
|
-
"envFile": ".knowhow/worker.env"
|
|
303
|
+
{ "source": "./", "target": "/workspace" }
|
|
304
|
+
],
|
|
305
|
+
"envFile": ".env"
|
|
330
306
|
}
|
|
331
307
|
}
|
|
332
308
|
```
|
|
333
309
|
|
|
334
|
-
> As above, the worker passes `config` through to the Docker runner.
|
|
335
|
-
|
|
336
|
-
### Notes specific to nested containers
|
|
337
|
-
|
|
338
|
-
If you run the worker inside an environment where:
|
|
339
|
-
|
|
340
|
-
- `KNOWHOW_DOCKER=true`
|
|
341
|
-
|
|
342
|
-
then the worker automatically disables sandbox mode (prevents “nested Docker”).
|
|
343
|
-
|
|
344
310
|
---
|
|
345
311
|
|
|
346
|
-
## 9
|
|
312
|
+
## 9. Passkey security
|
|
347
313
|
|
|
348
|
-
Passkey auth protects
|
|
314
|
+
Passkey auth protects the worker so only the owner can unlock tool access.
|
|
349
315
|
|
|
350
316
|
### Setup and reset
|
|
317
|
+
- Setup:
|
|
318
|
+
- `knowhow worker --passkey`
|
|
319
|
+
- Reset:
|
|
320
|
+
- `knowhow worker --passkey-reset`
|
|
351
321
|
|
|
352
|
-
|
|
322
|
+
### How passkeys block unauthorized access
|
|
323
|
+
If passkey config exists in:
|
|
353
324
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
```
|
|
325
|
+
- `config.worker.auth.passkey.publicKey`
|
|
326
|
+
- `config.worker.auth.passkey.credentialId`
|
|
357
327
|
|
|
358
|
-
|
|
328
|
+
then on startup the worker:
|
|
359
329
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
330
|
+
1. creates a `WorkerPasskeyAuthService`
|
|
331
|
+
2. starts **locked**
|
|
332
|
+
3. wraps every configured tool such that:
|
|
333
|
+
- if locked, tool calls return:
|
|
363
334
|
|
|
364
|
-
|
|
335
|
+
- `error: "WORKER_LOCKED"`
|
|
336
|
+
- message instructing to call unlock first
|
|
365
337
|
|
|
366
|
-
|
|
338
|
+
4. registers auth tools:
|
|
339
|
+
- `unlock` and `lock`
|
|
367
340
|
|
|
368
|
-
|
|
369
|
-
|
|
341
|
+
### Unlock flow (tool behavior)
|
|
342
|
+
The worker’s `unlock` tool is a two-step flow:
|
|
370
343
|
|
|
371
|
-
|
|
344
|
+
1. Call `unlock()` **with no parameters**
|
|
345
|
+
→ it returns a `challenge` (and `credentialId`), and you must sign it using WebAuthn in the browser.
|
|
372
346
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
- wraps each configured allowed tool so that when locked it returns:
|
|
347
|
+
2. Call `unlock({ signature, credentialId, authenticatorData, clientDataJSON, challenge })`
|
|
348
|
+
→ it verifies the assertion and unlocks the session.
|
|
376
349
|
|
|
377
|
-
|
|
378
|
-
{
|
|
379
|
-
"error": "WORKER_LOCKED",
|
|
380
|
-
"message": "Worker is locked. Call the `unlock` tool with your passkey assertion to unlock it first."
|
|
381
|
-
}
|
|
382
|
-
```
|
|
350
|
+
There is also a standalone `getChallenge` tool in the codebase (`makeGetChallengeTool`), which is typically used by clients/UI flows, but the worker startup injects `unlock` and `lock` explicitly.
|
|
383
351
|
|
|
384
|
-
###
|
|
352
|
+
### Session duration
|
|
353
|
+
Passkey gating uses:
|
|
385
354
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
- `getChallenge` (returns a challenge string)
|
|
389
|
-
- `unlock` (two-step tool)
|
|
390
|
-
- **Call without assertion fields** → returns a challenge
|
|
391
|
-
- **Call with assertion fields** → verifies assertion and unlocks
|
|
392
|
-
- `lock` (re-locks the worker)
|
|
393
|
-
|
|
394
|
-
**Important behavior:** the wrapper gating applies to your *configured allowed tools*, while the auth tools (`unlock`, `lock`, and the unlock flow challenge) are added so callers can regain access.
|
|
355
|
+
- `config.worker.auth.sessionDurationHours` (defaulted in code to 3 hours if not specified)
|
|
395
356
|
|
|
396
357
|
---
|
|
397
358
|
|
|
398
|
-
## 10
|
|
359
|
+
## 10. Worker in production (systemd / background)
|
|
399
360
|
|
|
400
|
-
|
|
361
|
+
For production-like usage you should:
|
|
362
|
+
- start `knowhow worker` at boot
|
|
363
|
+
- ensure it runs in the correct working directory (the repo/workspace containing `./.knowhow/knowhow.json`)
|
|
364
|
+
- consider enabling sandbox + tunnel only when required
|
|
401
365
|
|
|
402
|
-
### systemd
|
|
366
|
+
### Example: systemd service
|
|
403
367
|
|
|
404
368
|
Create `/etc/systemd/system/knowhow-worker.service`:
|
|
405
369
|
|
|
@@ -411,14 +375,14 @@ Wants=network-online.target
|
|
|
411
375
|
|
|
412
376
|
[Service]
|
|
413
377
|
Type=simple
|
|
414
|
-
WorkingDirectory=/path/to/your/
|
|
415
|
-
ExecStart=/usr/
|
|
378
|
+
WorkingDirectory=/path/to/your/project
|
|
379
|
+
ExecStart=/usr/bin/knowhow worker --no-sandbox
|
|
416
380
|
Restart=always
|
|
417
381
|
RestartSec=5
|
|
418
382
|
Environment=NODE_ENV=production
|
|
419
383
|
|
|
420
|
-
# Optional:
|
|
421
|
-
#
|
|
384
|
+
# Optional: pass share mode
|
|
385
|
+
# ExecStart=/usr/bin/knowhow worker --share --no-sandbox
|
|
422
386
|
|
|
423
387
|
[Install]
|
|
424
388
|
WantedBy=multi-user.target
|
|
@@ -432,95 +396,124 @@ sudo systemctl enable --now knowhow-worker
|
|
|
432
396
|
sudo journalctl -u knowhow-worker -f
|
|
433
397
|
```
|
|
434
398
|
|
|
435
|
-
###
|
|
399
|
+
### Example: keep logs and use sandbox
|
|
436
400
|
|
|
437
|
-
```
|
|
438
|
-
|
|
401
|
+
```ini
|
|
402
|
+
ExecStart=/usr/bin/knowhow worker --sandbox
|
|
439
403
|
```
|
|
440
404
|
|
|
441
405
|
---
|
|
442
406
|
|
|
443
|
-
|
|
407
|
+
# Example worker configuration (`knowhow.json`)
|
|
444
408
|
|
|
445
|
-
|
|
409
|
+
A complete example showing the major worker settings:
|
|
446
410
|
|
|
447
411
|
```json
|
|
448
412
|
{
|
|
449
413
|
"worker": {
|
|
414
|
+
"sandbox": true,
|
|
450
415
|
"allowedTools": [
|
|
451
|
-
"
|
|
452
|
-
"
|
|
453
|
-
"
|
|
416
|
+
"agents_md_search",
|
|
417
|
+
"exec_run",
|
|
418
|
+
"file_read",
|
|
419
|
+
"file_write",
|
|
420
|
+
"mcp_0_browser_newPage",
|
|
454
421
|
"mcp_0_browser_navigate",
|
|
455
|
-
"
|
|
422
|
+
"reloadConfig"
|
|
456
423
|
],
|
|
457
|
-
"
|
|
424
|
+
"workerId": "",
|
|
458
425
|
"tunnel": {
|
|
459
426
|
"enabled": true,
|
|
460
|
-
"allowedPorts": [3000,
|
|
427
|
+
"allowedPorts": [3000, 5173]
|
|
461
428
|
},
|
|
462
429
|
"auth": {
|
|
430
|
+
"sessionDurationHours": 3,
|
|
463
431
|
"passkey": {
|
|
464
|
-
"publicKey": "
|
|
465
|
-
"credentialId": "
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
},
|
|
469
|
-
"volumes": [],
|
|
470
|
-
"envFile": ".knowhow/worker.env"
|
|
432
|
+
"publicKey": "BASE64URL_PUBLIC_KEY",
|
|
433
|
+
"credentialId": "BASE64URL_CREDENTIAL_ID"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
471
436
|
}
|
|
472
437
|
}
|
|
473
438
|
```
|
|
474
439
|
|
|
475
440
|
---
|
|
476
441
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
### Workflow A: Configure allowed tools (safe first run)
|
|
442
|
+
# Example workflows
|
|
480
443
|
|
|
481
|
-
|
|
444
|
+
## Workflow A: First-time setup (generate allowed tools)
|
|
445
|
+
1. `knowhow login`
|
|
446
|
+
2. Run:
|
|
482
447
|
```bash
|
|
483
448
|
knowhow worker
|
|
484
449
|
```
|
|
485
|
-
|
|
486
|
-
|
|
450
|
+
3. On first run, the worker prints a message and auto-writes:
|
|
451
|
+
- `worker.allowedTools = Tools.getToolNames()`
|
|
452
|
+
4. Edit `./.knowhow/knowhow.json` to narrow the allowlist.
|
|
453
|
+
5. Run again:
|
|
487
454
|
```bash
|
|
488
|
-
knowhow worker
|
|
455
|
+
knowhow worker
|
|
489
456
|
```
|
|
490
457
|
|
|
491
|
-
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Workflow B: Share worker with organization
|
|
461
|
+
```bash
|
|
462
|
+
knowhow worker --share
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
This sets the `Shared` header to `true` during WebSocket connection.
|
|
466
|
+
|
|
467
|
+
---
|
|
492
468
|
|
|
493
|
-
|
|
469
|
+
## Workflow C: Enable tunnel for local web apps
|
|
470
|
+
1. Add to config:
|
|
494
471
|
```json
|
|
495
472
|
{
|
|
496
473
|
"worker": {
|
|
497
|
-
"tunnel": {
|
|
474
|
+
"tunnel": {
|
|
475
|
+
"enabled": true,
|
|
476
|
+
"allowedPorts": [3000, 8080]
|
|
477
|
+
}
|
|
498
478
|
}
|
|
499
479
|
}
|
|
500
480
|
```
|
|
501
|
-
2.
|
|
502
|
-
|
|
503
|
-
knowhow worker --share
|
|
504
|
-
```
|
|
505
|
-
3. Your cloud agent can then reach forwarded services via tunnel-generated subdomains (URL rewriting enabled by default).
|
|
481
|
+
2. Restart the worker.
|
|
482
|
+
3. Agents can then reach forwarded ports through the tunnel mechanism.
|
|
506
483
|
|
|
507
|
-
|
|
484
|
+
---
|
|
508
485
|
|
|
509
|
-
|
|
486
|
+
## Workflow D: Lock down worker with a passkey
|
|
487
|
+
1. Ensure logged in:
|
|
510
488
|
```bash
|
|
511
489
|
knowhow login
|
|
512
490
|
```
|
|
513
|
-
2.
|
|
491
|
+
2. Setup passkey:
|
|
514
492
|
```bash
|
|
515
493
|
knowhow worker --passkey
|
|
516
494
|
```
|
|
517
|
-
3.
|
|
518
|
-
4. Start the worker normally (it starts locked):
|
|
495
|
+
3. Start worker normally:
|
|
519
496
|
```bash
|
|
520
497
|
knowhow worker
|
|
521
498
|
```
|
|
522
|
-
|
|
499
|
+
|
|
500
|
+
Agents/tools will be blocked until they perform the `unlock` passkey flow.
|
|
501
|
+
|
|
502
|
+
To remove it:
|
|
503
|
+
```bash
|
|
504
|
+
knowhow worker --passkey-reset
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Workflow E: Production deployment (systemd)
|
|
510
|
+
Use the systemd unit example above, then enable and watch logs:
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
sudo systemctl enable --now knowhow-worker
|
|
514
|
+
sudo journalctl -u knowhow-worker -f
|
|
515
|
+
```
|
|
523
516
|
|
|
524
517
|
---
|
|
525
518
|
|
|
526
|
-
If you
|
|
519
|
+
If you share your current `./.knowhow/knowhow.json` (redact secrets), I can help you produce a safe `worker.allowedTools` allowlist and an example tunnel + sandbox setup tailored to your MCP servers.
|