clawdbot 2026.1.4-1 → 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.
Files changed (116) hide show
  1. package/CHANGELOG.md +32 -6
  2. package/README.md +26 -1
  3. package/dist/agents/pi-embedded-runner.js +2 -0
  4. package/dist/agents/pi-embedded-subscribe.js +18 -3
  5. package/dist/agents/pi-tools.js +45 -6
  6. package/dist/agents/tools/browser-tool.js +38 -89
  7. package/dist/agents/tools/cron-tool.js +8 -8
  8. package/dist/agents/workspace.js +8 -1
  9. package/dist/auto-reply/command-detection.js +26 -0
  10. package/dist/auto-reply/reply/agent-runner.js +15 -8
  11. package/dist/auto-reply/reply/commands.js +36 -25
  12. package/dist/auto-reply/reply/directive-handling.js +4 -2
  13. package/dist/auto-reply/reply/directives.js +12 -0
  14. package/dist/auto-reply/reply/session-updates.js +2 -4
  15. package/dist/auto-reply/reply.js +26 -4
  16. package/dist/browser/config.js +22 -4
  17. package/dist/browser/profiles-service.js +3 -1
  18. package/dist/browser/profiles.js +14 -3
  19. package/dist/canvas-host/a2ui/.bundle.hash +2 -0
  20. package/dist/cli/gateway-cli.js +2 -2
  21. package/dist/cli/profile.js +81 -0
  22. package/dist/cli/program.js +10 -1
  23. package/dist/cli/run-main.js +33 -0
  24. package/dist/commands/configure.js +5 -0
  25. package/dist/commands/onboard-providers.js +1 -1
  26. package/dist/commands/setup.js +4 -1
  27. package/dist/config/defaults.js +56 -0
  28. package/dist/config/io.js +47 -6
  29. package/dist/config/paths.js +2 -2
  30. package/dist/config/port-defaults.js +32 -0
  31. package/dist/config/sessions.js +3 -2
  32. package/dist/config/validation.js +2 -2
  33. package/dist/config/zod-schema.js +16 -0
  34. package/dist/discord/monitor.js +75 -266
  35. package/dist/entry.js +16 -0
  36. package/dist/gateway/call.js +8 -1
  37. package/dist/gateway/server-methods/chat.js +1 -1
  38. package/dist/gateway/server.js +14 -3
  39. package/dist/index.js +2 -2
  40. package/dist/infra/control-ui-assets.js +118 -0
  41. package/dist/infra/dotenv.js +15 -0
  42. package/dist/infra/shell-env.js +79 -0
  43. package/dist/infra/system-events.js +50 -23
  44. package/dist/macos/relay.js +8 -2
  45. package/dist/telegram/bot.js +24 -1
  46. package/dist/utils.js +8 -2
  47. package/dist/web/auto-reply.js +18 -21
  48. package/dist/web/inbound.js +5 -1
  49. package/dist/web/qr-image.js +4 -4
  50. package/dist/web/session.js +2 -3
  51. package/docs/agent.md +0 -2
  52. package/docs/assets/markdown.css +4 -1
  53. package/docs/audio.md +0 -2
  54. package/docs/clawd.md +0 -2
  55. package/docs/configuration.md +62 -3
  56. package/docs/docs.json +9 -1
  57. package/docs/faq.md +32 -7
  58. package/docs/gateway.md +28 -0
  59. package/docs/images.md +0 -2
  60. package/docs/index.md +2 -4
  61. package/docs/mac/icon.md +1 -1
  62. package/docs/nix.md +57 -11
  63. package/docs/onboarding.md +0 -2
  64. package/docs/refactor/webagent-session.md +0 -2
  65. package/docs/research/memory.md +1 -1
  66. package/docs/skills.md +0 -2
  67. package/docs/templates/AGENTS.md +2 -2
  68. package/docs/tools.md +15 -0
  69. package/docs/whatsapp.md +2 -0
  70. package/package.json +9 -16
  71. package/dist/control-ui/assets/index-BFID3yAA.css +0 -1
  72. package/dist/control-ui/assets/index-CE_axlTS.js +0 -2235
  73. package/dist/control-ui/assets/index-CE_axlTS.js.map +0 -1
  74. package/dist/control-ui/index.html +0 -15
  75. package/dist/daemon/constants.js +0 -10
  76. package/dist/daemon/launchd.js +0 -276
  77. package/dist/daemon/legacy.js +0 -63
  78. package/dist/daemon/program-args.js +0 -76
  79. package/dist/daemon/schtasks.js +0 -257
  80. package/dist/daemon/service.js +0 -60
  81. package/dist/daemon/systemd.js +0 -266
  82. package/dist/imessage/client.js +0 -165
  83. package/dist/imessage/index.js +0 -3
  84. package/dist/imessage/monitor.js +0 -272
  85. package/dist/imessage/probe.js +0 -26
  86. package/dist/imessage/send.js +0 -83
  87. package/dist/imessage/targets.js +0 -176
  88. package/dist/signal/client.js +0 -134
  89. package/dist/signal/daemon.js +0 -69
  90. package/dist/signal/index.js +0 -3
  91. package/dist/signal/monitor.js +0 -336
  92. package/dist/signal/probe.js +0 -46
  93. package/dist/signal/send.js +0 -91
  94. package/dist/slack/actions.js +0 -97
  95. package/dist/slack/index.js +0 -5
  96. package/dist/slack/monitor.js +0 -1029
  97. package/dist/slack/probe.js +0 -47
  98. package/dist/slack/send.js +0 -131
  99. package/dist/slack/token.js +0 -10
  100. package/dist/tui/commands.js +0 -74
  101. package/dist/tui/components/assistant-message.js +0 -16
  102. package/dist/tui/components/chat-log.js +0 -92
  103. package/dist/tui/components/custom-editor.js +0 -53
  104. package/dist/tui/components/selectors.js +0 -8
  105. package/dist/tui/components/tool-execution.js +0 -111
  106. package/dist/tui/components/user-message.js +0 -17
  107. package/dist/tui/gateway-chat.js +0 -140
  108. package/dist/tui/layout.js +0 -41
  109. package/dist/tui/message-list.js +0 -57
  110. package/dist/tui/theme/theme.js +0 -80
  111. package/dist/tui/theme.js +0 -25
  112. package/dist/tui/tui.js +0 -708
  113. package/dist/wizard/clack-prompter.js +0 -56
  114. package/dist/wizard/onboarding.js +0 -452
  115. package/dist/wizard/prompts.js +0 -6
  116. package/dist/wizard/session.js +0 -203
package/CHANGELOG.md CHANGED
@@ -4,19 +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 passes `id` to the gateway for update/remove/run/runs (keeps `jobId` input). (#180) — thanks @adamgall
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
15
-
16
- ## 2026.1.4-1
17
-
18
- ### Fixes
19
- - npm package: include missing `dist/*` runtime modules (fixes `npx clawdbot@latest`).
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).
20
46
 
21
47
 
22
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) · Docker (optional): [`docs/docker.md`](docs/docker.md) · Discord: https://discord.gg/clawd
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.push(text);
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") {
@@ -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
- if (!Array.isArray(schema.anyOf))
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 schema.anyOf) {
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
- ...nextSchema,
227
- type: nextSchema.type ?? "object",
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 sandboxed = sandbox
430
- ? filterToolsByPolicy(filtered, sandbox.tools)
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
- const BrowserToolSchema = Type.Union([
76
- Type.Object({
77
- action: Type.Literal("status"),
78
- controlUrl: Type.Optional(Type.String()),
79
- }),
80
- Type.Object({
81
- action: Type.Literal("start"),
82
- controlUrl: Type.Optional(Type.String()),
83
- }),
84
- Type.Object({
85
- action: Type.Literal("stop"),
86
- controlUrl: Type.Optional(Type.String()),
87
- }),
88
- Type.Object({
89
- action: Type.Literal("tabs"),
90
- controlUrl: Type.Optional(Type.String()),
91
- }),
92
- Type.Object({
93
- action: Type.Literal("open"),
94
- controlUrl: Type.Optional(Type.String()),
95
- targetUrl: Type.String(),
96
- }),
97
- Type.Object({
98
- action: Type.Literal("focus"),
99
- controlUrl: Type.Optional(Type.String()),
100
- targetId: Type.String(),
101
- }),
102
- Type.Object({
103
- action: Type.Literal("close"),
104
- controlUrl: Type.Optional(Type.String()),
105
- targetId: Type.Optional(Type.String()),
106
- }),
107
- Type.Object({
108
- action: Type.Literal("snapshot"),
109
- controlUrl: Type.Optional(Type.String()),
110
- format: Type.Optional(Type.Union([Type.Literal("aria"), Type.Literal("ai")])),
111
- targetId: Type.Optional(Type.String()),
112
- limit: Type.Optional(Type.Number()),
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
- jobId: Type.String(),
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
- jobId: Type.String(),
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
- jobId: Type.String(),
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
- jobId: Type.String(),
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, "jobId", { required: true });
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, "jobId", { required: true });
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, "jobId", { required: true });
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, "jobId", { required: true });
109
+ const id = readStringParam(params, "id", { required: true });
110
110
  return jsonResult(await callGatewayTool("cron.runs", gatewayOpts, { id }));
111
111
  }
112
112
  case "wake": {
@@ -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 const DEFAULT_AGENT_WORKSPACE_DIR = path.join(os.homedir(), "clawd");
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 (!opts?.isHeartbeat && text?.includes("HEARTBEAT_OK")) {
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
- await typing.startTypingOnText(text);
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 (!opts?.isHeartbeat && text?.includes("HEARTBEAT_OK")) {
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
- await typing.startTypingOnText(cleaned);
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 (!opts?.isHeartbeat && text?.includes("HEARTBEAT_OK")) {
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
- await typing.startTypingOnText(text);
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 = opts?.isHeartbeat
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) {