@swarmclawai/swarmclaw 1.2.0 → 1.2.2
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 +19 -0
- package/package.json +5 -2
- package/skills/coding-agent/SKILL.md +111 -0
- package/skills/github/SKILL.md +140 -0
- package/skills/nano-banana-pro/SKILL.md +62 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
- package/skills/nano-pdf/SKILL.md +53 -0
- package/skills/openai-image-gen/SKILL.md +78 -0
- package/skills/openai-image-gen/scripts/gen.py +328 -0
- package/skills/resourceful-problem-solving/SKILL.md +49 -0
- package/skills/skill-creator/SKILL.md +147 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/quick_validate.py +159 -0
- package/skills/summarize/SKILL.md +77 -0
- package/src/app/api/auth/route.ts +20 -5
- package/src/app/api/chats/[id]/deploy/route.ts +11 -6
- package/src/app/api/chats/[id]/devserver/route.ts +17 -20
- package/src/app/api/chats/[id]/messages/route.ts +15 -11
- package/src/app/api/chats/[id]/route.ts +9 -10
- package/src/app/api/chats/[id]/stop/route.ts +5 -7
- package/src/app/api/chats/messages-route.test.ts +8 -6
- package/src/app/api/chats/route.ts +9 -10
- package/src/app/api/credentials/[id]/route.ts +4 -1
- package/src/app/api/extensions/marketplace/route.ts +5 -2
- package/src/app/api/ip/route.ts +2 -2
- package/src/app/api/memory/maintenance/route.ts +5 -2
- package/src/app/api/preview-server/route.ts +15 -12
- package/src/app/api/projects/[id]/route.ts +7 -46
- package/src/app/api/system/status/route.ts +11 -0
- package/src/app/api/upload/route.ts +4 -1
- package/src/cli/index.js +7 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-files-editor.tsx +44 -32
- package/src/components/agents/personality-builder.tsx +13 -7
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/chat/chat-area.tsx +45 -23
- package/src/components/chat/message-bubble.test.ts +35 -0
- package/src/components/chat/message-bubble.tsx +20 -9
- package/src/components/chat/message-list.tsx +62 -42
- package/src/components/chat/swarm-status-card.tsx +10 -3
- package/src/components/input/chat-input.tsx +34 -14
- package/src/components/layout/daemon-indicator.tsx +7 -8
- package/src/components/layout/update-banner.tsx +8 -13
- package/src/components/logs/log-list.tsx +1 -1
- package/src/components/memory/memory-card.tsx +3 -1
- package/src/components/org-chart/org-chart-view.tsx +4 -0
- package/src/components/projects/project-list.tsx +4 -2
- package/src/components/projects/tabs/overview-tab.tsx +3 -2
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +12 -6
- package/src/components/shared/dir-browser.tsx +22 -18
- package/src/components/skills/skill-sheet.tsx +2 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +1 -1
- package/src/hooks/use-openclaw-gateway.ts +46 -27
- package/src/instrumentation.ts +10 -7
- package/src/lib/chat/assistant-render-id.ts +3 -0
- package/src/lib/chat/chat-streaming-state.test.ts +42 -3
- package/src/lib/chat/chat-streaming-state.ts +20 -8
- package/src/lib/chat/chat.ts +18 -2
- package/src/lib/chat/queued-message-queue.test.ts +23 -1
- package/src/lib/chat/queued-message-queue.ts +11 -2
- package/src/lib/providers/anthropic.ts +6 -3
- package/src/lib/providers/claude-cli.ts +9 -3
- package/src/lib/providers/cli-utils.test.ts +124 -0
- package/src/lib/providers/cli-utils.ts +15 -0
- package/src/lib/providers/codex-cli.ts +9 -3
- package/src/lib/providers/gemini-cli.ts +6 -2
- package/src/lib/providers/index.ts +4 -1
- package/src/lib/providers/ollama.ts +5 -2
- package/src/lib/providers/openai.ts +8 -5
- package/src/lib/providers/opencode-cli.ts +6 -2
- package/src/lib/server/activity/activity-log.ts +21 -0
- package/src/lib/server/agents/agent-availability.test.ts +10 -5
- package/src/lib/server/agents/agent-cascade.ts +79 -59
- package/src/lib/server/agents/agent-registry.ts +23 -4
- package/src/lib/server/agents/agent-repository.ts +90 -0
- package/src/lib/server/agents/delegation-job-repository.ts +53 -0
- package/src/lib/server/agents/delegation-jobs.ts +11 -4
- package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
- package/src/lib/server/agents/guardian.ts +2 -2
- package/src/lib/server/agents/main-agent-loop.ts +14 -6
- package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
- package/src/lib/server/agents/subagent-runtime.ts +9 -6
- package/src/lib/server/agents/subagent-swarm.ts +3 -2
- package/src/lib/server/agents/task-session.ts +3 -4
- package/src/lib/server/approvals/approval-repository.ts +30 -0
- package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
- package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
- package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution.ts +84 -1914
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
- package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
- package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
- package/src/lib/server/chat-execution/message-classifier.ts +5 -2
- package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
- package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
- package/src/lib/server/chat-execution/response-completeness.ts +5 -2
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- package/src/lib/server/connectors/bluebubbles.ts +7 -4
- package/src/lib/server/connectors/connector-inbound.ts +16 -13
- package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
- package/src/lib/server/connectors/connector-outbound.ts +6 -3
- package/src/lib/server/connectors/connector-repository.ts +58 -0
- package/src/lib/server/connectors/discord.ts +10 -7
- package/src/lib/server/connectors/email.ts +17 -14
- package/src/lib/server/connectors/googlechat.ts +7 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
- package/src/lib/server/connectors/matrix.ts +6 -3
- package/src/lib/server/connectors/openclaw.ts +20 -17
- package/src/lib/server/connectors/outbox.ts +4 -1
- package/src/lib/server/connectors/runtime-state.test.ts +117 -0
- package/src/lib/server/connectors/runtime-state.ts +19 -0
- package/src/lib/server/connectors/session-consolidation.ts +5 -2
- package/src/lib/server/connectors/signal.ts +9 -6
- package/src/lib/server/connectors/slack.ts +13 -10
- package/src/lib/server/connectors/teams.ts +8 -5
- package/src/lib/server/connectors/telegram.ts +15 -12
- package/src/lib/server/connectors/whatsapp.ts +32 -29
- package/src/lib/server/credentials/credential-repository.ts +7 -0
- package/src/lib/server/embeddings.ts +4 -1
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/link-understanding.ts +4 -1
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- package/src/lib/server/memory/memory-abstract.ts +59 -0
- package/src/lib/server/memory/memory-db.ts +40 -14
- package/src/lib/server/missions/mission-repository.ts +74 -0
- package/src/lib/server/missions/mission-service/actions.ts +6 -0
- package/src/lib/server/missions/mission-service/bindings.ts +9 -0
- package/src/lib/server/missions/mission-service/context.ts +4 -0
- package/src/lib/server/missions/mission-service/core.ts +2269 -0
- package/src/lib/server/missions/mission-service/queries.ts +12 -0
- package/src/lib/server/missions/mission-service/recovery.ts +5 -0
- package/src/lib/server/missions/mission-service/ticks.ts +9 -0
- package/src/lib/server/missions/mission-service.test.ts +9 -2
- package/src/lib/server/missions/mission-service.ts +6 -2263
- package/src/lib/server/openclaw/gateway.ts +8 -5
- package/src/lib/server/persistence/repository-utils.ts +154 -0
- package/src/lib/server/persistence/storage-context.ts +51 -0
- package/src/lib/server/persistence/transaction.ts +1 -0
- package/src/lib/server/project-utils.ts +13 -0
- package/src/lib/server/projects/project-repository.ts +36 -0
- package/src/lib/server/projects/project-service.ts +79 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
- package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
- package/src/lib/server/provider-health.ts +18 -0
- package/src/lib/server/query-expansion.ts +4 -1
- package/src/lib/server/runtime/alert-dispatch.ts +8 -7
- package/src/lib/server/runtime/daemon-policy.ts +1 -1
- package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
- package/src/lib/server/runtime/daemon-state/health.ts +6 -0
- package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
- package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
- package/src/lib/server/runtime/daemon-state.test.ts +48 -0
- package/src/lib/server/runtime/daemon-state.ts +3 -1331
- package/src/lib/server/runtime/estop-repository.ts +4 -0
- package/src/lib/server/runtime/estop.ts +3 -1
- package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
- package/src/lib/server/runtime/heartbeat-service.ts +78 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +6 -3
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- package/src/lib/server/runtime/perf.ts +4 -1
- package/src/lib/server/runtime/process-manager.ts +7 -4
- package/src/lib/server/runtime/queue/claims.ts +4 -0
- package/src/lib/server/runtime/queue/core.ts +2079 -0
- package/src/lib/server/runtime/queue/execution.ts +7 -0
- package/src/lib/server/runtime/queue/followups.ts +4 -0
- package/src/lib/server/runtime/queue/queries.ts +12 -0
- package/src/lib/server/runtime/queue/recovery.ts +7 -0
- package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
- package/src/lib/server/runtime/queue-repository.ts +17 -0
- package/src/lib/server/runtime/queue.ts +5 -2058
- package/src/lib/server/runtime/run-ledger.ts +6 -5
- package/src/lib/server/runtime/run-repository.ts +73 -0
- package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
- package/src/lib/server/runtime/runtime-settings.ts +1 -1
- package/src/lib/server/runtime/runtime-state.ts +99 -0
- package/src/lib/server/runtime/scheduler.ts +13 -8
- package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
- package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
- package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
- package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
- package/src/lib/server/runtime/session-run-manager.ts +72 -1374
- package/src/lib/server/runtime/watch-job-repository.ts +35 -0
- package/src/lib/server/runtime/watch-jobs.ts +3 -1
- package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
- package/src/lib/server/sandbox/novnc-auth.ts +10 -0
- package/src/lib/server/schedules/schedule-repository.ts +42 -0
- package/src/lib/server/session-tools/context.ts +14 -0
- package/src/lib/server/session-tools/discovery.ts +9 -6
- package/src/lib/server/session-tools/index.ts +3 -1
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/subagent.ts +23 -2
- package/src/lib/server/session-tools/wallet.ts +4 -1
- package/src/lib/server/sessions/session-repository.ts +85 -0
- package/src/lib/server/settings/settings-repository.ts +25 -0
- package/src/lib/server/skills/clawhub-client.ts +4 -1
- package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
- package/src/lib/server/skills/skill-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-eligibility.ts +6 -0
- package/src/lib/server/skills/skill-repository.ts +14 -0
- package/src/lib/server/solana.ts +6 -0
- package/src/lib/server/storage-auth.ts +5 -5
- package/src/lib/server/storage-normalization.ts +4 -0
- package/src/lib/server/storage.ts +32 -32
- package/src/lib/server/tasks/task-followups.ts +4 -1
- package/src/lib/server/tasks/task-repository.ts +54 -0
- package/src/lib/server/tool-loop-detection.ts +8 -3
- package/src/lib/server/tool-planning.ts +226 -0
- package/src/lib/server/tool-retry.ts +4 -3
- package/src/lib/server/usage/usage-repository.ts +30 -0
- package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/server/ws-hub.ts +5 -2
- package/src/lib/strip-internal-metadata.test.ts +78 -37
- package/src/lib/strip-internal-metadata.ts +20 -6
- package/src/stores/use-approval-store.ts +7 -1
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +26 -6
- package/src/types/index.ts +6 -0
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
package/src/lib/server/ws-hub.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { WebSocketServer, WebSocket } from 'ws'
|
|
|
2
2
|
import type { IncomingMessage } from 'http'
|
|
3
3
|
import { validateAccessKey } from './storage'
|
|
4
4
|
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
5
|
+
import { log } from '@/lib/server/logger'
|
|
6
|
+
|
|
7
|
+
const TAG = 'ws-hub'
|
|
5
8
|
|
|
6
9
|
interface WsClient {
|
|
7
10
|
ws: WebSocket
|
|
@@ -67,10 +70,10 @@ export function initWsServer() {
|
|
|
67
70
|
})
|
|
68
71
|
|
|
69
72
|
wss.on('error', (err) => {
|
|
70
|
-
|
|
73
|
+
log.error(TAG, 'WebSocket server error:', err.message)
|
|
71
74
|
})
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
log.info(TAG, `WebSocket server listening on port ${port}`)
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
export function closeWsServer(): Promise<void> {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
2
3
|
import { stripInternalJson, stripLoopDetectionMessages, stripAllInternalMetadata } from './strip-internal-metadata'
|
|
3
4
|
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
@@ -8,37 +9,37 @@ import { stripInternalJson, stripLoopDetectionMessages, stripAllInternalMetadata
|
|
|
8
9
|
describe('stripInternalJson', () => {
|
|
9
10
|
it('removes single-line classification JSON', () => {
|
|
10
11
|
const input = '{ "isDeliverableTask": true, "quality_score": 0.8, "isBroadGoal": false }'
|
|
11
|
-
|
|
12
|
+
assert.equal(stripInternalJson(input).trim(), '')
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
it('removes classification JSON embedded in surrounding text', () => {
|
|
15
16
|
const input = 'Here is the answer.\n{ "isDeliverableTask": true, "confidence": 0.9 }\nMore text follows.'
|
|
16
17
|
const result = stripInternalJson(input)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
assert.match(result, /Here is the answer\./)
|
|
19
|
+
assert.match(result, /More text follows\./)
|
|
20
|
+
assert.doesNotMatch(result, /isDeliverableTask/)
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
it('preserves legitimate JSON that does not contain internal keys', () => {
|
|
23
24
|
const input = 'The config is { "name": "test", "port": 3000 }'
|
|
24
|
-
|
|
25
|
+
assert.equal(stripInternalJson(input), input)
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
it('preserves JSON with nested objects if no internal keys', () => {
|
|
28
29
|
const input = '{ "user": { "name": "alice" } }'
|
|
29
|
-
|
|
30
|
+
assert.equal(stripInternalJson(input), input)
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
it('removes JSON with nested objects when internal keys are present', () => {
|
|
33
34
|
const input = '{ "walletIntent": "send", "details": { "amount": 100 } }'
|
|
34
|
-
|
|
35
|
+
assert.equal(stripInternalJson(input).trim(), '')
|
|
35
36
|
})
|
|
36
37
|
|
|
37
38
|
it('handles multiple JSON blocks, only removing internal ones', () => {
|
|
38
39
|
const input = '{ "isDeliverableTask": true } some text { "foo": "bar" }'
|
|
39
40
|
const result = stripInternalJson(input)
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
assert.doesNotMatch(result, /isDeliverableTask/)
|
|
42
|
+
assert.match(result, /\{ "foo": "bar" \}/)
|
|
42
43
|
})
|
|
43
44
|
})
|
|
44
45
|
|
|
@@ -49,70 +50,110 @@ describe('stripInternalJson', () => {
|
|
|
49
50
|
describe('stripLoopDetectionMessages', () => {
|
|
50
51
|
it('strips tool frequency "called N times" messages', () => {
|
|
51
52
|
const input = 'Tool "shell" called 30 times this turn. Excessive repetition — wrap up with available results.'
|
|
52
|
-
|
|
53
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
53
54
|
})
|
|
54
55
|
|
|
55
56
|
it('strips tool frequency "would be called" messages', () => {
|
|
56
57
|
const input = 'Tool "shell" would be called 31 times this turn. Excessive repetition — wrap up with available results.'
|
|
57
|
-
|
|
58
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
it('strips "nearing overuse" messages', () => {
|
|
61
62
|
const input = 'Tool "read" is nearing overuse (15 calls this turn). Consider whether another call is needed.'
|
|
62
|
-
|
|
63
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
63
64
|
})
|
|
64
65
|
|
|
65
|
-
it('strips generic repeat "
|
|
66
|
-
const input = '
|
|
67
|
-
|
|
66
|
+
it('strips generic repeat "You called" messages', () => {
|
|
67
|
+
const input = 'You called "browser" 6 times with identical input. Input: "{"action":"screenshot"}" — State your blocker or deliver what you have.'
|
|
68
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('strips generic repeat "You called" warning messages', () => {
|
|
72
|
+
const input = 'You called "search" 4 times with identical input. Input: "query" — Try a fundamentally different approach or deliver partial results.'
|
|
73
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
68
74
|
})
|
|
69
75
|
|
|
70
76
|
it('strips "would repeat the same input" messages', () => {
|
|
71
|
-
const input = '
|
|
72
|
-
|
|
77
|
+
const input = '"search" would repeat the same input 12 times. Input: "query" — State your blocker or deliver what you have.'
|
|
78
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
73
79
|
})
|
|
74
80
|
|
|
75
81
|
it('strips "is about to repeat the same input" messages', () => {
|
|
76
|
-
const input = '
|
|
77
|
-
|
|
82
|
+
const input = '"search" is about to repeat the same input 6 times. Input: "query" — Try a different approach.'
|
|
83
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
78
84
|
})
|
|
79
85
|
|
|
80
86
|
it('strips circuit breaker messages', () => {
|
|
81
87
|
const input = 'Circuit breaker: "shell" called 20 times with identical input. Halting to prevent runaway.'
|
|
82
|
-
|
|
88
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
83
89
|
})
|
|
84
90
|
|
|
85
91
|
it('strips circuit breaker preview messages', () => {
|
|
86
92
|
const input = 'Circuit breaker: "shell" would be called 20 times with identical input. Halting before another runaway call.'
|
|
87
|
-
|
|
93
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
88
94
|
})
|
|
89
95
|
|
|
90
96
|
it('strips polling stall messages', () => {
|
|
91
97
|
const input = 'Polling stall: "status_check" returned identical output 8 times consecutively. The polled resource is not changing.'
|
|
92
|
-
|
|
98
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
93
99
|
})
|
|
94
100
|
|
|
95
101
|
it('strips ping-pong "are alternating" messages', () => {
|
|
96
102
|
const input = 'Ping-pong: "read" and "write" are alternating with identical results (5 cycles). Breaking the loop.'
|
|
97
|
-
|
|
103
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
98
104
|
})
|
|
99
105
|
|
|
100
106
|
it('strips ping-pong "may be stuck" messages', () => {
|
|
101
107
|
const input = 'Ping-pong: "read" and "write" may be stuck in an alternating loop (3 cycles).'
|
|
102
|
-
|
|
108
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('strips output stagnation messages', () => {
|
|
112
|
+
const input = 'Output stagnation: last 8 tool calls all produced identical output. The approach is not working — try something fundamentally different or report the blocker.'
|
|
113
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('strips output stagnation warning messages', () => {
|
|
117
|
+
const input = 'Output stagnation: 6 of the last 8 tool calls produced identical output. Your tools may not be making progress.'
|
|
118
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('strips error convergence messages', () => {
|
|
122
|
+
const input = 'Error convergence: 5 of the last 6 tool calls returned errors. Stop retrying and report the underlying issue (likely an infrastructure or configuration problem).'
|
|
123
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('strips error convergence warning messages', () => {
|
|
127
|
+
const input = 'Error convergence: 4 of the last 6 tool calls returned errors. You may be hitting a systemic issue — consider a different approach or report the blocker.'
|
|
128
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('strips messages wrapped in [Error: ...] brackets', () => {
|
|
132
|
+
const input = '[Error: You called "browser" 6 times with identical input. Input: "{"action":"screenshot"}" — State your blocker or deliver what you have.]'
|
|
133
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('strips [Error: ...] wrapped tool frequency messages', () => {
|
|
137
|
+
const input = '[Error: Tool "shell" called 30 times this turn. Excessive repetition — wrap up with available results.]'
|
|
138
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('strips [Error: ...] wrapped output stagnation messages', () => {
|
|
142
|
+
const input = '[Error: Output stagnation: last 8 tool calls all produced identical output.]'
|
|
143
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
103
144
|
})
|
|
104
145
|
|
|
105
146
|
it('preserves normal text that mentions tools', () => {
|
|
106
147
|
const input = 'I used the shell tool to run the command.'
|
|
107
|
-
|
|
148
|
+
assert.equal(stripLoopDetectionMessages(input), input)
|
|
108
149
|
})
|
|
109
150
|
|
|
110
151
|
it('strips loop message embedded in surrounding text', () => {
|
|
111
152
|
const input = 'Working on it.\nTool "shell" called 30 times this turn. Excessive repetition — wrap up with available results.\nHere are the results.'
|
|
112
153
|
const result = stripLoopDetectionMessages(input)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
154
|
+
assert.match(result, /Working on it\./)
|
|
155
|
+
assert.match(result, /Here are the results\./)
|
|
156
|
+
assert.doesNotMatch(result, /called 30 times/)
|
|
116
157
|
})
|
|
117
158
|
})
|
|
118
159
|
|
|
@@ -129,32 +170,32 @@ describe('stripAllInternalMetadata', () => {
|
|
|
129
170
|
'The answer is 42.',
|
|
130
171
|
].join('\n')
|
|
131
172
|
const result = stripAllInternalMetadata(input)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
assert.doesNotMatch(result, /isDeliverableTask/)
|
|
174
|
+
assert.doesNotMatch(result, /called 30 times/)
|
|
175
|
+
assert.match(result, /Here is my analysis\./)
|
|
176
|
+
assert.match(result, /The answer is 42\./)
|
|
136
177
|
})
|
|
137
178
|
|
|
138
179
|
it('collapses excessive newlines after stripping', () => {
|
|
139
180
|
const input = 'Hello.\n\n\n\n{ "isDeliverableTask": true }\n\n\n\nWorld.'
|
|
140
181
|
const result = stripAllInternalMetadata(input)
|
|
141
|
-
|
|
182
|
+
assert.doesNotMatch(result, /\n{3,}/)
|
|
142
183
|
})
|
|
143
184
|
|
|
144
185
|
it('returns empty string for purely internal content', () => {
|
|
145
186
|
const input = '{ "isDeliverableTask": true, "quality_score": 0.9 }'
|
|
146
|
-
|
|
187
|
+
assert.equal(stripAllInternalMetadata(input), '')
|
|
147
188
|
})
|
|
148
189
|
|
|
149
190
|
it('leaves normal messages untouched', () => {
|
|
150
191
|
const input = 'Here is a normal response with no internal metadata.'
|
|
151
|
-
|
|
192
|
+
assert.equal(stripAllInternalMetadata(input), input)
|
|
152
193
|
})
|
|
153
194
|
|
|
154
195
|
it('preserves code blocks with JSON that happen to have similar-looking keys', () => {
|
|
155
196
|
// JSON in code blocks uses real braces, so the regex will match the block.
|
|
156
197
|
// But since 'name' and 'age' are not internal keys, it should be preserved.
|
|
157
198
|
const input = 'Result: { "name": "Alice", "age": 30 }'
|
|
158
|
-
|
|
199
|
+
assert.equal(stripAllInternalMetadata(input), input)
|
|
159
200
|
})
|
|
160
201
|
})
|
|
@@ -43,35 +43,49 @@ export function stripInternalJson(text: string): string {
|
|
|
43
43
|
* - Tool "X" called N times ...
|
|
44
44
|
* - Tool "X" would be called N times ...
|
|
45
45
|
* - Tool "X" is nearing overuse ...
|
|
46
|
-
* -
|
|
47
|
-
* -
|
|
48
|
-
* -
|
|
46
|
+
* - You called "X" N times with identical input ...
|
|
47
|
+
* - "X" would repeat the same input N times ...
|
|
48
|
+
* - "X" is about to repeat the same input N times ...
|
|
49
49
|
* - Circuit breaker: "X" called N times ...
|
|
50
50
|
* - Circuit breaker: "X" would be called N times ...
|
|
51
51
|
* - Polling stall: "X" returned identical output N times ...
|
|
52
52
|
* - Ping-pong: "X" and "Y" are alternating ...
|
|
53
53
|
* - Ping-pong: "X" and "Y" may be stuck ...
|
|
54
|
+
* - Output stagnation: last N / N of the last N ...
|
|
55
|
+
* - Error convergence: N of the last N ...
|
|
54
56
|
*/
|
|
55
57
|
const LOOP_DETECTION_RE = new RegExp(
|
|
56
58
|
[
|
|
57
59
|
// Tool frequency: called / would be called / nearing overuse
|
|
58
60
|
String.raw`Tool "[^"]*" (?:called|would be called) \d+ times[^\n]*`,
|
|
59
61
|
String.raw`Tool "[^"]*" is nearing overuse[^\n]*`,
|
|
60
|
-
// Generic repeat:
|
|
61
|
-
String.raw`
|
|
62
|
+
// Generic repeat: "You called" (post-call) / "X" would repeat / is about to repeat (preview)
|
|
63
|
+
String.raw`You called "[^"]*" \d+ times[^\n]*`,
|
|
64
|
+
String.raw`"[^"]*" (?:would repeat the same input|is about to repeat the same input) \d+ times[^\n]*`,
|
|
62
65
|
// Circuit breaker
|
|
63
66
|
String.raw`Circuit breaker: "[^"]*" (?:called|would be called) \d+ times[^\n]*`,
|
|
64
67
|
// Polling stall
|
|
65
68
|
String.raw`Polling stall: "[^"]*" returned identical output \d+ times[^\n]*`,
|
|
66
69
|
// Ping-pong
|
|
67
70
|
String.raw`Ping-pong: "[^"]*" and "[^"]*" (?:are alternating|may be stuck)[^\n]*`,
|
|
71
|
+
// Output stagnation
|
|
72
|
+
String.raw`Output stagnation:[^\n]*`,
|
|
73
|
+
// Error convergence
|
|
74
|
+
String.raw`Error convergence:[^\n]*`,
|
|
68
75
|
].join('|'),
|
|
69
76
|
'g',
|
|
70
77
|
)
|
|
71
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Matches loop detection messages wrapped in `[Error: ...]` brackets
|
|
81
|
+
* (from the err SSE event handler in use-chat-store.ts).
|
|
82
|
+
*/
|
|
83
|
+
const LOOP_DETECTION_WRAPPED_RE = /\[Error: (?:Tool "[^"]*" (?:called|would be called) \d+ times|Tool "[^"]*" is nearing overuse|You called "[^"]*" \d+ times|"[^"]*" (?:would repeat the same input|is about to repeat the same input) \d+ times|Circuit breaker: "[^"]*" (?:called|would be called) \d+ times|Polling stall: "[^"]*" returned identical output \d+ times|Ping-pong: "[^"]*" and "[^"]*" (?:are alternating|may be stuck)|Output stagnation:|Error convergence:)[^\]]*\]/g
|
|
84
|
+
|
|
72
85
|
/** Remove loop detection messages that the LLM echoed from tool error results. */
|
|
73
86
|
export function stripLoopDetectionMessages(text: string): string {
|
|
74
|
-
|
|
87
|
+
// Strip [Error: ...] wrapped versions first, before the inner regex eats the content
|
|
88
|
+
return text.replace(LOOP_DETECTION_WRAPPED_RE, '').replace(LOOP_DETECTION_RE, '')
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
// ---------------------------------------------------------------------------
|
|
@@ -76,7 +76,13 @@ export const useApprovalStore = create<ApprovalState>((set) => ({
|
|
|
76
76
|
for (const [id, a] of Object.entries(s.approvals)) {
|
|
77
77
|
if (a.expiresAtMs > now && !s.resolvedIds.has(id)) next[id] = a
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
// Prune resolvedIds for IDs no longer in the approval set
|
|
80
|
+
const liveIds = new Set(Object.keys(next))
|
|
81
|
+
const nextResolved = new Set<string>()
|
|
82
|
+
for (const id of s.resolvedIds) {
|
|
83
|
+
if (liveIds.has(id)) nextResolved.add(id)
|
|
84
|
+
}
|
|
85
|
+
return { approvals: next, resolvedIds: nextResolved }
|
|
80
86
|
})
|
|
81
87
|
},
|
|
82
88
|
|
|
@@ -196,6 +196,60 @@ describe('useChatStore control-token hygiene', () => {
|
|
|
196
196
|
assert.equal(assistantMessage?.clientRenderId, state.assistantRenderId)
|
|
197
197
|
})
|
|
198
198
|
|
|
199
|
+
it('marks the session idle locally as soon as a direct stream finishes', async () => {
|
|
200
|
+
const session = makeSession({ active: true, currentRunId: 'run-1' } as Partial<Session>)
|
|
201
|
+
useAppStore.setState({
|
|
202
|
+
agents: { 'agent-1': makeAgent() },
|
|
203
|
+
sessions: { [session.id]: session },
|
|
204
|
+
currentAgentId: 'agent-1',
|
|
205
|
+
})
|
|
206
|
+
useChatStore.setState({
|
|
207
|
+
messages: [],
|
|
208
|
+
pendingFiles: [],
|
|
209
|
+
replyingTo: null,
|
|
210
|
+
toolEvents: [],
|
|
211
|
+
streamText: '',
|
|
212
|
+
displayText: '',
|
|
213
|
+
streaming: false,
|
|
214
|
+
streamingSessionId: null,
|
|
215
|
+
streamSource: null,
|
|
216
|
+
assistantRenderId: null,
|
|
217
|
+
streamPhase: 'thinking',
|
|
218
|
+
streamToolName: '',
|
|
219
|
+
thinkingText: '',
|
|
220
|
+
thinkingStartTime: 0,
|
|
221
|
+
queuedMessages: [],
|
|
222
|
+
agentStatus: null,
|
|
223
|
+
lastUsage: null,
|
|
224
|
+
hasMoreMessages: false,
|
|
225
|
+
loadingMore: false,
|
|
226
|
+
totalMessages: 0,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
global.fetch = (async (input: RequestInfo | URL) => {
|
|
230
|
+
const url = String(input)
|
|
231
|
+
if (url === '/api/chats/session-1/chat') {
|
|
232
|
+
return sseResponse([
|
|
233
|
+
{ t: 'r', text: 'Done' },
|
|
234
|
+
{ t: 'done' },
|
|
235
|
+
])
|
|
236
|
+
}
|
|
237
|
+
if (url === '/api/chats/session-1') {
|
|
238
|
+
return new Response(JSON.stringify({ ...session, active: false, currentRunId: null }), {
|
|
239
|
+
status: 200,
|
|
240
|
+
headers: { 'content-type': 'application/json' },
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
throw new Error(`Unexpected fetch: ${url}`)
|
|
244
|
+
}) as unknown as typeof fetch
|
|
245
|
+
|
|
246
|
+
await useChatStore.getState().sendMessage('Hello', { sessionId: 'session-1' })
|
|
247
|
+
|
|
248
|
+
const refreshedSession = useAppStore.getState().sessions['session-1']
|
|
249
|
+
assert.equal(refreshedSession?.active, false)
|
|
250
|
+
assert.equal(refreshedSession?.currentRunId, null)
|
|
251
|
+
})
|
|
252
|
+
|
|
199
253
|
it('replaces optimistic queued items with the backend queue snapshot', async () => {
|
|
200
254
|
const session = makeSession()
|
|
201
255
|
useAppStore.setState({
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
removeQueuedSessionMessage,
|
|
11
11
|
} from '@/lib/chat/chats'
|
|
12
12
|
import { mergeCompletedAssistantMessage, reconcileClientMessageMetadata } from '@/lib/chat/chat-streaming-state'
|
|
13
|
+
import { createAssistantRenderId } from '@/lib/chat/assistant-render-id'
|
|
13
14
|
import { stripAllInternalMetadata } from '@/lib/strip-internal-metadata'
|
|
14
15
|
import {
|
|
15
16
|
clearQueuedMessagesForSession,
|
|
@@ -152,10 +153,6 @@ function stripHiddenControlTokens(text: string): string {
|
|
|
152
153
|
return cleaned.replace(/\n{3,}/g, '\n\n').trim()
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
function nextAssistantRenderId(): string {
|
|
156
|
-
return `assistant-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
|
157
|
-
}
|
|
158
|
-
|
|
159
156
|
function reconcileMessagesForState(
|
|
160
157
|
nextMessages: Message[],
|
|
161
158
|
currentMessages: Message[],
|
|
@@ -184,6 +181,17 @@ function syncSessionQueueState(sessionId: string, params: {
|
|
|
184
181
|
})
|
|
185
182
|
}
|
|
186
183
|
|
|
184
|
+
function markSessionRunIdle(sessionId: string): void {
|
|
185
|
+
const appState = useAppStore.getState()
|
|
186
|
+
const session = appState.sessions[sessionId]
|
|
187
|
+
if (!session) return
|
|
188
|
+
appState.updateSessionInStore({
|
|
189
|
+
...session,
|
|
190
|
+
active: false,
|
|
191
|
+
currentRunId: null,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
187
195
|
export const useChatStore = create<ChatState>((set, get) => ({
|
|
188
196
|
streaming: false,
|
|
189
197
|
streamingSessionId: null,
|
|
@@ -239,6 +247,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
239
247
|
s.queuedMessages,
|
|
240
248
|
sessionId,
|
|
241
249
|
snapshotToQueuedMessages(snapshot),
|
|
250
|
+
{ activeRunId: snapshot.activeRunId },
|
|
242
251
|
)
|
|
243
252
|
// Clear "sending" items whose text has already appeared in chat messages
|
|
244
253
|
const messages = s.messages
|
|
@@ -285,6 +294,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
285
294
|
removeQueuedMessageById(s.queuedMessages, optimistic.runId),
|
|
286
295
|
sessionId,
|
|
287
296
|
snapshotToQueuedMessages(response.snapshot),
|
|
297
|
+
{ activeRunId: response.snapshot.activeRunId },
|
|
288
298
|
),
|
|
289
299
|
}))
|
|
290
300
|
syncSessionQueueState(sessionId, {
|
|
@@ -314,6 +324,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
314
324
|
s.queuedMessages,
|
|
315
325
|
sessionId,
|
|
316
326
|
snapshotToQueuedMessages(response.snapshot),
|
|
327
|
+
{ activeRunId: response.snapshot.activeRunId },
|
|
317
328
|
),
|
|
318
329
|
}))
|
|
319
330
|
syncSessionQueueState(sessionId, {
|
|
@@ -331,6 +342,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
331
342
|
s.queuedMessages,
|
|
332
343
|
sessionId,
|
|
333
344
|
snapshotToQueuedMessages(response.snapshot),
|
|
345
|
+
{ activeRunId: response.snapshot.activeRunId },
|
|
334
346
|
),
|
|
335
347
|
}))
|
|
336
348
|
syncSessionQueueState(sessionId, {
|
|
@@ -388,7 +400,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
388
400
|
attachedFiles,
|
|
389
401
|
...(replyToId ? { replyToId } : {}),
|
|
390
402
|
}
|
|
391
|
-
const assistantRenderId =
|
|
403
|
+
const assistantRenderId = createAssistantRenderId()
|
|
392
404
|
set((s) => ({
|
|
393
405
|
streaming: true,
|
|
394
406
|
streamingSessionId: sessionId,
|
|
@@ -418,7 +430,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
418
430
|
let toolCallCounter = 0
|
|
419
431
|
let soundFiredStart = false
|
|
420
432
|
const shouldIgnoreTransientError = (msg: string) =>
|
|
421
|
-
/cancelled by steer mode|stopped by user/i.test(msg || '')
|
|
433
|
+
/cancelled by steer mode|stopped by user|stream timed out/i.test(msg || '')
|
|
422
434
|
|
|
423
435
|
try { await streamChat(sessionId, text, imagePath, imageUrl, (event: SSEEvent) => {
|
|
424
436
|
// Forward events to voice conversation handler if active
|
|
@@ -603,6 +615,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
603
615
|
thinkingText: '',
|
|
604
616
|
thinkingStartTime: 0,
|
|
605
617
|
}))
|
|
618
|
+
markSessionRunIdle(sessionId)
|
|
606
619
|
if (get().ttsEnabled && !get().voiceConversationActive) speak(visibleFinalText)
|
|
607
620
|
} else {
|
|
608
621
|
set({
|
|
@@ -617,6 +630,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
617
630
|
thinkingText: '',
|
|
618
631
|
thinkingStartTime: 0,
|
|
619
632
|
})
|
|
633
|
+
markSessionRunIdle(sessionId)
|
|
620
634
|
}
|
|
621
635
|
|
|
622
636
|
void useAppStore.getState().refreshSession(sessionId)
|
|
@@ -635,6 +649,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
635
649
|
thinkingText: '',
|
|
636
650
|
thinkingStartTime: 0,
|
|
637
651
|
})
|
|
652
|
+
markSessionRunIdle(sessionId)
|
|
638
653
|
}
|
|
639
654
|
}
|
|
640
655
|
},
|
|
@@ -860,6 +875,11 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
860
875
|
displayText: '',
|
|
861
876
|
streamPhase: 'thinking' as const,
|
|
862
877
|
streamToolName: '',
|
|
878
|
+
thinkingText: '',
|
|
879
|
+
thinkingStartTime: 0,
|
|
880
|
+
toolEvents: [],
|
|
881
|
+
agentStatus: null,
|
|
863
882
|
})
|
|
883
|
+
if (sessionId) markSessionRunIdle(sessionId)
|
|
864
884
|
},
|
|
865
885
|
}))
|
package/src/types/index.ts
CHANGED
|
@@ -452,6 +452,8 @@ export interface Session {
|
|
|
452
452
|
theme?: string
|
|
453
453
|
avatar?: string
|
|
454
454
|
canvasContent?: CanvasContent
|
|
455
|
+
/** Tracks how many times each memory ID has been injected via proactive recall in this session. */
|
|
456
|
+
injectedMemoryIds?: Record<string, number>
|
|
455
457
|
}
|
|
456
458
|
|
|
457
459
|
export type Sessions = Record<string, Session>
|
|
@@ -1545,6 +1547,7 @@ export interface MemoryEntry {
|
|
|
1545
1547
|
lastAccessedAt?: number
|
|
1546
1548
|
contentHash?: string
|
|
1547
1549
|
reinforcementCount?: number
|
|
1550
|
+
abstract?: string | null
|
|
1548
1551
|
createdAt: number
|
|
1549
1552
|
updatedAt: number
|
|
1550
1553
|
}
|
|
@@ -2147,6 +2150,9 @@ export interface SessionRunRecord {
|
|
|
2147
2150
|
recoveredFromRestart?: boolean
|
|
2148
2151
|
recoveredFromRunId?: string
|
|
2149
2152
|
recoveryPayload?: SessionRunRecoveryPayload
|
|
2153
|
+
totalInputTokens?: number
|
|
2154
|
+
totalOutputTokens?: number
|
|
2155
|
+
estimatedCost?: number
|
|
2150
2156
|
}
|
|
2151
2157
|
|
|
2152
2158
|
export interface SessionQueuedTurn {
|
|
File without changes
|