@swarmclawai/swarmclaw 1.9.31 → 1.9.33

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 (42) hide show
  1. package/README.md +41 -3
  2. package/package.json +3 -3
  3. package/src/app/api/agents/agents-route.test.ts +36 -0
  4. package/src/app/api/tasks/[id]/retry/route.ts +12 -0
  5. package/src/app/api/tasks/task-workspace-route.test.ts +62 -0
  6. package/src/cli/index.js +1 -0
  7. package/src/cli/index.test.js +30 -0
  8. package/src/cli/spec.js +1 -0
  9. package/src/lib/autonomy/supervisor-settings.test.ts +26 -0
  10. package/src/lib/autonomy/supervisor-settings.ts +27 -0
  11. package/src/lib/providers/openclaw.test.ts +8 -1
  12. package/src/lib/providers/openclaw.ts +4 -2
  13. package/src/lib/server/autonomy/supervisor-reflection.test.ts +206 -0
  14. package/src/lib/server/autonomy/supervisor-reflection.ts +54 -4
  15. package/src/lib/server/chat-execution/chat-execution-connector-delivery.ts +17 -1
  16. package/src/lib/server/chat-execution/chat-turn-finalization.ts +2 -2
  17. package/src/lib/server/chat-execution/compaction-generation-preference.test.ts +24 -0
  18. package/src/lib/server/chat-execution/compaction-generation-preference.ts +25 -0
  19. package/src/lib/server/chat-execution/post-stream-finalization.test.ts +24 -0
  20. package/src/lib/server/chat-execution/stream-agent-chat.ts +25 -3
  21. package/src/lib/server/connectors/connector-inbound.ts +6 -6
  22. package/src/lib/server/connectors/openclaw.test.ts +9 -2
  23. package/src/lib/server/connectors/openclaw.ts +1 -1
  24. package/src/lib/server/memory/dream-generation-preference.ts +30 -2
  25. package/src/lib/server/memory/dream-service.test.ts +19 -0
  26. package/src/lib/server/memory/dream-service.ts +3 -1
  27. package/src/lib/server/memory/memory-consolidation.test.ts +31 -0
  28. package/src/lib/server/memory/memory-consolidation.ts +3 -2
  29. package/src/lib/server/memory/memory-db.ts +24 -0
  30. package/src/lib/server/session-tools/credential-env.ts +4 -3
  31. package/src/lib/server/session-tools/crud.ts +5 -0
  32. package/src/lib/server/session-tools/execute.test.ts +29 -0
  33. package/src/lib/server/session-tools/manage-tasks.test.ts +55 -0
  34. package/src/lib/server/storage-auth.test.ts +102 -0
  35. package/src/lib/server/storage-auth.ts +104 -16
  36. package/src/lib/server/tasks/task-route-service.ts +46 -0
  37. package/src/lib/server/tasks/task-service.test.ts +50 -0
  38. package/src/lib/server/tasks/task-service.ts +16 -3
  39. package/src/lib/validation/schemas.ts +12 -0
  40. package/src/types/agent.ts +1 -0
  41. package/src/types/app-settings.ts +14 -0
  42. package/src/types/dream.ts +8 -0
package/README.md CHANGED
@@ -146,10 +146,29 @@ Then open `http://localhost:3456`.
146
146
  Install the SwarmClaw skill for your [OpenClaw](https://openclaw.ai) agents:
147
147
 
148
148
  ```bash
149
- clawhub install swarmclaw
149
+ openclaw skills install swarmclaw
150
150
  ```
151
151
 
152
- [Browse on ClawHub](https://clawhub.ai/skills/swarmclaw)
152
+ [Browse on ClawHub](https://clawhub.ai/waydelyle/swarmclaw)
153
+
154
+ ## v1.9.33 Highlights
155
+
156
+ Issue and PR validation release for credential durability, delegated task dispatch, connector output hygiene, and OpenClaw gateway protocol compatibility.
157
+
158
+ - **Credential durability.** Execute-tool credential injection now reads the persisted `encryptedKey` field, and `CREDENTIAL_SECRET` now resolves in a stable order: explicit environment value, `DATA_DIR/credential-secret`, legacy env files, then generated fallback.
159
+ - **Delegated task dispatch.** Agent-created tasks delegated to another agent auto-queue when no explicit status is supplied, and failed dead-lettered tasks can be requeued through `POST /api/tasks/:id/retry`.
160
+ - **Connector output hygiene.** Connector replies now reuse the internal metadata scrubber before delivery and persistence, while successful non-connector delivery tool output is no longer overwritten as an unconfirmed send.
161
+ - **Agent and gateway compatibility.** Agent updates preserve workspace filesystem settings, and OpenClaw gateway routes now use protocol version 4.
162
+ - **Regression coverage.** Added tests for credential env injection, secret precedence, delegated queueing, failed-task retry, connector sanitization, agent workspace settings, and OpenClaw gateway protocol exports.
163
+
164
+ ## v1.9.32 Highlights
165
+
166
+ PR integration release for background model routing, reflection memory controls, and current ClawHub install guidance.
167
+
168
+ - **Background model routing.** Per-agent `dreamConfig` overrides can route dream cycles and daily digests before global dream settings, while `compactionProvider` settings can route live auto-compaction summaries through a cheaper or faster model.
169
+ - **Reflection memory controls.** `reflectionMinQuality` gates automatic reflection memory writes without dropping the reflection record, and optional embedding dedup skips near-duplicate reflection notes when embeddings are configured.
170
+ - **ClawHub install guidance.** OpenClaw skill docs now use `openclaw skills install swarmclaw` and current owner-scoped ClawHub links.
171
+ - **Regression coverage.** Added tests for dream override precedence, compaction preference resolution, reflection quality gating, and embedding-based reflection dedup.
153
172
 
154
173
  ## v1.9.31 Highlights
155
174
 
@@ -403,7 +422,7 @@ SwarmClaw agents can join [SwarmFeed](https://swarmfeed.ai) — a social network
403
422
  - **Per-agent opt-in**: enable SwarmFeed on any agent with automatic Ed25519 registration
404
423
  - **Richer in-app surface**: feed tabs for For You, Following, Trending, Bookmarks, and Notifications, plus thread detail, profile sheets, suggested follows, and search
405
424
  - **Heartbeat integration**: agents can auto-post, auto-reply to mentions, auto-follow with guardrails, and publish task-completion updates during heartbeat cycles
406
- - **Multiple access methods**: [SDK](https://www.npmjs.com/package/@swarmfeed/sdk), [CLI](https://www.npmjs.com/package/@swarmfeed/cli), [MCP Server](https://www.npmjs.com/package/@swarmfeed/mcp-server), and [ClawHub skill](https://clawhub.ai/skills/swarmfeed)
425
+ - **Multiple access methods**: [SDK](https://www.npmjs.com/package/@swarmfeed/sdk), [CLI](https://www.npmjs.com/package/@swarmfeed/cli), [MCP Server](https://www.npmjs.com/package/@swarmfeed/mcp-server), and [ClawHub skill](https://clawhub.ai/waydelyle/swarmfeed)
407
426
 
408
427
  Read the docs at [swarmclaw.ai/docs/swarmfeed](https://swarmclaw.ai/docs/swarmfeed) and visit [swarmfeed.ai](https://swarmfeed.ai) for the platform itself.
409
428
 
@@ -426,6 +445,25 @@ Operational docs: https://swarmclaw.ai/docs/observability
426
445
 
427
446
  ## Releases
428
447
 
448
+ ### v1.9.33 Highlights
449
+
450
+ Issue and PR validation release for credential durability, delegated task dispatch, connector output hygiene, and OpenClaw gateway protocol compatibility.
451
+
452
+ - **Credential durability.** Execute-tool credential injection now reads the persisted `encryptedKey` field, and `CREDENTIAL_SECRET` now resolves in a stable order: explicit environment value, `DATA_DIR/credential-secret`, legacy env files, then generated fallback.
453
+ - **Delegated task dispatch.** Agent-created tasks delegated to another agent auto-queue when no explicit status is supplied, and failed dead-lettered tasks can be requeued through `POST /api/tasks/:id/retry`.
454
+ - **Connector output hygiene.** Connector replies now reuse the internal metadata scrubber before delivery and persistence, while successful non-connector delivery tool output is no longer overwritten as an unconfirmed send.
455
+ - **Agent and gateway compatibility.** Agent updates preserve workspace filesystem settings, and OpenClaw gateway routes now use protocol version 4.
456
+ - **Regression coverage.** Added tests for credential env injection, secret precedence, delegated queueing, failed-task retry, connector sanitization, agent workspace settings, and OpenClaw gateway protocol exports.
457
+
458
+ ### v1.9.32 Highlights
459
+
460
+ PR integration release for background model routing, reflection memory controls, and current ClawHub install guidance.
461
+
462
+ - **Background model routing.** Per-agent `dreamConfig` overrides can route dream cycles and daily digests before global dream settings, while `compactionProvider` settings can route live auto-compaction summaries through a cheaper or faster model.
463
+ - **Reflection memory controls.** `reflectionMinQuality` gates automatic reflection memory writes without dropping the reflection record, and optional embedding dedup skips near-duplicate reflection notes when embeddings are configured.
464
+ - **ClawHub install guidance.** OpenClaw skill docs now use `openclaw skills install swarmclaw` and current owner-scoped ClawHub links.
465
+ - **Regression coverage.** Added tests for dream override precedence, compaction preference resolution, reflection quality gating, and embedding-based reflection dedup.
466
+
429
467
  ### v1.9.31 Highlights
430
468
 
431
469
  Documentation cleanup release for public release notes and OpenClaw guidance. No runtime behavior changed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.9.31",
3
+ "version": "1.9.33",
4
4
  "description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
5
5
  "main": "electron-dist/main.js",
6
6
  "license": "MIT",
@@ -88,7 +88,7 @@
88
88
  "test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/electron-signing-config.test.mjs scripts/ensure-sandbox-browser-image.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
89
89
  "test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
90
90
  "test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/gateways/gateway-topology.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/gateways/topology-route.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
91
- "test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/agent-planning-mode.test.ts src/lib/agent-config-history.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/provider-diagnostics.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/memory/dream-service.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/prompt-sections.planning-mode.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/chats/session-context-pack.test.ts src/lib/server/connectors/email.test.ts src/lib/server/connectors/slack.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/runs/run-handoff.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/server/schedules/schedule-timing.test.ts src/lib/server/schedules/schedule-preview.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/server/session-tools/web-crawl.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-pack-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/config-versions/config-versions-route.test.ts src/app/api/runs/run-handoff-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/gateways/control-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/preview/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
91
+ "test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/agent-planning-mode.test.ts src/lib/agent-config-history.test.ts src/lib/autonomy/supervisor-settings.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/autonomy/supervisor-reflection.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/provider-diagnostics.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/memory/dream-service.test.ts src/lib/server/memory/memory-consolidation.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/compaction-generation-preference.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/prompt-sections.planning-mode.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/chats/session-context-pack.test.ts src/lib/server/connectors/email.test.ts src/lib/server/connectors/slack.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/runs/run-handoff.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/server/schedules/schedule-timing.test.ts src/lib/server/schedules/schedule-preview.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/server/session-tools/web-crawl.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-pack-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/config-versions/config-versions-route.test.ts src/app/api/runs/run-handoff-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/gateways/control-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/preview/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
92
92
  "test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
93
93
  "test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
94
94
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
@@ -154,7 +154,7 @@
154
154
  "next": "16.2.4",
155
155
  "next-themes": "^0.4.6",
156
156
  "nodemailer": "^8.0.1",
157
- "openclaw": "^2026.4.22",
157
+ "openclaw": "^2026.5.12",
158
158
  "pdf-parse": "^2.4.5",
159
159
  "qrcode": "^1.5.4",
160
160
  "radix-ui": "^1.4.3",
@@ -206,6 +206,42 @@ test('PUT /api/agents/:id updates planning mode without clobbering other fields'
206
206
  assert.equal(body.proactiveMemory, false)
207
207
  })
208
208
 
209
+ test('PUT /api/agents/:id persists workspace filesystem settings', async () => {
210
+ seedAgent('agent-workspace-settings', {
211
+ name: 'Workspace Agent',
212
+ workspace: null,
213
+ filesystemScope: null,
214
+ fileAccessPolicy: null,
215
+ })
216
+
217
+ const response = await putAgent(new Request('http://local/api/agents/agent-workspace-settings', {
218
+ method: 'PUT',
219
+ headers: { 'content-type': 'application/json' },
220
+ body: JSON.stringify({
221
+ workspace: '/tmp/swarmclaw-agent-workspace',
222
+ filesystemScope: 'workspace',
223
+ fileAccessPolicy: {
224
+ allowedPaths: ['/tmp/swarmclaw-agent-workspace'],
225
+ blockedPaths: ['/tmp/swarmclaw-agent-workspace/private'],
226
+ },
227
+ }),
228
+ }), routeParams('agent-workspace-settings'))
229
+
230
+ assert.equal(response.status, 200)
231
+ const body = await response.json()
232
+ assert.equal(body.workspace, '/tmp/swarmclaw-agent-workspace')
233
+ assert.equal(body.filesystemScope, 'workspace')
234
+ assert.deepEqual(body.fileAccessPolicy, {
235
+ allowedPaths: ['/tmp/swarmclaw-agent-workspace'],
236
+ blockedPaths: ['/tmp/swarmclaw-agent-workspace/private'],
237
+ })
238
+
239
+ const stored = loadAgents()['agent-workspace-settings']
240
+ assert.equal(stored.workspace, '/tmp/swarmclaw-agent-workspace')
241
+ assert.equal(stored.filesystemScope, 'workspace')
242
+ assert.deepEqual(stored.fileAccessPolicy, body.fileAccessPolicy)
243
+ })
244
+
209
245
  test('PUT /api/agents/:id rejects non-string name', async () => {
210
246
  seedAgent('agent-bad-name', { name: 'Good' })
211
247
 
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { notFound } from '@/lib/server/collection-helpers'
3
+ import { retryTaskFromRoute } from '@/lib/server/tasks/task-route-service'
4
+
5
+ export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ const { id } = await params
7
+ const result = retryTaskFromRoute(id)
8
+ if (!result.ok && result.status === 404) return notFound()
9
+ return result.ok
10
+ ? NextResponse.json(result.payload)
11
+ : NextResponse.json(result.payload, { status: result.status })
12
+ }
@@ -19,6 +19,7 @@ let getTaskHandoff: typeof import('./[id]/handoff/route')['GET']
19
19
  let postTaskHandoff: typeof import('./[id]/handoff/route')['POST']
20
20
  let getTaskExecutionPolicy: typeof import('./[id]/execution-policy/route')['GET']
21
21
  let postTaskExecutionPolicy: typeof import('./[id]/execution-policy/route')['POST']
22
+ let postTaskRetry: typeof import('./[id]/retry/route')['POST']
22
23
  let getTaskHandoffs: typeof import('./handoffs/route')['GET']
23
24
  let getTasks: typeof import('./route')['GET']
24
25
  let storage: typeof import('@/lib/server/storage')
@@ -57,6 +58,7 @@ before(async () => {
57
58
  const policyRoute = await import('./[id]/execution-policy/route')
58
59
  getTaskExecutionPolicy = policyRoute.GET
59
60
  postTaskExecutionPolicy = policyRoute.POST
61
+ postTaskRetry = (await import('./[id]/retry/route')).POST
60
62
  getTaskHandoffs = (await import('./handoffs/route')).GET
61
63
  getTasks = (await import('./route')).GET
62
64
  })
@@ -255,6 +257,66 @@ test('GET /api/tasks/:id/execution-policy returns policy summary', async () => {
255
257
  assert.equal(body.summary.status, 'waiting')
256
258
  })
257
259
 
260
+ test('POST /api/tasks/:id/retry requeues a dead-lettered failed task', async () => {
261
+ seedTask('task-dead-letter-retry', {
262
+ title: 'Dead Letter Retry',
263
+ status: 'failed',
264
+ attempts: 3,
265
+ maxAttempts: 3,
266
+ retryScheduledAt: Date.now() + 60_000,
267
+ deadLetteredAt: Date.now(),
268
+ checkoutRunId: 'run-failed',
269
+ error: 'Dead-lettered after 3/3 attempts: timeout',
270
+ validation: { ok: false, reasons: ['No result'], checkedAt: Date.now() },
271
+ })
272
+
273
+ const response = await postTaskRetry(
274
+ new Request('http://local/api/tasks/task-dead-letter-retry/retry', { method: 'POST' }),
275
+ routeParams('task-dead-letter-retry'),
276
+ )
277
+
278
+ assert.equal(response.status, 200)
279
+ const body = await response.json() as BoardTask
280
+ assert.equal(body.status, 'queued')
281
+ assert.equal(body.attempts, 0)
282
+ assert.equal(body.retryScheduledAt, null)
283
+ assert.equal(body.deadLetteredAt, null)
284
+ assert.equal(body.checkoutRunId, null)
285
+ assert.equal(body.error, null)
286
+ assert.equal(body.validation, null)
287
+ assert.equal(storage.loadQueue().includes('task-dead-letter-retry'), true)
288
+ assert.equal(body.comments?.some((comment) => comment.text.includes('retry requested')), true)
289
+ })
290
+
291
+ test('POST /api/tasks/:id/retry rejects tasks still blocked by dependencies', async () => {
292
+ seedTask('task-blocked-retry', {
293
+ title: 'Blocked Retry',
294
+ status: 'failed',
295
+ blockedBy: ['retry-dep'],
296
+ deadLetteredAt: Date.now(),
297
+ })
298
+ const tasks = storage.loadTasks()
299
+ tasks['retry-dep'] = {
300
+ id: 'retry-dep',
301
+ title: 'Retry Dependency',
302
+ description: '',
303
+ status: 'running',
304
+ agentId: 'agent-1',
305
+ createdAt: Date.now(),
306
+ updatedAt: Date.now(),
307
+ } as BoardTask
308
+ storage.saveTasks(tasks)
309
+
310
+ const response = await postTaskRetry(
311
+ new Request('http://local/api/tasks/task-blocked-retry/retry', { method: 'POST' }),
312
+ routeParams('task-blocked-retry'),
313
+ )
314
+
315
+ assert.equal(response.status, 409)
316
+ assert.equal(storage.loadTasks()['task-blocked-retry']?.status, 'failed')
317
+ assert.equal(storage.loadQueue().includes('task-blocked-retry'), false)
318
+ })
319
+
258
320
  test('POST /api/tasks/:id/handoff saves markdown and JSON snapshots into the workspace', async () => {
259
321
  seedTask('task-handoff-save', {
260
322
  title: 'Saved Handoff Task',
package/src/cli/index.js CHANGED
@@ -761,6 +761,7 @@ const COMMAND_GROUPS = [
761
761
  cmd('handoffs', 'GET', '/tasks/handoffs', 'List task handoff readiness packets'),
762
762
  cmd('execution-policy', 'GET', '/tasks/:id/execution-policy', 'Get task execution policy state'),
763
763
  cmd('execution-policy-decision', 'POST', '/tasks/:id/execution-policy', 'Approve, request changes, or reset a task policy stage', { expectsJsonBody: true }),
764
+ cmd('retry', 'POST', '/tasks/:id/retry', 'Retry a failed task and requeue it'),
764
765
  cmd('create', 'POST', '/tasks', 'Create task', { expectsJsonBody: true }),
765
766
  cmd('bulk', 'POST', '/tasks/bulk', 'Bulk update tasks (status/agent/project)', { expectsJsonBody: true }),
766
767
  cmd('update', 'PUT', '/tasks/:id', 'Update task', { expectsJsonBody: true }),
@@ -225,6 +225,36 @@ test('tasks execution-policy-decision posts policy decisions', async () => {
225
225
  assert.equal(stderr.toString(), '')
226
226
  })
227
227
 
228
+ test('tasks retry posts to the failed-task retry endpoint', async () => {
229
+ const stdout = makeWritable()
230
+ const stderr = makeWritable()
231
+ const calls = []
232
+
233
+ const fetchImpl = async (url, init) => {
234
+ calls.push({ url: String(url), init })
235
+ return jsonResponse({ id: 'task-1', status: 'queued' })
236
+ }
237
+
238
+ const exitCode = await runCli(
239
+ ['tasks', 'retry', 'task-1', '--json'],
240
+ {
241
+ fetchImpl,
242
+ stdout,
243
+ stderr,
244
+ env: {},
245
+ cwd: process.cwd(),
246
+ }
247
+ )
248
+
249
+ assert.equal(exitCode, 0)
250
+ assert.equal(calls.length, 1)
251
+ assert.match(calls[0].url, /\/api\/tasks\/task-1\/retry$/)
252
+ assert.equal(calls[0].init.method, 'POST')
253
+ assert.equal(calls[0].init.body, undefined)
254
+ assert.match(stdout.toString(), /"queued"/)
255
+ assert.equal(stderr.toString(), '')
256
+ })
257
+
228
258
  test('gateways drain command posts a lifecycle control action', async () => {
229
259
  const stdout = makeWritable()
230
260
  const stderr = makeWritable()
package/src/cli/spec.js CHANGED
@@ -541,6 +541,7 @@ const COMMAND_GROUPS = {
541
541
  handoffs: { description: 'List task handoff readiness packets', method: 'GET', path: '/tasks/handoffs' },
542
542
  'execution-policy': { description: 'Get task execution policy state', method: 'GET', path: '/tasks/:id/execution-policy', params: ['id'] },
543
543
  'execution-policy-decision': { description: 'Approve, request changes, or reset a task policy stage', method: 'POST', path: '/tasks/:id/execution-policy', params: ['id'] },
544
+ retry: { description: 'Retry a failed task and requeue it', method: 'POST', path: '/tasks/:id/retry', params: ['id'] },
544
545
  create: { description: 'Create task', method: 'POST', path: '/tasks' },
545
546
  bulk: { description: 'Bulk update tasks (status/agent/project)', method: 'POST', path: '/tasks/bulk' },
546
547
  update: { description: 'Update task', method: 'PUT', path: '/tasks/:id', params: ['id'] },
@@ -0,0 +1,26 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+ import { normalizeSupervisorSettings } from './supervisor-settings'
4
+
5
+ describe('normalizeSupervisorSettings', () => {
6
+ it('preserves and clamps reflection memory quality settings', () => {
7
+ assert.deepEqual(
8
+ {
9
+ minQuality: normalizeSupervisorSettings({ reflectionMinQuality: '0.72' }).reflectionMinQuality,
10
+ minQualityHigh: normalizeSupervisorSettings({ reflectionMinQuality: 2 }).reflectionMinQuality,
11
+ minQualityLow: normalizeSupervisorSettings({ reflectionMinQuality: -1 }).reflectionMinQuality,
12
+ semanticEnabled: normalizeSupervisorSettings({ reflectionSemanticDedupEnabled: 'on' }).reflectionSemanticDedupEnabled,
13
+ semanticThreshold: normalizeSupervisorSettings({ reflectionSemanticDedupThreshold: '0.91' }).reflectionSemanticDedupThreshold,
14
+ semanticThresholdHigh: normalizeSupervisorSettings({ reflectionSemanticDedupThreshold: 1.5 }).reflectionSemanticDedupThreshold,
15
+ },
16
+ {
17
+ minQuality: 0.72,
18
+ minQualityHigh: 1,
19
+ minQualityLow: 0,
20
+ semanticEnabled: true,
21
+ semanticThreshold: 0.91,
22
+ semanticThresholdHigh: 1,
23
+ },
24
+ )
25
+ })
26
+ })
@@ -8,6 +8,9 @@ export const DEFAULT_SUPERVISOR_NO_PROGRESS_LIMIT = 2
8
8
  export const DEFAULT_SUPERVISOR_REPEATED_TOOL_LIMIT = 3
9
9
  export const DEFAULT_REFLECTION_ENABLED = true
10
10
  export const DEFAULT_REFLECTION_AUTO_WRITE_MEMORY = true
11
+ export const DEFAULT_REFLECTION_MIN_QUALITY = 0
12
+ export const DEFAULT_REFLECTION_SEMANTIC_DEDUP_ENABLED = false
13
+ export const DEFAULT_REFLECTION_SEMANTIC_DEDUP_THRESHOLD = 0.88
11
14
 
12
15
  export const SUPERVISOR_NO_PROGRESS_LIMIT_MIN = 1
13
16
  export const SUPERVISOR_NO_PROGRESS_LIMIT_MAX = 8
@@ -24,6 +27,16 @@ function parseIntSetting(value: unknown, fallback: number, min: number, max: num
24
27
  return Math.max(min, Math.min(max, Math.trunc(parsed)))
25
28
  }
26
29
 
30
+ function parseNumberSetting(value: unknown, fallback: number, min: number, max: number): number {
31
+ const parsed = typeof value === 'number'
32
+ ? value
33
+ : typeof value === 'string'
34
+ ? Number.parseFloat(value)
35
+ : Number.NaN
36
+ if (!Number.isFinite(parsed)) return fallback
37
+ return Math.max(min, Math.min(max, parsed))
38
+ }
39
+
27
40
  function parseBoolSetting(value: unknown, fallback: boolean): boolean {
28
41
  if (typeof value === 'boolean') return value
29
42
  if (typeof value === 'string') {
@@ -41,6 +54,9 @@ export interface NormalizedSupervisorSettings {
41
54
  supervisorRepeatedToolLimit: number
42
55
  reflectionEnabled: boolean
43
56
  reflectionAutoWriteMemory: boolean
57
+ reflectionMinQuality: number
58
+ reflectionSemanticDedupEnabled: boolean
59
+ reflectionSemanticDedupThreshold: number
44
60
  }
45
61
 
46
62
  export function normalizeSupervisorSettings(
@@ -69,6 +85,17 @@ export function normalizeSupervisorSettings(
69
85
  ),
70
86
  reflectionEnabled: parseBoolSetting(current.reflectionEnabled, DEFAULT_REFLECTION_ENABLED),
71
87
  reflectionAutoWriteMemory: parseBoolSetting(current.reflectionAutoWriteMemory, DEFAULT_REFLECTION_AUTO_WRITE_MEMORY),
88
+ reflectionMinQuality: parseNumberSetting(current.reflectionMinQuality, DEFAULT_REFLECTION_MIN_QUALITY, 0, 1),
89
+ reflectionSemanticDedupEnabled: parseBoolSetting(
90
+ current.reflectionSemanticDedupEnabled,
91
+ DEFAULT_REFLECTION_SEMANTIC_DEDUP_ENABLED,
92
+ ),
93
+ reflectionSemanticDedupThreshold: parseNumberSetting(
94
+ current.reflectionSemanticDedupThreshold,
95
+ DEFAULT_REFLECTION_SEMANTIC_DEDUP_THRESHOLD,
96
+ 0,
97
+ 1,
98
+ ),
72
99
  }
73
100
  }
74
101
 
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { afterEach, test } from 'node:test'
3
3
 
4
- import { buildOpenClawSessionKey, resolveGatewayAgentId } from './openclaw'
4
+ import { buildOpenClawConnectParams, buildOpenClawSessionKey, resolveGatewayAgentId } from './openclaw'
5
5
  import { loadAgents, saveAgents } from '../server/storage'
6
6
  import type { Agent } from '@/types'
7
7
 
@@ -72,3 +72,10 @@ test('buildOpenClawSessionKey honors explicit OpenClaw session keys when provide
72
72
 
73
73
  assert.equal(sessionKey, 'agent:ops:benchmark:fixed-key')
74
74
  })
75
+
76
+ test('buildOpenClawConnectParams advertises the current gateway protocol', () => {
77
+ const params = buildOpenClawConnectParams('test-token', 'test-nonce')
78
+
79
+ assert.equal(params.minProtocol, 4)
80
+ assert.equal(params.maxProtocol, 4)
81
+ })
@@ -113,6 +113,8 @@ export function getDeviceId(): string {
113
113
 
114
114
  // --- Protocol helpers ---
115
115
 
116
+ export const OPENCLAW_GATEWAY_PROTOCOL_VERSION = 4
117
+
116
118
  /**
117
119
  * Build connect params for the OpenClaw gateway protocol.
118
120
  *
@@ -132,8 +134,8 @@ export function buildOpenClawConnectParams(
132
134
  const scopes = ['operator.admin']
133
135
 
134
136
  const params: Record<string, unknown> = {
135
- minProtocol: 3,
136
- maxProtocol: 3,
137
+ minProtocol: OPENCLAW_GATEWAY_PROTOCOL_VERSION,
138
+ maxProtocol: OPENCLAW_GATEWAY_PROTOCOL_VERSION,
137
139
  auth: token ? { token } : undefined,
138
140
  client: {
139
141
  id: clientId,
@@ -202,6 +202,212 @@ describe('supervisor-reflection', () => {
202
202
  ])
203
203
  })
204
204
 
205
+ it('persists low-quality reflections while skipping auto-written memory', () => {
206
+ const output = runWithTempDataDir(`
207
+ const storageMod = await import('@/lib/server/storage')
208
+ const storage = storageMod.default || storageMod['module.exports'] || storageMod
209
+ const reflectionMod = await import('@/lib/server/autonomy/supervisor-reflection')
210
+ const mod = reflectionMod.default || reflectionMod['module.exports'] || reflectionMod
211
+ const memoryDbMod = await import('@/lib/server/memory/memory-db')
212
+ const memoryMod = memoryDbMod.default || memoryDbMod['module.exports'] || memoryDbMod
213
+
214
+ storage.saveAgents({
215
+ 'agent-a': {
216
+ id: 'agent-a',
217
+ name: 'Agent A',
218
+ provider: 'openai',
219
+ model: 'gpt-test',
220
+ },
221
+ })
222
+
223
+ storage.saveSessions({
224
+ s1: {
225
+ id: 's1',
226
+ name: 'Autonomy Session',
227
+ cwd: process.cwd(),
228
+ user: 'tester',
229
+ provider: 'openai',
230
+ model: 'gpt-test',
231
+ claudeSessionId: null,
232
+ messages: [
233
+ { role: 'user', text: 'Repair the deployment workflow and keep notes for later.', time: 1 },
234
+ { role: 'assistant', text: 'I retried the same shell path and nothing changed.', time: 2 },
235
+ ],
236
+ createdAt: 1,
237
+ lastActiveAt: 2,
238
+ sessionType: 'human',
239
+ agentId: 'agent-a',
240
+ },
241
+ })
242
+
243
+ storage.saveSettings({
244
+ supervisorEnabled: true,
245
+ supervisorRuntimeScope: 'both',
246
+ supervisorNoProgressLimit: 2,
247
+ supervisorRepeatedToolLimit: 3,
248
+ reflectionEnabled: true,
249
+ reflectionAutoWriteMemory: true,
250
+ reflectionMinQuality: 0.8,
251
+ })
252
+
253
+ const result = await mod.observeAutonomyRunOutcome({
254
+ runId: 'run-low-quality',
255
+ sessionId: 's1',
256
+ agentId: 'agent-a',
257
+ source: 'chat',
258
+ status: 'completed',
259
+ resultText: 'I retried the same shell path and nothing changed.',
260
+ toolEvents: [
261
+ { name: 'shell', input: '{"cmd":"npm test"}' },
262
+ { name: 'shell', input: '{"cmd":"npm test"}' },
263
+ { name: 'shell', input: '{"cmd":"npm test"}' },
264
+ ],
265
+ mainLoopState: {
266
+ followupChainCount: 2,
267
+ summary: 'I retried the same shell path and nothing changed.',
268
+ },
269
+ sourceMessage: 'Repair the deployment workflow and keep notes for later.',
270
+ }, {
271
+ generateText: async () => JSON.stringify({
272
+ summary: 'Low quality reflection',
273
+ lessons: ['This weak note should not be written to memory.'],
274
+ quality_score: 0.25,
275
+ quality_reasoning: 'Too generic to keep as durable memory.',
276
+ }),
277
+ })
278
+
279
+ const memories = memoryMod.getMemoryDb().list(undefined, 50)
280
+ .filter((entry) => entry.metadata && entry.metadata.origin === 'autonomy-reflection')
281
+
282
+ console.log(JSON.stringify({
283
+ reflectionSummary: result.reflection?.summary ?? null,
284
+ qualityScore: result.reflection?.qualityScore ?? null,
285
+ autoMemoryCount: result.reflection?.autoMemoryIds?.length ?? 0,
286
+ storedReflectionMemoryCount: memories.length,
287
+ }))
288
+ `)
289
+
290
+ assert.equal(output.reflectionSummary, 'Low quality reflection')
291
+ assert.equal(output.qualityScore, 0.25)
292
+ assert.equal(output.autoMemoryCount, 0)
293
+ assert.equal(output.storedReflectionMemoryCount, 0)
294
+ })
295
+
296
+ it('skips semantically duplicate reflection memory when embedding dedup is enabled', () => {
297
+ const output = runWithTempDataDir(`
298
+ const http = await import('node:http')
299
+ const path = await import('node:path')
300
+ const Database = (await import('better-sqlite3')).default
301
+ const storageMod = await import('@/lib/server/storage')
302
+ const storage = storageMod.default || storageMod['module.exports'] || storageMod
303
+ const reflectionMod = await import('@/lib/server/autonomy/supervisor-reflection')
304
+ const mod = reflectionMod.default || reflectionMod['module.exports'] || reflectionMod
305
+ const memoryDbMod = await import('@/lib/server/memory/memory-db')
306
+ const memoryMod = memoryDbMod.default || memoryDbMod['module.exports'] || memoryDbMod
307
+ const settingsRepositoryMod = await import('@/lib/server/settings/settings-repository')
308
+ const settingsRepository = settingsRepositoryMod.default || settingsRepositoryMod['module.exports'] || settingsRepositoryMod
309
+
310
+ const server = http.createServer((req, res) => {
311
+ res.setHeader('content-type', 'application/json')
312
+ res.end(JSON.stringify({ embedding: [1, 0] }))
313
+ })
314
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
315
+ const endpoint = 'http://127.0.0.1:' + server.address().port
316
+
317
+ try {
318
+ storage.saveAgents({
319
+ 'agent-a': {
320
+ id: 'agent-a',
321
+ name: 'Agent A',
322
+ provider: 'openai',
323
+ model: 'gpt-test',
324
+ },
325
+ })
326
+
327
+ storage.saveSessions({
328
+ s1: {
329
+ id: 's1',
330
+ name: 'Semantic Dedup Session',
331
+ cwd: process.cwd(),
332
+ user: 'tester',
333
+ provider: 'openai',
334
+ model: 'gpt-test',
335
+ claudeSessionId: null,
336
+ messages: [
337
+ { role: 'user', text: 'Release carefully and keep the durable lesson.', time: 1 },
338
+ { role: 'assistant', text: 'I checked the release gates before shipping.', time: 2 },
339
+ ],
340
+ createdAt: 1,
341
+ lastActiveAt: 2,
342
+ sessionType: 'human',
343
+ agentId: 'agent-a',
344
+ },
345
+ })
346
+
347
+ const memDb = memoryMod.getMemoryDb()
348
+ const existing = memDb.add({
349
+ agentId: 'agent-a',
350
+ sessionId: 's1',
351
+ category: 'reflection/lesson',
352
+ title: 'Reflection Lesson',
353
+ content: 'Always verify release gates before shipping.',
354
+ metadata: { origin: 'autonomy-reflection' },
355
+ })
356
+ const rawDb = new Database(path.join(process.env.DATA_DIR, 'memory.db'))
357
+ rawDb.prepare('UPDATE memories SET embedding = ? WHERE id = ?').run(Buffer.from(new Float32Array([1, 0]).buffer), existing.id)
358
+ rawDb.close()
359
+
360
+ settingsRepository.saveSettings({
361
+ supervisorEnabled: true,
362
+ supervisorRuntimeScope: 'both',
363
+ supervisorNoProgressLimit: 2,
364
+ supervisorRepeatedToolLimit: 3,
365
+ reflectionEnabled: true,
366
+ reflectionAutoWriteMemory: true,
367
+ reflectionSemanticDedupEnabled: true,
368
+ reflectionSemanticDedupThreshold: 0.9,
369
+ embeddingProvider: 'ollama',
370
+ embeddingModel: 'test-embedding',
371
+ embeddingEndpoint: endpoint,
372
+ })
373
+
374
+ const result = await mod.observeAutonomyRunOutcome({
375
+ runId: 'run-semantic-dedup',
376
+ sessionId: 's1',
377
+ agentId: 'agent-a',
378
+ source: 'chat',
379
+ status: 'completed',
380
+ resultText: 'I checked the release gates before shipping.',
381
+ toolEvents: [
382
+ { name: 'shell', input: '{"cmd":"npm test"}' },
383
+ ],
384
+ sourceMessage: 'Release carefully and keep the durable lesson.',
385
+ }, {
386
+ generateText: async () => JSON.stringify({
387
+ summary: 'Release gate reflection',
388
+ lessons: ['Confirm release gates before shipping.'],
389
+ quality_score: 0.95,
390
+ }),
391
+ })
392
+
393
+ const reflectionMemories = memDb.list('agent-a', 50)
394
+ .filter((entry) => entry.metadata && entry.metadata.origin === 'autonomy-reflection')
395
+
396
+ console.log(JSON.stringify({
397
+ reflectionSummary: result.reflection?.summary ?? null,
398
+ autoMemoryCount: result.reflection?.autoMemoryIds?.length ?? 0,
399
+ reflectionMemoryContents: reflectionMemories.map((entry) => entry.content).sort(),
400
+ }))
401
+ } finally {
402
+ await new Promise((resolve) => server.close(resolve))
403
+ }
404
+ `)
405
+
406
+ assert.equal(output.reflectionSummary, 'Release gate reflection')
407
+ assert.equal(output.autoMemoryCount, 0)
408
+ assert.deepEqual(output.reflectionMemoryContents, ['Always verify release gates before shipping.'])
409
+ })
410
+
205
411
  it('reflects short human chats when they contain durable personal context', () => {
206
412
  const output = runWithTempDataDir(`
207
413
  const storageMod = await import('@/lib/server/storage')