clawdbot 2026.1.4 → 2026.1.5-1
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/CHANGELOG.md +32 -1
- package/README.md +26 -1
- package/dist/agents/pi-embedded-runner.js +2 -0
- package/dist/agents/pi-embedded-subscribe.js +18 -3
- package/dist/agents/pi-tools.js +45 -6
- package/dist/agents/tools/browser-tool.js +38 -89
- package/dist/agents/tools/cron-tool.js +8 -8
- package/dist/agents/workspace.js +8 -1
- package/dist/auto-reply/command-detection.js +26 -0
- package/dist/auto-reply/reply/agent-runner.js +15 -8
- package/dist/auto-reply/reply/commands.js +36 -25
- package/dist/auto-reply/reply/directive-handling.js +4 -2
- package/dist/auto-reply/reply/directives.js +12 -0
- package/dist/auto-reply/reply/session-updates.js +2 -4
- package/dist/auto-reply/reply.js +26 -4
- package/dist/browser/config.js +22 -4
- package/dist/browser/profiles-service.js +3 -1
- package/dist/browser/profiles.js +14 -3
- package/dist/canvas-host/a2ui/.bundle.hash +2 -0
- package/dist/cli/gateway-cli.js +2 -2
- package/dist/cli/profile.js +81 -0
- package/dist/cli/program.js +10 -1
- package/dist/cli/run-main.js +33 -0
- package/dist/commands/configure.js +5 -0
- package/dist/commands/onboard-providers.js +1 -1
- package/dist/commands/setup.js +4 -1
- package/dist/config/defaults.js +56 -0
- package/dist/config/io.js +47 -6
- package/dist/config/paths.js +2 -2
- package/dist/config/port-defaults.js +32 -0
- package/dist/config/sessions.js +3 -2
- package/dist/config/validation.js +2 -2
- package/dist/config/zod-schema.js +16 -0
- package/dist/discord/monitor.js +75 -266
- package/dist/entry.js +16 -0
- package/dist/gateway/call.js +8 -1
- package/dist/gateway/server-methods/chat.js +1 -1
- package/dist/gateway/server.js +14 -3
- package/dist/index.js +2 -2
- package/dist/infra/control-ui-assets.js +118 -0
- package/dist/infra/dotenv.js +15 -0
- package/dist/infra/shell-env.js +79 -0
- package/dist/infra/system-events.js +50 -23
- package/dist/macos/relay.js +8 -2
- package/dist/sessions/send-policy.js +68 -0
- package/dist/telegram/bot.js +24 -1
- package/dist/utils.js +8 -2
- package/dist/web/auto-reply.js +18 -21
- package/dist/web/inbound.js +5 -1
- package/dist/web/qr-image.js +4 -4
- package/dist/web/session.js +2 -3
- package/docs/agent.md +0 -2
- package/docs/assets/markdown.css +4 -1
- package/docs/audio.md +0 -2
- package/docs/clawd.md +0 -2
- package/docs/configuration.md +62 -3
- package/docs/docs.json +9 -1
- package/docs/faq.md +32 -7
- package/docs/gateway.md +28 -0
- package/docs/images.md +0 -2
- package/docs/index.md +2 -4
- package/docs/mac/icon.md +1 -1
- package/docs/nix.md +57 -11
- package/docs/onboarding.md +0 -2
- package/docs/refactor/webagent-session.md +0 -2
- package/docs/research/memory.md +1 -1
- package/docs/skills.md +0 -2
- package/docs/templates/AGENTS.md +2 -2
- package/docs/tools.md +15 -0
- package/docs/whatsapp.md +2 -0
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,45 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
### Fixes
|
|
8
|
+
- Onboarding: resolve CLI entrypoint when running via `npx` so gateway daemon install works without a build step.
|
|
9
|
+
|
|
10
|
+
## 2026.1.5-1
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
- NPM package: include `dist/sessions` so `clawdbot agent` resolves session helpers in npx installs.
|
|
14
|
+
- Node 25: avoid unsupported directory import by targeting `qrcode-terminal/vendor/QRCode/index.js`.
|
|
15
|
+
|
|
16
|
+
## 2026.1.5
|
|
17
|
+
|
|
7
18
|
### Highlights
|
|
8
19
|
- Models: add image-specific model config (`agent.imageModel` + fallbacks) and scan support.
|
|
9
20
|
- Agent tools: new `image` tool routed to the image model (when configured).
|
|
21
|
+
- Config: default model shorthands (`opus`, `sonnet`, `gpt`, `gpt-mini`, `gemini`, `gemini-flash`).
|
|
22
|
+
- Docs: document built-in model shorthands + precedence (user config wins).
|
|
10
23
|
|
|
11
24
|
### Fixes
|
|
25
|
+
- Control UI: render Markdown in tool result cards.
|
|
26
|
+
- Control UI: prevent overlapping action buttons in Discord guild rules on narrow layouts.
|
|
12
27
|
- Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids
|
|
13
|
-
- Cron tool
|
|
28
|
+
- Cron tool uses `id` for update/remove/run/runs (aligns with gateway params). (#180) — thanks @adamgall
|
|
29
|
+
- Control UI: chat view uses page scroll with sticky header/sidebar and fixed composer (no inner scroll frame).
|
|
14
30
|
- macOS: treat location permission as always-only to avoid iOS-only enums. (#165) — thanks @Nachx639
|
|
31
|
+
- macOS: make generated gateway protocol models `Sendable` for Swift 6 strict concurrency. (#195) — thanks @andranik-sahakyan
|
|
32
|
+
- macOS: bundle QR code renderer modules so DMG gateway boot doesn't crash on missing qrcode-terminal vendor files.
|
|
33
|
+
- WhatsApp: suppress typing indicator during heartbeat background tasks. (#190) — thanks @mcinteerj
|
|
34
|
+
- WhatsApp: mark offline history sync messages as read without auto-reply. (#193) — thanks @mcinteerj
|
|
35
|
+
- Discord: avoid duplicate replies when a provider emits late streaming `text_end` events (OpenAI/GPT).
|
|
36
|
+
- CLI: use tailnet IP for local gateway calls when bind is tailnet/auto (fixes #176).
|
|
37
|
+
- Env: load global `$CLAWDBOT_STATE_DIR/.env` (`~/.clawdbot/.env`) as a fallback after CWD `.env`.
|
|
38
|
+
- Env: optional login-shell env fallback (opt-in; imports expected keys without overriding existing env).
|
|
39
|
+
- Agent tools: OpenAI-compatible tool JSON Schemas (fix `browser`, normalize union schemas).
|
|
40
|
+
- Onboarding: when running from source, auto-build missing Control UI assets (`pnpm ui:build`).
|
|
41
|
+
- Discord/Slack: route reaction + system notifications to the correct session (no main-session bleed).
|
|
42
|
+
- Agent tools: honor `agent.tools` allow/deny policy even when sandbox is off.
|
|
43
|
+
- Discord: avoid duplicate replies when OpenAI emits repeated `message_end` events.
|
|
44
|
+
- Commands: unify /status (inline) and command auth across providers; group bypass for authorized control commands; remove Discord /clawd slash handler.
|
|
45
|
+
- Control UI: render Markdown in chat messages (sanitized).
|
|
15
46
|
|
|
16
47
|
|
|
17
48
|
## 2026.1.4
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ It answers you on the surfaces you already use (WhatsApp, Telegram, Discord, iMe
|
|
|
20
20
|
|
|
21
21
|
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
|
22
22
|
|
|
23
|
-
Website: https://clawd.me · Docs: [`docs/index.md`](docs/index.md) · FAQ: [`docs/faq.md`](docs/faq.md) · Wizard: [`docs/wizard.md`](docs/wizard.md) ·
|
|
23
|
+
Website: https://clawd.me · Docs: [`docs/index.md`](docs/index.md) · FAQ: [`docs/faq.md`](docs/faq.md) · Wizard: [`docs/wizard.md`](docs/wizard.md) · Nix: [nix-clawdbot](https://github.com/clawdbot/nix-clawdbot) · Docker: [`docs/docker.md`](docs/docker.md) · Discord: https://discord.gg/clawd
|
|
24
24
|
|
|
25
25
|
Preferred setup: run the onboarding wizard (`clawdbot onboard`). It walks through gateway, workspace, providers, and skills. The CLI wizard is the recommended path and works on **macOS, Windows, and Linux**.
|
|
26
26
|
|
|
@@ -111,6 +111,12 @@ Your surfaces
|
|
|
111
111
|
└─ iOS node (Canvas + voice)
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Skills registry (ClawdHub)
|
|
115
|
+
|
|
116
|
+
ClawdHub is a minimal skill registry. With ClawdHub enabled, the agent can search for skills automatically and pull in new ones as needed.
|
|
117
|
+
|
|
118
|
+
https://clawdhub.com
|
|
119
|
+
|
|
114
120
|
## Quick start (from source)
|
|
115
121
|
|
|
116
122
|
Runtime: **Node ≥22** + **pnpm**.
|
|
@@ -214,6 +220,25 @@ Minimal `~/.clawdbot/clawdbot.json`:
|
|
|
214
220
|
}
|
|
215
221
|
```
|
|
216
222
|
|
|
223
|
+
Env vars: loaded from `.env` in the current working directory, plus a global fallback at `~/.clawdbot/.env` (aka `$CLAWDBOT_STATE_DIR/.env`) without overriding existing values.
|
|
224
|
+
|
|
225
|
+
Optional: import missing keys from your login shell env (sources your shell profile) via config or env var:
|
|
226
|
+
|
|
227
|
+
```json5
|
|
228
|
+
{
|
|
229
|
+
env: {
|
|
230
|
+
shellEnv: {
|
|
231
|
+
enabled: true,
|
|
232
|
+
timeoutMs: 15000
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- Env var: `CLAWDBOT_LOAD_SHELL_ENV=1`
|
|
239
|
+
- Timeout override: `CLAWDBOT_SHELL_ENV_TIMEOUT_MS=15000`
|
|
240
|
+
- Behavior: only imports known/expected keys, never overrides existing `process.env`.
|
|
241
|
+
|
|
217
242
|
### WhatsApp
|
|
218
243
|
|
|
219
244
|
- Link the device: `pnpm clawdbot login` (stores creds in `~/.clawdbot/credentials`).
|
|
@@ -192,6 +192,8 @@ export async function runEmbeddedPiAgent(params) {
|
|
|
192
192
|
const bootstrapFiles = await loadWorkspaceBootstrapFiles(resolvedWorkspace);
|
|
193
193
|
const contextFiles = buildBootstrapContextFiles(bootstrapFiles);
|
|
194
194
|
const promptSkills = resolvePromptSkills(skillsSnapshot, skillEntries);
|
|
195
|
+
// Tool schemas must be provider-compatible (OpenAI requires top-level `type: "object"`).
|
|
196
|
+
// `createClawdbotCodingTools()` normalizes schemas so the session can pass them through unchanged.
|
|
195
197
|
const tools = createClawdbotCodingTools({
|
|
196
198
|
bash: {
|
|
197
199
|
...params.config?.agent?.bash,
|
|
@@ -283,6 +283,19 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
283
283
|
assistantTextBaseline = 0;
|
|
284
284
|
};
|
|
285
285
|
const unsubscribe = params.session.subscribe((evt) => {
|
|
286
|
+
if (evt.type === "message_start") {
|
|
287
|
+
const msg = evt.message;
|
|
288
|
+
if (msg?.role === "assistant") {
|
|
289
|
+
// Start-of-message is a safer reset point than message_end: some providers
|
|
290
|
+
// may deliver late text_end updates after message_end, which would
|
|
291
|
+
// otherwise re-trigger block replies.
|
|
292
|
+
deltaBuffer = "";
|
|
293
|
+
blockBuffer = "";
|
|
294
|
+
lastStreamedAssistant = undefined;
|
|
295
|
+
lastBlockReplyText = undefined;
|
|
296
|
+
assistantTextBaseline = assistantTexts.length;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
286
299
|
if (evt.type === "tool_execution_start") {
|
|
287
300
|
const toolName = String(evt.toolName);
|
|
288
301
|
const toolCallId = String(evt.toolCallId);
|
|
@@ -482,8 +495,11 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
482
495
|
? (extractFinalText(cleaned)?.trim() ?? cleaned)
|
|
483
496
|
: cleaned;
|
|
484
497
|
const addedDuringMessage = assistantTexts.length > assistantTextBaseline;
|
|
485
|
-
if (!addedDuringMessage && text)
|
|
486
|
-
assistantTexts.
|
|
498
|
+
if (!addedDuringMessage && text) {
|
|
499
|
+
const last = assistantTexts.at(-1);
|
|
500
|
+
if (!last || last !== text)
|
|
501
|
+
assistantTexts.push(text);
|
|
502
|
+
}
|
|
487
503
|
assistantTextBaseline = assistantTexts.length;
|
|
488
504
|
if ((blockReplyBreak === "message_end" || blockBuffer.length > 0) &&
|
|
489
505
|
text &&
|
|
@@ -505,7 +521,6 @@ export function subscribeEmbeddedPiSession(params) {
|
|
|
505
521
|
deltaBuffer = "";
|
|
506
522
|
blockBuffer = "";
|
|
507
523
|
lastStreamedAssistant = undefined;
|
|
508
|
-
lastBlockReplyText = undefined;
|
|
509
524
|
}
|
|
510
525
|
}
|
|
511
526
|
if (evt.type === "tool_execution_end") {
|
package/dist/agents/pi-tools.js
CHANGED
|
@@ -171,6 +171,12 @@ function normalizeToolParameters(tool) {
|
|
|
171
171
|
: undefined;
|
|
172
172
|
if (!schema)
|
|
173
173
|
return tool;
|
|
174
|
+
// Provider quirks:
|
|
175
|
+
// - Gemini rejects several JSON Schema keywords, so we scrub those.
|
|
176
|
+
// - OpenAI rejects function tool schemas unless the *top-level* is `type: "object"`.
|
|
177
|
+
// (TypeBox root unions compile to `{ anyOf: [...] }` without `type`).
|
|
178
|
+
//
|
|
179
|
+
// Normalize once here so callers can always pass `tools` through unchanged.
|
|
174
180
|
// If schema already has type + properties (no top-level anyOf to merge),
|
|
175
181
|
// still clean it for Gemini compatibility
|
|
176
182
|
if ("type" in schema &&
|
|
@@ -181,12 +187,29 @@ function normalizeToolParameters(tool) {
|
|
|
181
187
|
parameters: cleanSchemaForGemini(schema),
|
|
182
188
|
};
|
|
183
189
|
}
|
|
184
|
-
|
|
190
|
+
// Some tool schemas (esp. unions) may omit `type` at the top-level. If we see
|
|
191
|
+
// object-ish fields, force `type: "object"` so OpenAI accepts the schema.
|
|
192
|
+
if (!("type" in schema) &&
|
|
193
|
+
(typeof schema.properties === "object" || Array.isArray(schema.required)) &&
|
|
194
|
+
!Array.isArray(schema.anyOf) &&
|
|
195
|
+
!Array.isArray(schema.oneOf)) {
|
|
196
|
+
return {
|
|
197
|
+
...tool,
|
|
198
|
+
parameters: cleanSchemaForGemini({ ...schema, type: "object" }),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const variantKey = Array.isArray(schema.anyOf)
|
|
202
|
+
? "anyOf"
|
|
203
|
+
: Array.isArray(schema.oneOf)
|
|
204
|
+
? "oneOf"
|
|
205
|
+
: null;
|
|
206
|
+
if (!variantKey)
|
|
185
207
|
return tool;
|
|
208
|
+
const variants = schema[variantKey];
|
|
186
209
|
const mergedProperties = {};
|
|
187
210
|
const requiredCounts = new Map();
|
|
188
211
|
let objectVariants = 0;
|
|
189
|
-
for (const entry of
|
|
212
|
+
for (const entry of variants) {
|
|
190
213
|
if (!entry || typeof entry !== "object")
|
|
191
214
|
continue;
|
|
192
215
|
const props = entry.properties;
|
|
@@ -222,9 +245,18 @@ function normalizeToolParameters(tool) {
|
|
|
222
245
|
const nextSchema = { ...schema };
|
|
223
246
|
return {
|
|
224
247
|
...tool,
|
|
248
|
+
// Flatten union schemas into a single object schema:
|
|
249
|
+
// - Gemini doesn't allow top-level `type` together with `anyOf`.
|
|
250
|
+
// - OpenAI rejects schemas without top-level `type: "object"`.
|
|
251
|
+
// Merging properties preserves useful enums like `action` while keeping schemas portable.
|
|
225
252
|
parameters: cleanSchemaForGemini({
|
|
226
|
-
|
|
227
|
-
|
|
253
|
+
type: "object",
|
|
254
|
+
...(typeof nextSchema.title === "string"
|
|
255
|
+
? { title: nextSchema.title }
|
|
256
|
+
: {}),
|
|
257
|
+
...(typeof nextSchema.description === "string"
|
|
258
|
+
? { description: nextSchema.description }
|
|
259
|
+
: {}),
|
|
228
260
|
properties: Object.keys(mergedProperties).length > 0
|
|
229
261
|
? mergedProperties
|
|
230
262
|
: (schema.properties ?? {}),
|
|
@@ -426,8 +458,15 @@ export function createClawdbotCodingTools(options) {
|
|
|
426
458
|
return allowSlack;
|
|
427
459
|
return true;
|
|
428
460
|
});
|
|
429
|
-
const
|
|
430
|
-
|
|
461
|
+
const globallyFiltered = options?.config?.agent?.tools &&
|
|
462
|
+
(options.config.agent.tools.allow?.length ||
|
|
463
|
+
options.config.agent.tools.deny?.length)
|
|
464
|
+
? filterToolsByPolicy(filtered, options.config.agent.tools)
|
|
431
465
|
: filtered;
|
|
466
|
+
const sandboxed = sandbox
|
|
467
|
+
? filterToolsByPolicy(globallyFiltered, sandbox.tools)
|
|
468
|
+
: globallyFiltered;
|
|
469
|
+
// Always normalize tool JSON Schemas before handing them to pi-agent/pi-ai.
|
|
470
|
+
// Without this, some providers (notably OpenAI) will reject root-level union schemas.
|
|
432
471
|
return sandboxed.map(normalizeToolParameters);
|
|
433
472
|
}
|
|
@@ -72,95 +72,44 @@ const BrowserActSchema = Type.Union([
|
|
|
72
72
|
targetId: Type.Optional(Type.String()),
|
|
73
73
|
}),
|
|
74
74
|
]);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Type.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Type.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Type.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}),
|
|
114
|
-
Type.Object({
|
|
115
|
-
action: Type.Literal("screenshot"),
|
|
116
|
-
controlUrl: Type.Optional(Type.String()),
|
|
117
|
-
targetId: Type.Optional(Type.String()),
|
|
118
|
-
fullPage: Type.Optional(Type.Boolean()),
|
|
119
|
-
ref: Type.Optional(Type.String()),
|
|
120
|
-
element: Type.Optional(Type.String()),
|
|
121
|
-
type: Type.Optional(Type.Union([Type.Literal("png"), Type.Literal("jpeg")])),
|
|
122
|
-
}),
|
|
123
|
-
Type.Object({
|
|
124
|
-
action: Type.Literal("navigate"),
|
|
125
|
-
controlUrl: Type.Optional(Type.String()),
|
|
126
|
-
targetUrl: Type.String(),
|
|
127
|
-
targetId: Type.Optional(Type.String()),
|
|
128
|
-
}),
|
|
129
|
-
Type.Object({
|
|
130
|
-
action: Type.Literal("console"),
|
|
131
|
-
controlUrl: Type.Optional(Type.String()),
|
|
132
|
-
level: Type.Optional(Type.String()),
|
|
133
|
-
targetId: Type.Optional(Type.String()),
|
|
134
|
-
}),
|
|
135
|
-
Type.Object({
|
|
136
|
-
action: Type.Literal("pdf"),
|
|
137
|
-
controlUrl: Type.Optional(Type.String()),
|
|
138
|
-
targetId: Type.Optional(Type.String()),
|
|
139
|
-
}),
|
|
140
|
-
Type.Object({
|
|
141
|
-
action: Type.Literal("upload"),
|
|
142
|
-
controlUrl: Type.Optional(Type.String()),
|
|
143
|
-
paths: Type.Array(Type.String()),
|
|
144
|
-
ref: Type.Optional(Type.String()),
|
|
145
|
-
inputRef: Type.Optional(Type.String()),
|
|
146
|
-
element: Type.Optional(Type.String()),
|
|
147
|
-
targetId: Type.Optional(Type.String()),
|
|
148
|
-
timeoutMs: Type.Optional(Type.Number()),
|
|
149
|
-
}),
|
|
150
|
-
Type.Object({
|
|
151
|
-
action: Type.Literal("dialog"),
|
|
152
|
-
controlUrl: Type.Optional(Type.String()),
|
|
153
|
-
accept: Type.Boolean(),
|
|
154
|
-
promptText: Type.Optional(Type.String()),
|
|
155
|
-
targetId: Type.Optional(Type.String()),
|
|
156
|
-
timeoutMs: Type.Optional(Type.Number()),
|
|
157
|
-
}),
|
|
158
|
-
Type.Object({
|
|
159
|
-
action: Type.Literal("act"),
|
|
160
|
-
controlUrl: Type.Optional(Type.String()),
|
|
161
|
-
request: BrowserActSchema,
|
|
162
|
-
}),
|
|
163
|
-
]);
|
|
75
|
+
// IMPORTANT: OpenAI function tool schemas must have a top-level `type: "object"`.
|
|
76
|
+
// A root-level `Type.Union([...])` compiles to `{ anyOf: [...] }` (no `type`),
|
|
77
|
+
// which OpenAI rejects ("Invalid schema ... type: None"). Keep this schema an object.
|
|
78
|
+
const BrowserToolSchema = Type.Object({
|
|
79
|
+
action: Type.Union([
|
|
80
|
+
Type.Literal("status"),
|
|
81
|
+
Type.Literal("start"),
|
|
82
|
+
Type.Literal("stop"),
|
|
83
|
+
Type.Literal("tabs"),
|
|
84
|
+
Type.Literal("open"),
|
|
85
|
+
Type.Literal("focus"),
|
|
86
|
+
Type.Literal("close"),
|
|
87
|
+
Type.Literal("snapshot"),
|
|
88
|
+
Type.Literal("screenshot"),
|
|
89
|
+
Type.Literal("navigate"),
|
|
90
|
+
Type.Literal("console"),
|
|
91
|
+
Type.Literal("pdf"),
|
|
92
|
+
Type.Literal("upload"),
|
|
93
|
+
Type.Literal("dialog"),
|
|
94
|
+
Type.Literal("act"),
|
|
95
|
+
]),
|
|
96
|
+
controlUrl: Type.Optional(Type.String()),
|
|
97
|
+
targetUrl: Type.Optional(Type.String()),
|
|
98
|
+
targetId: Type.Optional(Type.String()),
|
|
99
|
+
limit: Type.Optional(Type.Number()),
|
|
100
|
+
format: Type.Optional(Type.Union([Type.Literal("aria"), Type.Literal("ai")])),
|
|
101
|
+
fullPage: Type.Optional(Type.Boolean()),
|
|
102
|
+
ref: Type.Optional(Type.String()),
|
|
103
|
+
element: Type.Optional(Type.String()),
|
|
104
|
+
type: Type.Optional(Type.Union([Type.Literal("png"), Type.Literal("jpeg")])),
|
|
105
|
+
level: Type.Optional(Type.String()),
|
|
106
|
+
paths: Type.Optional(Type.Array(Type.String())),
|
|
107
|
+
inputRef: Type.Optional(Type.String()),
|
|
108
|
+
timeoutMs: Type.Optional(Type.Number()),
|
|
109
|
+
accept: Type.Optional(Type.Boolean()),
|
|
110
|
+
promptText: Type.Optional(Type.String()),
|
|
111
|
+
request: Type.Optional(BrowserActSchema),
|
|
112
|
+
});
|
|
164
113
|
function resolveBrowserBaseUrl(controlUrl) {
|
|
165
114
|
const cfg = loadConfig();
|
|
166
115
|
const resolved = resolveBrowserConfig(cfg.browser);
|
|
@@ -27,7 +27,7 @@ const CronToolSchema = Type.Union([
|
|
|
27
27
|
gatewayUrl: Type.Optional(Type.String()),
|
|
28
28
|
gatewayToken: Type.Optional(Type.String()),
|
|
29
29
|
timeoutMs: Type.Optional(Type.Number()),
|
|
30
|
-
|
|
30
|
+
id: Type.String(),
|
|
31
31
|
patch: Type.Object({}, { additionalProperties: true }),
|
|
32
32
|
}),
|
|
33
33
|
Type.Object({
|
|
@@ -35,21 +35,21 @@ const CronToolSchema = Type.Union([
|
|
|
35
35
|
gatewayUrl: Type.Optional(Type.String()),
|
|
36
36
|
gatewayToken: Type.Optional(Type.String()),
|
|
37
37
|
timeoutMs: Type.Optional(Type.Number()),
|
|
38
|
-
|
|
38
|
+
id: Type.String(),
|
|
39
39
|
}),
|
|
40
40
|
Type.Object({
|
|
41
41
|
action: Type.Literal("run"),
|
|
42
42
|
gatewayUrl: Type.Optional(Type.String()),
|
|
43
43
|
gatewayToken: Type.Optional(Type.String()),
|
|
44
44
|
timeoutMs: Type.Optional(Type.Number()),
|
|
45
|
-
|
|
45
|
+
id: Type.String(),
|
|
46
46
|
}),
|
|
47
47
|
Type.Object({
|
|
48
48
|
action: Type.Literal("runs"),
|
|
49
49
|
gatewayUrl: Type.Optional(Type.String()),
|
|
50
50
|
gatewayToken: Type.Optional(Type.String()),
|
|
51
51
|
timeoutMs: Type.Optional(Type.Number()),
|
|
52
|
-
|
|
52
|
+
id: Type.String(),
|
|
53
53
|
}),
|
|
54
54
|
Type.Object({
|
|
55
55
|
action: Type.Literal("wake"),
|
|
@@ -88,7 +88,7 @@ export function createCronTool() {
|
|
|
88
88
|
return jsonResult(await callGatewayTool("cron.add", gatewayOpts, params.job));
|
|
89
89
|
}
|
|
90
90
|
case "update": {
|
|
91
|
-
const id = readStringParam(params, "
|
|
91
|
+
const id = readStringParam(params, "id", { required: true });
|
|
92
92
|
if (!params.patch || typeof params.patch !== "object") {
|
|
93
93
|
throw new Error("patch required");
|
|
94
94
|
}
|
|
@@ -98,15 +98,15 @@ export function createCronTool() {
|
|
|
98
98
|
}));
|
|
99
99
|
}
|
|
100
100
|
case "remove": {
|
|
101
|
-
const id = readStringParam(params, "
|
|
101
|
+
const id = readStringParam(params, "id", { required: true });
|
|
102
102
|
return jsonResult(await callGatewayTool("cron.remove", gatewayOpts, { id }));
|
|
103
103
|
}
|
|
104
104
|
case "run": {
|
|
105
|
-
const id = readStringParam(params, "
|
|
105
|
+
const id = readStringParam(params, "id", { required: true });
|
|
106
106
|
return jsonResult(await callGatewayTool("cron.run", gatewayOpts, { id }));
|
|
107
107
|
}
|
|
108
108
|
case "runs": {
|
|
109
|
-
const id = readStringParam(params, "
|
|
109
|
+
const id = readStringParam(params, "id", { required: true });
|
|
110
110
|
return jsonResult(await callGatewayTool("cron.runs", gatewayOpts, { id }));
|
|
111
111
|
}
|
|
112
112
|
case "wake": {
|
package/dist/agents/workspace.js
CHANGED
|
@@ -3,7 +3,14 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { resolveUserPath } from "../utils.js";
|
|
6
|
-
export
|
|
6
|
+
export function resolveDefaultAgentWorkspaceDir(env = process.env, homedir = os.homedir) {
|
|
7
|
+
const profile = env.CLAWDBOT_PROFILE?.trim();
|
|
8
|
+
if (profile && profile.toLowerCase() !== "default") {
|
|
9
|
+
return path.join(homedir(), `clawd-${profile}`);
|
|
10
|
+
}
|
|
11
|
+
return path.join(homedir(), "clawd");
|
|
12
|
+
}
|
|
13
|
+
export const DEFAULT_AGENT_WORKSPACE_DIR = resolveDefaultAgentWorkspaceDir();
|
|
7
14
|
export const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
|
8
15
|
export const DEFAULT_SOUL_FILENAME = "SOUL.md";
|
|
9
16
|
export const DEFAULT_TOOLS_FILENAME = "TOOLS.md";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const CONTROL_COMMAND_RE = /(?:^|\s)\/(?:status|thinking|think|t|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new)(?=$|\s|:)\b/i;
|
|
2
|
+
const CONTROL_COMMAND_EXACT = new Set([
|
|
3
|
+
"status",
|
|
4
|
+
"/status",
|
|
5
|
+
"restart",
|
|
6
|
+
"/restart",
|
|
7
|
+
"activation",
|
|
8
|
+
"/activation",
|
|
9
|
+
"send",
|
|
10
|
+
"/send",
|
|
11
|
+
"reset",
|
|
12
|
+
"/reset",
|
|
13
|
+
"new",
|
|
14
|
+
"/new",
|
|
15
|
+
]);
|
|
16
|
+
export function hasControlCommand(text) {
|
|
17
|
+
if (!text)
|
|
18
|
+
return false;
|
|
19
|
+
const trimmed = text.trim();
|
|
20
|
+
if (!trimmed)
|
|
21
|
+
return false;
|
|
22
|
+
const lowered = trimmed.toLowerCase();
|
|
23
|
+
if (CONTROL_COMMAND_EXACT.has(lowered))
|
|
24
|
+
return true;
|
|
25
|
+
return CONTROL_COMMAND_RE.test(text);
|
|
26
|
+
}
|
|
@@ -15,6 +15,7 @@ import { enqueueFollowupRun, scheduleFollowupDrain, } from "./queue.js";
|
|
|
15
15
|
import { extractReplyToTag } from "./reply-tags.js";
|
|
16
16
|
export async function runReplyAgent(params) {
|
|
17
17
|
const { commandBody, followupRun, queueKey, resolvedQueue, shouldSteer, shouldFollowup, isActive, isStreaming, opts, typing, sessionEntry, sessionStore, sessionKey, storePath, defaultModel, agentCfgContextTokens, resolvedVerboseLevel, isNewSession, blockStreamingEnabled, blockReplyChunking, resolvedBlockStreamingBreak, sessionCtx, shouldInjectGroupIntro, } = params;
|
|
18
|
+
const isHeartbeat = opts?.isHeartbeat === true;
|
|
18
19
|
const shouldEmitToolResult = () => {
|
|
19
20
|
if (!sessionKey || !storePath) {
|
|
20
21
|
return resolvedVerboseLevel === "on";
|
|
@@ -126,7 +127,7 @@ export async function runReplyAgent(params) {
|
|
|
126
127
|
onPartialReply: opts?.onPartialReply
|
|
127
128
|
? async (payload) => {
|
|
128
129
|
let text = payload.text;
|
|
129
|
-
if (!
|
|
130
|
+
if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) {
|
|
130
131
|
const stripped = stripHeartbeatToken(text, {
|
|
131
132
|
mode: "message",
|
|
132
133
|
});
|
|
@@ -140,7 +141,9 @@ export async function runReplyAgent(params) {
|
|
|
140
141
|
}
|
|
141
142
|
text = stripped.text;
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
+
if (!isHeartbeat) {
|
|
145
|
+
await typing.startTypingOnText(text);
|
|
146
|
+
}
|
|
144
147
|
await opts.onPartialReply?.({
|
|
145
148
|
text,
|
|
146
149
|
mediaUrls: payload.mediaUrls,
|
|
@@ -150,7 +153,7 @@ export async function runReplyAgent(params) {
|
|
|
150
153
|
onBlockReply: blockStreamingEnabled && opts?.onBlockReply
|
|
151
154
|
? async (payload) => {
|
|
152
155
|
let text = payload.text;
|
|
153
|
-
if (!
|
|
156
|
+
if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) {
|
|
154
157
|
const stripped = stripHeartbeatToken(text, {
|
|
155
158
|
mode: "message",
|
|
156
159
|
});
|
|
@@ -183,7 +186,9 @@ export async function runReplyAgent(params) {
|
|
|
183
186
|
}
|
|
184
187
|
pendingStreamedPayloadKeys.add(payloadKey);
|
|
185
188
|
const task = (async () => {
|
|
186
|
-
|
|
189
|
+
if (!isHeartbeat) {
|
|
190
|
+
await typing.startTypingOnText(cleaned);
|
|
191
|
+
}
|
|
187
192
|
await opts.onBlockReply?.(blockPayload);
|
|
188
193
|
})()
|
|
189
194
|
.then(() => {
|
|
@@ -204,7 +209,7 @@ export async function runReplyAgent(params) {
|
|
|
204
209
|
onToolResult: opts?.onToolResult
|
|
205
210
|
? async (payload) => {
|
|
206
211
|
let text = payload.text;
|
|
207
|
-
if (!
|
|
212
|
+
if (!isHeartbeat && text?.includes("HEARTBEAT_OK")) {
|
|
208
213
|
const stripped = stripHeartbeatToken(text, {
|
|
209
214
|
mode: "message",
|
|
210
215
|
});
|
|
@@ -218,7 +223,9 @@ export async function runReplyAgent(params) {
|
|
|
218
223
|
}
|
|
219
224
|
text = stripped.text;
|
|
220
225
|
}
|
|
221
|
-
|
|
226
|
+
if (!isHeartbeat) {
|
|
227
|
+
await typing.startTypingOnText(text);
|
|
228
|
+
}
|
|
222
229
|
await opts.onToolResult?.({
|
|
223
230
|
text,
|
|
224
231
|
mediaUrls: payload.mediaUrls,
|
|
@@ -259,7 +266,7 @@ export async function runReplyAgent(params) {
|
|
|
259
266
|
if (pendingBlockTasks.size > 0) {
|
|
260
267
|
await Promise.allSettled(pendingBlockTasks);
|
|
261
268
|
}
|
|
262
|
-
const sanitizedPayloads =
|
|
269
|
+
const sanitizedPayloads = isHeartbeat
|
|
263
270
|
? payloadArray
|
|
264
271
|
: payloadArray.flatMap((payload) => {
|
|
265
272
|
const text = payload.text;
|
|
@@ -305,7 +312,7 @@ export async function runReplyAgent(params) {
|
|
|
305
312
|
return true;
|
|
306
313
|
return false;
|
|
307
314
|
});
|
|
308
|
-
if (shouldSignalTyping) {
|
|
315
|
+
if (shouldSignalTyping && !isHeartbeat) {
|
|
309
316
|
await typing.startTypingLoop();
|
|
310
317
|
}
|
|
311
318
|
if (sessionStore && sessionKey) {
|