@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
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it, before, after } from 'node:test'
|
|
3
|
+
|
|
4
|
+
const originalEnv = {
|
|
5
|
+
SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let mod: typeof import('@/lib/server/chat-execution/message-classifier')
|
|
9
|
+
|
|
10
|
+
before(async () => {
|
|
11
|
+
process.env.SWARMCLAW_BUILD_MODE = '1'
|
|
12
|
+
mod = await import('@/lib/server/chat-execution/message-classifier')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
after(() => {
|
|
16
|
+
if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
|
|
17
|
+
else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// parseClassificationResponse
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe('parseClassificationResponse', () => {
|
|
25
|
+
const validJson = JSON.stringify({
|
|
26
|
+
isDeliverableTask: true,
|
|
27
|
+
isBroadGoal: false,
|
|
28
|
+
walletIntent: 'none',
|
|
29
|
+
hasHumanSignals: false,
|
|
30
|
+
hasSignificantEvent: false,
|
|
31
|
+
isResearchSynthesis: false,
|
|
32
|
+
explicitToolRequests: [],
|
|
33
|
+
confidence: 0.9,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('parses valid JSON with all schema fields', () => {
|
|
37
|
+
const result = mod.parseClassificationResponse(validJson)
|
|
38
|
+
assert.ok(result)
|
|
39
|
+
assert.equal(result!.isDeliverableTask, true)
|
|
40
|
+
assert.equal(result!.isBroadGoal, false)
|
|
41
|
+
assert.equal(result!.walletIntent, 'none')
|
|
42
|
+
assert.equal(result!.confidence, 0.9)
|
|
43
|
+
assert.deepEqual(result!.explicitToolRequests, [])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns null for malformed JSON', () => {
|
|
47
|
+
assert.equal(mod.parseClassificationResponse('not json at all'), null)
|
|
48
|
+
assert.equal(mod.parseClassificationResponse('{broken'), null)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('returns null for JSON missing required keys', () => {
|
|
52
|
+
const partial = JSON.stringify({ isDeliverableTask: true })
|
|
53
|
+
assert.equal(mod.parseClassificationResponse(partial), null)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('tolerates extra keys in JSON', () => {
|
|
57
|
+
const withExtra = JSON.stringify({
|
|
58
|
+
isDeliverableTask: true,
|
|
59
|
+
isBroadGoal: false,
|
|
60
|
+
walletIntent: 'none',
|
|
61
|
+
hasHumanSignals: false,
|
|
62
|
+
hasSignificantEvent: false,
|
|
63
|
+
isResearchSynthesis: false,
|
|
64
|
+
explicitToolRequests: ['shell'],
|
|
65
|
+
confidence: 0.85,
|
|
66
|
+
extraKey: 'should be ignored',
|
|
67
|
+
})
|
|
68
|
+
const result = mod.parseClassificationResponse(withExtra)
|
|
69
|
+
assert.ok(result)
|
|
70
|
+
assert.equal(result!.isDeliverableTask, true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('extracts embedded JSON from prose text', () => {
|
|
74
|
+
const prose = `Here is my classification:\n${validJson}\nEnd of classification.`
|
|
75
|
+
const result = mod.parseClassificationResponse(prose)
|
|
76
|
+
assert.ok(result)
|
|
77
|
+
assert.equal(result!.isDeliverableTask, true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('returns null for empty text', () => {
|
|
81
|
+
assert.equal(mod.parseClassificationResponse(''), null)
|
|
82
|
+
assert.equal(mod.parseClassificationResponse(' '), null)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// isDeliverableTask
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
describe('isDeliverableTask', () => {
|
|
91
|
+
it('uses classification value when provided', () => {
|
|
92
|
+
const cls = makeClassification({ isDeliverableTask: true })
|
|
93
|
+
assert.equal(mod.isDeliverableTask(cls, 'anything'), true)
|
|
94
|
+
|
|
95
|
+
const cls2 = makeClassification({ isDeliverableTask: false })
|
|
96
|
+
assert.equal(mod.isDeliverableTask(cls2, 'build me a landing page'), false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('falls back to regex when classification is null', () => {
|
|
100
|
+
// A message that looks like a deliverable task
|
|
101
|
+
const result = mod.isDeliverableTask(null, 'Create a detailed marketing report with competitor analysis and market sizing. Include charts and recommendations for Q3 strategy across all regions.')
|
|
102
|
+
assert.equal(typeof result, 'boolean')
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// isBroadGoal
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
describe('isBroadGoal', () => {
|
|
111
|
+
it('uses classification value when provided', () => {
|
|
112
|
+
assert.equal(mod.isBroadGoal(makeClassification({ isBroadGoal: true }), ''), true)
|
|
113
|
+
assert.equal(mod.isBroadGoal(makeClassification({ isBroadGoal: false }), ''), false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('falls back to regex when classification is null', () => {
|
|
117
|
+
const result = mod.isBroadGoal(null, 'I want to build a complete e-commerce platform with user authentication, product catalog, shopping cart, and payment processing')
|
|
118
|
+
assert.equal(typeof result, 'boolean')
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// hasWalletIntent
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
describe('hasWalletIntent', () => {
|
|
127
|
+
it('walletIntent none returns false', () => {
|
|
128
|
+
assert.equal(mod.hasWalletIntent(makeClassification({ walletIntent: 'none' }), ''), false)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('walletIntent read_only returns true', () => {
|
|
132
|
+
assert.equal(mod.hasWalletIntent(makeClassification({ walletIntent: 'read_only' }), ''), true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('walletIntent transactional returns true', () => {
|
|
136
|
+
assert.equal(mod.hasWalletIntent(makeClassification({ walletIntent: 'transactional' }), ''), true)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('falls back to regex when classification is null', () => {
|
|
140
|
+
const result = mod.hasWalletIntent(null, 'check my wallet balance')
|
|
141
|
+
assert.equal(typeof result, 'boolean')
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// hasTransactionalWalletIntent
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
describe('hasTransactionalWalletIntent', () => {
|
|
150
|
+
it('only transactional returns true', () => {
|
|
151
|
+
assert.equal(mod.hasTransactionalWalletIntent(makeClassification({ walletIntent: 'transactional' }), ''), true)
|
|
152
|
+
assert.equal(mod.hasTransactionalWalletIntent(makeClassification({ walletIntent: 'read_only' }), ''), false)
|
|
153
|
+
assert.equal(mod.hasTransactionalWalletIntent(makeClassification({ walletIntent: 'none' }), ''), false)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('falls back to regex when classification is null', () => {
|
|
157
|
+
const result = mod.hasTransactionalWalletIntent(null, 'swap 1 ETH for USDC')
|
|
158
|
+
assert.equal(typeof result, 'boolean')
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// hasHumanSignals
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
describe('hasHumanSignals', () => {
|
|
167
|
+
it('uses classification value when provided', () => {
|
|
168
|
+
assert.equal(mod.hasHumanSignals(makeClassification({ hasHumanSignals: true }), ''), true)
|
|
169
|
+
assert.equal(mod.hasHumanSignals(makeClassification({ hasHumanSignals: false }), ''), false)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('regex detects personal text', () => {
|
|
173
|
+
assert.equal(mod.hasHumanSignals(null, 'my birthday is next week'), true)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('regex returns false for task-only text', () => {
|
|
177
|
+
assert.equal(mod.hasHumanSignals(null, 'deploy the app'), false)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// hasSignificantEvent
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
describe('hasSignificantEvent', () => {
|
|
186
|
+
it('uses classification value when provided', () => {
|
|
187
|
+
assert.equal(mod.hasSignificantEvent(makeClassification({ hasSignificantEvent: true }), ''), true)
|
|
188
|
+
assert.equal(mod.hasSignificantEvent(makeClassification({ hasSignificantEvent: false }), ''), false)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('regex detects significant events', () => {
|
|
192
|
+
assert.equal(mod.hasSignificantEvent(null, 'I just got promoted at work'), true)
|
|
193
|
+
assert.equal(mod.hasSignificantEvent(null, 'my graduation ceremony is on Friday'), true)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('regex returns false for non-event text', () => {
|
|
197
|
+
assert.equal(mod.hasSignificantEvent(null, 'fix the login bug'), false)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// isResearchSynthesis
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
describe('isResearchSynthesis', () => {
|
|
206
|
+
it('uses classification value when provided', () => {
|
|
207
|
+
assert.equal(mod.isResearchSynthesis(makeClassification({ isResearchSynthesis: true }), null), true)
|
|
208
|
+
assert.equal(mod.isResearchSynthesis(makeClassification({ isResearchSynthesis: false }), null), false)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('falls back to routingIntent when classification is null', () => {
|
|
212
|
+
assert.equal(mod.isResearchSynthesis(null, 'research'), true)
|
|
213
|
+
assert.equal(mod.isResearchSynthesis(null, 'browsing'), true)
|
|
214
|
+
assert.equal(mod.isResearchSynthesis(null, 'coding'), false)
|
|
215
|
+
assert.equal(mod.isResearchSynthesis(null, null), false)
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// classifyMessage — with generateText override
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
describe('classifyMessage', () => {
|
|
224
|
+
it('returns valid classification from mock generateText', async () => {
|
|
225
|
+
const mockResponse = JSON.stringify({
|
|
226
|
+
isDeliverableTask: true,
|
|
227
|
+
isBroadGoal: false,
|
|
228
|
+
walletIntent: 'none',
|
|
229
|
+
hasHumanSignals: false,
|
|
230
|
+
hasSignificantEvent: false,
|
|
231
|
+
isResearchSynthesis: false,
|
|
232
|
+
explicitToolRequests: ['shell'],
|
|
233
|
+
confidence: 0.95,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const result = await mod.classifyMessage(
|
|
237
|
+
{ sessionId: 'test-session', message: 'Build me a dashboard' },
|
|
238
|
+
{ generateText: async () => mockResponse },
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
assert.ok(result)
|
|
242
|
+
assert.equal(result!.isDeliverableTask, true)
|
|
243
|
+
assert.equal(result!.walletIntent, 'none')
|
|
244
|
+
assert.deepEqual(result!.explicitToolRequests, ['shell'])
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('returns null for empty message', async () => {
|
|
248
|
+
const result = await mod.classifyMessage(
|
|
249
|
+
{ sessionId: 'test-session', message: '' },
|
|
250
|
+
{ generateText: async () => '{}' },
|
|
251
|
+
)
|
|
252
|
+
assert.equal(result, null)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('returns null for whitespace-only message', async () => {
|
|
256
|
+
const result = await mod.classifyMessage(
|
|
257
|
+
{ sessionId: 'test-session', message: ' ' },
|
|
258
|
+
{ generateText: async () => '{}' },
|
|
259
|
+
)
|
|
260
|
+
assert.equal(result, null)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('returns null when generateText times out', async () => {
|
|
264
|
+
const result = await mod.classifyMessage(
|
|
265
|
+
{ sessionId: 'test-session', message: 'A message that will timeout for classification purposes' },
|
|
266
|
+
{
|
|
267
|
+
generateText: () => new Promise((resolve) => {
|
|
268
|
+
// Never resolves within 2s timeout
|
|
269
|
+
setTimeout(() => resolve('{}'), 10_000)
|
|
270
|
+
}),
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
assert.equal(result, null)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('caches results for the same message', async () => {
|
|
277
|
+
let callCount = 0
|
|
278
|
+
const mockResponse = JSON.stringify({
|
|
279
|
+
isDeliverableTask: false,
|
|
280
|
+
isBroadGoal: false,
|
|
281
|
+
walletIntent: 'none',
|
|
282
|
+
hasHumanSignals: false,
|
|
283
|
+
hasSignificantEvent: false,
|
|
284
|
+
isResearchSynthesis: false,
|
|
285
|
+
explicitToolRequests: [],
|
|
286
|
+
confidence: 0.8,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
const generateText = async () => {
|
|
290
|
+
callCount++
|
|
291
|
+
return mockResponse
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Use a unique message to avoid cache from other tests
|
|
295
|
+
const uniqueMsg = `cache-test-${Date.now()}-${Math.random()}`
|
|
296
|
+
|
|
297
|
+
const first = await mod.classifyMessage(
|
|
298
|
+
{ sessionId: 'test-session', message: uniqueMsg },
|
|
299
|
+
{ generateText },
|
|
300
|
+
)
|
|
301
|
+
const second = await mod.classifyMessage(
|
|
302
|
+
{ sessionId: 'test-session', message: uniqueMsg },
|
|
303
|
+
{ generateText },
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
assert.ok(first)
|
|
307
|
+
assert.ok(second)
|
|
308
|
+
assert.deepEqual(first, second)
|
|
309
|
+
assert.equal(callCount, 1, 'generateText should only be called once due to cache')
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Helper
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
function makeClassification(overrides: Partial<import('@/lib/server/chat-execution/message-classifier').MessageClassification>): import('@/lib/server/chat-execution/message-classifier').MessageClassification {
|
|
318
|
+
return {
|
|
319
|
+
isDeliverableTask: false,
|
|
320
|
+
isBroadGoal: false,
|
|
321
|
+
walletIntent: 'none',
|
|
322
|
+
hasHumanSignals: false,
|
|
323
|
+
hasSignificantEvent: false,
|
|
324
|
+
isResearchSynthesis: false,
|
|
325
|
+
explicitToolRequests: [],
|
|
326
|
+
confidence: 0.9,
|
|
327
|
+
...overrides,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -13,9 +13,12 @@ import crypto from 'node:crypto'
|
|
|
13
13
|
import { HumanMessage } from '@langchain/core/messages'
|
|
14
14
|
import { z } from 'zod'
|
|
15
15
|
import { buildLLM } from '@/lib/server/build-llm'
|
|
16
|
+
import { log } from '@/lib/server/logger'
|
|
16
17
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
17
18
|
import type { Message } from '@/types'
|
|
18
19
|
|
|
20
|
+
const TAG = 'message-classifier'
|
|
21
|
+
|
|
19
22
|
// ---------------------------------------------------------------------------
|
|
20
23
|
// Schema
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
@@ -221,7 +224,7 @@ export async function classifyMessage(
|
|
|
221
224
|
])
|
|
222
225
|
|
|
223
226
|
const durationMs = Date.now() - startMs
|
|
224
|
-
|
|
227
|
+
log.info(TAG, `session=${input.sessionId} completed in ${durationMs}ms`)
|
|
225
228
|
|
|
226
229
|
const classification = parseClassificationResponse(responseText)
|
|
227
230
|
if (classification) {
|
|
@@ -230,7 +233,7 @@ export async function classifyMessage(
|
|
|
230
233
|
return classification
|
|
231
234
|
} catch (err: unknown) {
|
|
232
235
|
const durationMs = Date.now() - startMs
|
|
233
|
-
|
|
236
|
+
log.warn(TAG, `session=${input.sessionId} failed in ${durationMs}ms: ${err instanceof Error ? err.message : 'unknown'}`)
|
|
234
237
|
return null
|
|
235
238
|
}
|
|
236
239
|
}
|
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
* and OpenClaw sync.
|
|
7
7
|
*/
|
|
8
8
|
import type { Session, UsageRecord } from '@/types'
|
|
9
|
+
import { log } from '@/lib/server/logger'
|
|
9
10
|
import type { ChatTurnState } from '@/lib/server/chat-execution/chat-turn-state'
|
|
11
|
+
|
|
12
|
+
const TAG = 'post-stream'
|
|
10
13
|
import { extractSuggestions } from '@/lib/server/suggestions'
|
|
11
14
|
import type { StructuredToolInterface } from '@langchain/core/tools'
|
|
12
15
|
import { estimateCost, buildExtensionDefinitionCosts } from '@/lib/server/cost'
|
|
13
|
-
import { appendUsage } from '@/lib/server/
|
|
16
|
+
import { appendUsage } from '@/lib/server/usage/usage-repository'
|
|
14
17
|
import { runCapabilityHook } from '@/lib/server/native-capabilities'
|
|
15
18
|
import {
|
|
16
19
|
shouldForceExternalServiceSummary,
|
|
@@ -39,7 +42,7 @@ function stripLeakedClassificationJson(text: string): { cleaned: string; strippe
|
|
|
39
42
|
else if (text[i] === '}') { depth--; if (depth === 0) { end = i + 1; break } }
|
|
40
43
|
}
|
|
41
44
|
if (end === -1) return { cleaned: text, stripped: false }
|
|
42
|
-
|
|
45
|
+
log.warn(TAG, 'Stripped leaked classification JSON from model output')
|
|
43
46
|
return { cleaned: (text.slice(0, startIdx) + text.slice(end)).trimStart(), stripped: true }
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -73,13 +73,24 @@ function buildExtensionCapabilityLines(enabledExtensions: string[], opts?: { del
|
|
|
73
73
|
return lines
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
const DISPLAY_TOOL_ALIASES: Record<string, string[]> = {
|
|
77
|
+
files: ['send_file'],
|
|
78
|
+
shell: ['sandbox_exec', 'sandbox_list_runtimes'],
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
function buildExactToolNameList(enabledExtensions: string[]): string[] {
|
|
77
82
|
const planning = getEnabledToolPlanningView(enabledExtensions)
|
|
83
|
+
const displayAliases = dedup(
|
|
84
|
+
enabledExtensions
|
|
85
|
+
.map((toolId) => canonicalizeExtensionId(toolId))
|
|
86
|
+
.flatMap((toolId) => DISPLAY_TOOL_ALIASES[toolId] || []),
|
|
87
|
+
)
|
|
78
88
|
const extensionToolNames = getExtensionManager()
|
|
79
89
|
.getTools(enabledExtensions)
|
|
80
90
|
.map(({ tool }) => tool.name)
|
|
81
91
|
const combined = [
|
|
82
92
|
...planning.displayToolIds,
|
|
93
|
+
...displayAliases,
|
|
83
94
|
...planning.entries.map((entry) => entry.toolName),
|
|
84
95
|
...extensionToolNames,
|
|
85
96
|
]
|
|
@@ -120,6 +131,16 @@ export function buildToolDisciplineLines(enabledExtensions: string[]): string[]
|
|
|
120
131
|
|
|
121
132
|
lines.push(...planning.disciplineGuidance)
|
|
122
133
|
|
|
134
|
+
// Universal tool efficiency guidance — tool-specific lines live in CORE_TOOL_PLANNING (tool-planning.ts)
|
|
135
|
+
lines.push(
|
|
136
|
+
'## Tool Efficiency',
|
|
137
|
+
'Plan your approach before starting tool calls. State what you will do, then do it.',
|
|
138
|
+
'Prefer fewer, larger tool calls over many small ones.',
|
|
139
|
+
'Do not poll for status in a loop. If waiting on a process, check once and move on.',
|
|
140
|
+
'If stuck after 2-3 attempts with the same approach, stop and state the blocker — do not keep retrying.',
|
|
141
|
+
'When delegating to subagents, use waitForCompletion or wait/wait_all instead of polling status in a loop.',
|
|
142
|
+
)
|
|
143
|
+
|
|
123
144
|
const researchSearchTools = getToolsForCapability(enabledExtensions, TOOL_CAPABILITY.researchSearch)
|
|
124
145
|
const researchFetchTools = getToolsForCapability(enabledExtensions, TOOL_CAPABILITY.researchFetch)
|
|
125
146
|
const browserCaptureTools = getToolsForCapability(enabledExtensions, TOOL_CAPABILITY.browserCapture)
|
|
@@ -275,7 +296,7 @@ const GOAL_DECOMPOSITION_BLOCK = [
|
|
|
275
296
|
'## Goal Decomposition',
|
|
276
297
|
'When you receive a broad, open-ended goal:',
|
|
277
298
|
'1. Break it into 3-7 concrete, sequentially-executable subtasks before taking action.',
|
|
278
|
-
'2. If manage_tasks is available, use it only for durable tracking: multi-turn work, delegation, explicit backlog requests, or work you expect to resume later. Do not create a task for every micro-step.',
|
|
299
|
+
'2. If manage_tasks is available, use it only for durable tracking: multi-turn work, delegation, explicit backlog requests, or work you expect to resume later. Do not create a task for every micro-step. Do not re-read the task list after every update. Read once, make your changes, then move on.',
|
|
279
300
|
'Single-step instructions are not broad goals. For direct actions like storing a memory, answering a recall question, editing one file, or sending one message, execute the relevant tool immediately instead of creating tasks or delegating.',
|
|
280
301
|
'3. Present the plan as a short checklist or numbered list in plain language. If durable tracking is unnecessary, keep it inline instead of creating tasks.',
|
|
281
302
|
'4. Execute the first substantive subtask immediately — do not stop after planning.',
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import type { Session, Agent } from '@/types'
|
|
10
10
|
import type { ActiveProjectContext } from '@/lib/server/project-context'
|
|
11
11
|
import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
|
|
12
|
-
import {
|
|
12
|
+
import { getAgent, listAgents } from '@/lib/server/agents/agent-repository'
|
|
13
|
+
import { loadSkills } from '@/lib/server/skills/skill-repository'
|
|
13
14
|
import { buildRuntimeSkillPromptBlocks, resolveRuntimeSkills } from '@/lib/server/skills/runtime-skill-resolver'
|
|
14
15
|
import { resolveTeam } from '@/lib/server/agents/team-resolution'
|
|
15
16
|
|
|
@@ -123,7 +124,20 @@ export async function buildAgentAwarenessSection(
|
|
|
123
124
|
if (!hasMultiAgentTool || !session.agentId) return null
|
|
124
125
|
try {
|
|
125
126
|
const { buildAgentAwarenessBlock } = await import('@/lib/server/agents/agent-registry')
|
|
126
|
-
|
|
127
|
+
|
|
128
|
+
// Load agent to get delegation settings so the awareness block respects them
|
|
129
|
+
let delegationOpts: { delegationTargetMode?: 'all' | 'selected'; delegationTargetAgentIds?: string[] } | undefined
|
|
130
|
+
try {
|
|
131
|
+
const agent = getAgent(session.agentId)
|
|
132
|
+
if (agent?.delegationTargetMode === 'selected') {
|
|
133
|
+
delegationOpts = {
|
|
134
|
+
delegationTargetMode: 'selected',
|
|
135
|
+
delegationTargetAgentIds: agent.delegationTargetAgentIds || [],
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch { /* non-critical */ }
|
|
139
|
+
|
|
140
|
+
return buildAgentAwarenessBlock(session.agentId, delegationOpts) || null
|
|
127
141
|
} catch { return null }
|
|
128
142
|
}
|
|
129
143
|
|
|
@@ -185,6 +199,8 @@ export function buildProjectSection(
|
|
|
185
199
|
`project secrets ${summary.secretCount}`,
|
|
186
200
|
]
|
|
187
201
|
if (summary.topTaskTitles.length > 0) lines.push(`Top open tasks: ${summary.topTaskTitles.join('; ')}`)
|
|
202
|
+
if (summary.failedTaskCount > 0) lines.push(`Failed tasks needing attention: ${summary.failedTaskCount}`)
|
|
203
|
+
if (summary.staleTaskCount > 0) lines.push(`Stale tasks (no update in 3+ days): ${summary.staleTaskCount}`)
|
|
188
204
|
if (summary.scheduleNames.length > 0) lines.push(`Active schedules: ${summary.scheduleNames.join('; ')}`)
|
|
189
205
|
if (summary.secretNames.length > 0) lines.push(`Known project secrets: ${summary.secretNames.join('; ')}`)
|
|
190
206
|
lines.push(`Project resource summary: ${resourceBits.join(', ')}.`)
|
|
@@ -257,6 +273,11 @@ export function buildSuggestionsSection(
|
|
|
257
273
|
// Proactive Memory Recall (async)
|
|
258
274
|
// ---------------------------------------------------------------------------
|
|
259
275
|
|
|
276
|
+
export interface ProactiveMemoryResult {
|
|
277
|
+
section: string | null
|
|
278
|
+
injectedIds: Record<string, number>
|
|
279
|
+
}
|
|
280
|
+
|
|
260
281
|
export async function buildProactiveMemorySection(
|
|
261
282
|
session: Session,
|
|
262
283
|
agent: Agent | null | undefined,
|
|
@@ -264,9 +285,10 @@ export async function buildProactiveMemorySection(
|
|
|
264
285
|
activeProjectRoot: string | null,
|
|
265
286
|
isMinimalPrompt: boolean,
|
|
266
287
|
currentThreadRecallRequest: boolean,
|
|
267
|
-
): Promise<
|
|
268
|
-
|
|
269
|
-
if (!
|
|
288
|
+
): Promise<ProactiveMemoryResult> {
|
|
289
|
+
const noResult: ProactiveMemoryResult = { section: null, injectedIds: {} }
|
|
290
|
+
if (isMinimalPrompt || !session.agentId || currentThreadRecallRequest || message.length <= 12) return noResult
|
|
291
|
+
if (!agent?.proactiveMemory) return noResult
|
|
270
292
|
try {
|
|
271
293
|
const { getMemoryDb } = await import('@/lib/server/memory/memory-db')
|
|
272
294
|
const { buildSessionMemoryScopeFilter } = await import('@/lib/server/memory/session-memory-scope')
|
|
@@ -274,13 +296,29 @@ export async function buildProactiveMemorySection(
|
|
|
274
296
|
const recalled = memDb.search(message, session.agentId, {
|
|
275
297
|
scope: buildSessionMemoryScopeFilter(session, agent.memoryScopeMode || null, activeProjectRoot),
|
|
276
298
|
})
|
|
277
|
-
|
|
299
|
+
|
|
300
|
+
// Dedup: skip memories already injected 2+ times in this session
|
|
301
|
+
const priorCounts = session.injectedMemoryIds || {}
|
|
302
|
+
const filtered = recalled.filter((entry) => (priorCounts[entry.id] || 0) < 2)
|
|
303
|
+
|
|
304
|
+
const topRecalled = filtered.slice(0, 3)
|
|
278
305
|
if (topRecalled.length > 0) {
|
|
279
|
-
|
|
280
|
-
|
|
306
|
+
// Track injection counts
|
|
307
|
+
const updatedCounts: Record<string, number> = { ...priorCounts }
|
|
308
|
+
for (const entry of topRecalled) {
|
|
309
|
+
updatedCounts[entry.id] = (updatedCounts[entry.id] || 0) + 1
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const recalledLines = topRecalled.map((entry) =>
|
|
313
|
+
`- ${entry.abstract || entry.content.slice(0, 300)}`,
|
|
314
|
+
)
|
|
315
|
+
return {
|
|
316
|
+
section: `## Recalled Context\nRelevant memories from previous interactions:\n${recalledLines.join('\n')}`,
|
|
317
|
+
injectedIds: updatedCounts,
|
|
318
|
+
}
|
|
281
319
|
}
|
|
282
320
|
} catch { /* non-critical */ }
|
|
283
|
-
return
|
|
321
|
+
return noResult
|
|
284
322
|
}
|
|
285
323
|
|
|
286
324
|
// ---------------------------------------------------------------------------
|
|
@@ -296,7 +334,7 @@ export function buildCoordinatorSection(
|
|
|
296
334
|
): string | null {
|
|
297
335
|
if (!agent || agent.role !== 'coordinator') return null
|
|
298
336
|
|
|
299
|
-
const allAgents =
|
|
337
|
+
const allAgents = listAgents()
|
|
300
338
|
const selfId = agent.id
|
|
301
339
|
|
|
302
340
|
// Resolve which agents this coordinator can delegate to
|
|
@@ -328,7 +366,7 @@ export function buildCoordinatorSection(
|
|
|
328
366
|
for (const w of listed) {
|
|
329
367
|
const caps = w.capabilities?.length ? ` [${w.capabilities.join(', ')}]` : ''
|
|
330
368
|
const desc = w.description ? ` — ${w.description.slice(0, 100)}` : ''
|
|
331
|
-
const line = `- **${w.name}
|
|
369
|
+
const line = `- **${w.name}** [id: ${w.id}]${caps}${desc}`
|
|
332
370
|
if (charBudget - line.length < 0) break
|
|
333
371
|
charBudget -= line.length + 1
|
|
334
372
|
lines.push(line)
|
|
@@ -338,6 +376,11 @@ export function buildCoordinatorSection(
|
|
|
338
376
|
lines.push(`- ... and ${workers.length - COORDINATOR_MAX_WORKERS} more workers`)
|
|
339
377
|
}
|
|
340
378
|
|
|
379
|
+
if (delegateMode === 'selected') {
|
|
380
|
+
lines.push('')
|
|
381
|
+
lines.push('**IMPORTANT:** You may ONLY delegate to the workers listed above. Do NOT attempt to delegate to any other agents — such attempts will be rejected.')
|
|
382
|
+
}
|
|
383
|
+
|
|
341
384
|
lines.push('')
|
|
342
385
|
lines.push('### Orchestration Tools')
|
|
343
386
|
lines.push('- **`spawn_subagent`** — Simple fire-and-forget delegation. Best for: single tasks, batch parallel/serial, basic swarm. Use when tasks are independent.')
|
|
@@ -468,8 +511,7 @@ export function buildCliDelegationContext(opts: {
|
|
|
468
511
|
// Team roster summary
|
|
469
512
|
if (opts.agent?.id) {
|
|
470
513
|
try {
|
|
471
|
-
const
|
|
472
|
-
const team = resolveTeam(opts.agent.id, agents)
|
|
514
|
+
const team = resolveTeam(opts.agent.id, listAgents())
|
|
473
515
|
if (team.mode === 'team') {
|
|
474
516
|
const teammates = [
|
|
475
517
|
...(team.coordinator ? [`${team.coordinator.name} (coordinator)`] : []),
|
|
@@ -9,6 +9,9 @@ import crypto from 'node:crypto'
|
|
|
9
9
|
import { HumanMessage } from '@langchain/core/messages'
|
|
10
10
|
import { z } from 'zod'
|
|
11
11
|
import { buildLLM } from '@/lib/server/build-llm'
|
|
12
|
+
import { log } from '@/lib/server/logger'
|
|
13
|
+
|
|
14
|
+
const TAG = 'response-completeness'
|
|
12
15
|
|
|
13
16
|
// ---------------------------------------------------------------------------
|
|
14
17
|
// Schema
|
|
@@ -191,7 +194,7 @@ export async function evaluateResponseCompleteness(
|
|
|
191
194
|
])
|
|
192
195
|
|
|
193
196
|
const durationMs = Date.now() - startMs
|
|
194
|
-
|
|
197
|
+
log.info(TAG, `session=${input.sessionId} completed in ${durationMs}ms`)
|
|
195
198
|
|
|
196
199
|
const completeness = parseCompletenessResponse(responseText)
|
|
197
200
|
if (completeness) {
|
|
@@ -200,7 +203,7 @@ export async function evaluateResponseCompleteness(
|
|
|
200
203
|
return completeness
|
|
201
204
|
} catch (err: unknown) {
|
|
202
205
|
const durationMs = Date.now() - startMs
|
|
203
|
-
|
|
206
|
+
log.warn(TAG, `session=${input.sessionId} failed in ${durationMs}ms: ${err instanceof Error ? err.message : 'unknown'}`)
|
|
204
207
|
return null
|
|
205
208
|
}
|
|
206
209
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listAgentIncidents } from '@/lib/server/autonomy/supervisor-incident-repository'
|
|
2
|
+
import { listAgents } from '@/lib/server/agents/agent-repository'
|
|
3
|
+
import { loadChatrooms } from '@/lib/server/chatrooms/chatroom-repository'
|
|
4
|
+
import { loadConnectors } from '@/lib/server/connectors/connector-repository'
|
|
5
|
+
import { loadMission } from '@/lib/server/missions/mission-repository'
|
|
6
|
+
import { loadSchedules } from '@/lib/server/schedules/schedule-repository'
|
|
7
|
+
import { loadTasks } from '@/lib/server/tasks/task-repository'
|
|
8
|
+
import { loadUsage } from '@/lib/server/usage/usage-repository'
|
|
2
9
|
import { listPersistedRuns } from '@/lib/server/runtime/run-ledger'
|
|
3
10
|
import type { BoardTask, Mission, Schedule, SupervisorIncident, SessionRunRecord } from '@/types'
|
|
4
11
|
|
|
@@ -258,7 +265,7 @@ export function buildPlatformStatusSummary(): string {
|
|
|
258
265
|
const oneDayAgo = now - 86_400_000
|
|
259
266
|
|
|
260
267
|
// Agents
|
|
261
|
-
const agents = Object.values(
|
|
268
|
+
const agents = Object.values(listAgents())
|
|
262
269
|
const activeAgents = agents.filter((a) => a.lastUsedAt && a.lastUsedAt > oneHourAgo)
|
|
263
270
|
|
|
264
271
|
// Tasks
|
|
@@ -276,7 +283,7 @@ export function buildPlatformStatusSummary(): string {
|
|
|
276
283
|
const connectors = Object.values(loadConnectors())
|
|
277
284
|
const connectorLines: string[] = []
|
|
278
285
|
for (const c of connectors) {
|
|
279
|
-
const status = c.status === 'running'
|
|
286
|
+
const status = c.status === 'running' ? '✓' : `✗ (${c.status})`
|
|
280
287
|
connectorLines.push(`${c.platform || c.id} ${status}`)
|
|
281
288
|
}
|
|
282
289
|
|
|
@@ -285,8 +292,7 @@ export function buildPlatformStatusSummary(): string {
|
|
|
285
292
|
const activeChatrooms = chatrooms.filter((c) => !c.archivedAt && !c.temporary)
|
|
286
293
|
|
|
287
294
|
// Incidents
|
|
288
|
-
const
|
|
289
|
-
const recentIncidents = allIncidents.filter((i) => i.createdAt > oneDayAgo)
|
|
295
|
+
const recentIncidents = listAgentIncidents().filter((i) => i.createdAt > oneDayAgo)
|
|
290
296
|
const warnings = recentIncidents.filter((i) => i.severity === 'medium').length
|
|
291
297
|
const errors = recentIncidents.filter((i) => i.severity === 'high').length
|
|
292
298
|
|
|
@@ -348,8 +354,7 @@ export function buildSituationalAwarenessBlock(input: SituationalAwarenessInput)
|
|
|
348
354
|
|
|
349
355
|
const failedRuns = listPersistedRuns({ sessionId, status: 'failed', limit: 10 })
|
|
350
356
|
|
|
351
|
-
const
|
|
352
|
-
const incidents = Object.values(allIncidents).filter((i) => i.agentId === agentId)
|
|
357
|
+
const incidents = listAgentIncidents(agentId)
|
|
353
358
|
|
|
354
359
|
const mission = missionId ? loadMission(missionId) : null
|
|
355
360
|
|