@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.
- package/README.md +41 -3
- package/package.json +3 -3
- package/src/app/api/agents/agents-route.test.ts +36 -0
- package/src/app/api/tasks/[id]/retry/route.ts +12 -0
- package/src/app/api/tasks/task-workspace-route.test.ts +62 -0
- package/src/cli/index.js +1 -0
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +1 -0
- package/src/lib/autonomy/supervisor-settings.test.ts +26 -0
- package/src/lib/autonomy/supervisor-settings.ts +27 -0
- package/src/lib/providers/openclaw.test.ts +8 -1
- package/src/lib/providers/openclaw.ts +4 -2
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +206 -0
- package/src/lib/server/autonomy/supervisor-reflection.ts +54 -4
- package/src/lib/server/chat-execution/chat-execution-connector-delivery.ts +17 -1
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +2 -2
- package/src/lib/server/chat-execution/compaction-generation-preference.test.ts +24 -0
- package/src/lib/server/chat-execution/compaction-generation-preference.ts +25 -0
- package/src/lib/server/chat-execution/post-stream-finalization.test.ts +24 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +25 -3
- package/src/lib/server/connectors/connector-inbound.ts +6 -6
- package/src/lib/server/connectors/openclaw.test.ts +9 -2
- package/src/lib/server/connectors/openclaw.ts +1 -1
- package/src/lib/server/memory/dream-generation-preference.ts +30 -2
- package/src/lib/server/memory/dream-service.test.ts +19 -0
- package/src/lib/server/memory/dream-service.ts +3 -1
- package/src/lib/server/memory/memory-consolidation.test.ts +31 -0
- package/src/lib/server/memory/memory-consolidation.ts +3 -2
- package/src/lib/server/memory/memory-db.ts +24 -0
- package/src/lib/server/session-tools/credential-env.ts +4 -3
- package/src/lib/server/session-tools/crud.ts +5 -0
- package/src/lib/server/session-tools/execute.test.ts +29 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +55 -0
- package/src/lib/server/storage-auth.test.ts +102 -0
- package/src/lib/server/storage-auth.ts +104 -16
- package/src/lib/server/tasks/task-route-service.ts +46 -0
- package/src/lib/server/tasks/task-service.test.ts +50 -0
- package/src/lib/server/tasks/task-service.ts +16 -3
- package/src/lib/validation/schemas.ts +12 -0
- package/src/types/agent.ts +1 -0
- package/src/types/app-settings.ts +14 -0
- 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
|
-
|
|
149
|
+
openclaw skills install swarmclaw
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
[Browse on ClawHub](https://clawhub.ai/
|
|
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/
|
|
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.
|
|
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.
|
|
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 }),
|
package/src/cli/index.test.js
CHANGED
|
@@ -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:
|
|
136
|
-
maxProtocol:
|
|
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')
|