@swarmclawai/swarmclaw 1.2.4 → 1.2.5
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 +14 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +15 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +5 -5
- package/src/components/auth/setup-wizard/index.tsx +4 -4
- package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
- package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
- package/src/components/auth/setup-wizard/utils.ts +1 -1
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +60 -61
- package/src/components/providers/provider-sheet.tsx +74 -56
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +1 -1
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +44 -267
- package/src/lib/server/storage-normalization.ts +75 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +277 -12
|
@@ -4,7 +4,7 @@ import type { MessageToolEvent } from '@/types'
|
|
|
4
4
|
import { buildLLM } from '@/lib/server/build-llm'
|
|
5
5
|
|
|
6
6
|
const DirectMemoryIntentResponseSchema = z.object({
|
|
7
|
-
action: z.enum(['none', 'store', 'update', 'recall']),
|
|
7
|
+
action: z.enum(['none', 'store', 'update', 'recall', 'list']),
|
|
8
8
|
confidence: z.number().min(0).max(1).optional(),
|
|
9
9
|
title: z.string().optional().nullable(),
|
|
10
10
|
value: z.string().optional().nullable(),
|
|
@@ -18,6 +18,7 @@ export type DirectMemoryIntent =
|
|
|
18
18
|
| { action: 'none'; confidence: number }
|
|
19
19
|
| { action: 'store'; confidence: number; title?: string; value: string; acknowledgement: string; exclusiveCompletion: boolean }
|
|
20
20
|
| { action: 'update'; confidence: number; title?: string; value: string; acknowledgement: string; exclusiveCompletion: boolean }
|
|
21
|
+
| { action: 'list'; confidence: number }
|
|
21
22
|
| { action: 'recall'; confidence: number; query: string; missResponse: string }
|
|
22
23
|
|
|
23
24
|
export interface DirectMemoryIntentClassifierInput {
|
|
@@ -122,6 +123,13 @@ export function parseDirectMemoryIntentResponse(text: string): DirectMemoryInten
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
if (parsed.data.action === 'list') {
|
|
127
|
+
return {
|
|
128
|
+
action: 'list',
|
|
129
|
+
confidence,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
125
133
|
const query = normalizeText(parsed.data.query)
|
|
126
134
|
if (!query) return null
|
|
127
135
|
return {
|
|
@@ -153,15 +161,17 @@ function buildDirectMemoryIntentPrompt(input: DirectMemoryIntentClassifierInput)
|
|
|
153
161
|
'Rules:',
|
|
154
162
|
'- Choose "store" when the user wants a new durable fact, preference, decision, or profile detail remembered.',
|
|
155
163
|
'- Choose "update" when the user is correcting or replacing previously remembered information.',
|
|
164
|
+
'- Choose "list" when the user wants a broad inventory of stored memories or asks what the assistant remembers in general.',
|
|
156
165
|
'- Choose "recall" when the user is asking what the assistant remembers from earlier interactions.',
|
|
157
166
|
'- Choose "none" for ordinary conversation, current-thread-only questions, file/code/document work, and anything that should not touch durable memory.',
|
|
158
167
|
'- Be conservative. If unsure, return {"action":"none","confidence":0}.',
|
|
159
168
|
'- For "store" and "update", return the durable fact in "value" and a short natural user-facing acknowledgement in "acknowledgement". Do not mention tools, memory ids, storage, creation, or updating.',
|
|
160
169
|
'- Set "exclusiveCompletion" to true only when a successful memory write fully satisfies the user turn and the assistant should stop after the acknowledgement. Set it to false when the user also asked for other work in the same turn.',
|
|
170
|
+
'- Choose "recall" for targeted lookups about a specific remembered fact. Choose "list" for broad inventory requests like listing memories or asking what is remembered overall.',
|
|
161
171
|
'- For "recall", return a concise search query in "query" and a short natural "missResponse". Do not mention tools.',
|
|
162
172
|
'',
|
|
163
173
|
'Output shape:',
|
|
164
|
-
'{"action":"none|store|update|recall","confidence":0-1,"title":"optional short title","value":"for store/update","query":"for recall","acknowledgement":"for store/update","missResponse":"for recall","exclusiveCompletion":true}',
|
|
174
|
+
'{"action":"none|store|update|recall|list","confidence":0-1,"title":"optional short title","value":"for store/update","query":"for recall","acknowledgement":"for store/update","missResponse":"for recall","exclusiveCompletion":true}',
|
|
165
175
|
'',
|
|
166
176
|
`user_message: ${JSON.stringify(message)}`,
|
|
167
177
|
`assistant_response: ${JSON.stringify(currentResponse)}`,
|
|
@@ -23,12 +23,14 @@ after(() => {
|
|
|
23
23
|
|
|
24
24
|
describe('parseClassificationResponse', () => {
|
|
25
25
|
const validJson = JSON.stringify({
|
|
26
|
+
taskIntent: 'general',
|
|
26
27
|
isDeliverableTask: true,
|
|
27
28
|
isBroadGoal: false,
|
|
28
29
|
walletIntent: 'none',
|
|
29
30
|
hasHumanSignals: false,
|
|
30
31
|
hasSignificantEvent: false,
|
|
31
32
|
isResearchSynthesis: false,
|
|
33
|
+
workType: 'general',
|
|
32
34
|
explicitToolRequests: [],
|
|
33
35
|
confidence: 0.9,
|
|
34
36
|
})
|
|
@@ -39,6 +41,8 @@ describe('parseClassificationResponse', () => {
|
|
|
39
41
|
assert.equal(result!.isDeliverableTask, true)
|
|
40
42
|
assert.equal(result!.isBroadGoal, false)
|
|
41
43
|
assert.equal(result!.walletIntent, 'none')
|
|
44
|
+
assert.equal(result!.taskIntent, 'general')
|
|
45
|
+
assert.equal(result!.workType, 'general')
|
|
42
46
|
assert.equal(result!.confidence, 0.9)
|
|
43
47
|
assert.deepEqual(result!.explicitToolRequests, [])
|
|
44
48
|
})
|
|
@@ -55,12 +59,14 @@ describe('parseClassificationResponse', () => {
|
|
|
55
59
|
|
|
56
60
|
it('tolerates extra keys in JSON', () => {
|
|
57
61
|
const withExtra = JSON.stringify({
|
|
62
|
+
taskIntent: 'general',
|
|
58
63
|
isDeliverableTask: true,
|
|
59
64
|
isBroadGoal: false,
|
|
60
65
|
walletIntent: 'none',
|
|
61
66
|
hasHumanSignals: false,
|
|
62
67
|
hasSignificantEvent: false,
|
|
63
68
|
isResearchSynthesis: false,
|
|
69
|
+
workType: 'general',
|
|
64
70
|
explicitToolRequests: ['shell'],
|
|
65
71
|
confidence: 0.85,
|
|
66
72
|
extraKey: 'should be ignored',
|
|
@@ -97,9 +103,10 @@ describe('isDeliverableTask', () => {
|
|
|
97
103
|
})
|
|
98
104
|
|
|
99
105
|
it('falls back to regex when classification is null', () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
assert.equal(
|
|
107
|
+
mod.isDeliverableTask(null, 'Create a detailed marketing report with competitor analysis and market sizing.'),
|
|
108
|
+
false,
|
|
109
|
+
)
|
|
103
110
|
})
|
|
104
111
|
})
|
|
105
112
|
|
|
@@ -114,8 +121,10 @@ describe('isBroadGoal', () => {
|
|
|
114
121
|
})
|
|
115
122
|
|
|
116
123
|
it('falls back to regex when classification is null', () => {
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
assert.equal(
|
|
125
|
+
mod.isBroadGoal(null, 'I want to build a complete e-commerce platform with user authentication, product catalog, shopping cart, and payment processing'),
|
|
126
|
+
false,
|
|
127
|
+
)
|
|
119
128
|
})
|
|
120
129
|
})
|
|
121
130
|
|
|
@@ -137,8 +146,7 @@ describe('hasWalletIntent', () => {
|
|
|
137
146
|
})
|
|
138
147
|
|
|
139
148
|
it('falls back to regex when classification is null', () => {
|
|
140
|
-
|
|
141
|
-
assert.equal(typeof result, 'boolean')
|
|
149
|
+
assert.equal(mod.hasWalletIntent(null, 'check my wallet balance'), false)
|
|
142
150
|
})
|
|
143
151
|
})
|
|
144
152
|
|
|
@@ -154,8 +162,7 @@ describe('hasTransactionalWalletIntent', () => {
|
|
|
154
162
|
})
|
|
155
163
|
|
|
156
164
|
it('falls back to regex when classification is null', () => {
|
|
157
|
-
|
|
158
|
-
assert.equal(typeof result, 'boolean')
|
|
165
|
+
assert.equal(mod.hasTransactionalWalletIntent(null, 'swap 1 ETH for USDC'), false)
|
|
159
166
|
})
|
|
160
167
|
})
|
|
161
168
|
|
|
@@ -169,11 +176,8 @@ describe('hasHumanSignals', () => {
|
|
|
169
176
|
assert.equal(mod.hasHumanSignals(makeClassification({ hasHumanSignals: false }), ''), false)
|
|
170
177
|
})
|
|
171
178
|
|
|
172
|
-
it('
|
|
173
|
-
assert.equal(mod.hasHumanSignals(null, 'my birthday is next week'),
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('regex returns false for task-only text', () => {
|
|
179
|
+
it('returns false when classification is null', () => {
|
|
180
|
+
assert.equal(mod.hasHumanSignals(null, 'my birthday is next week'), false)
|
|
177
181
|
assert.equal(mod.hasHumanSignals(null, 'deploy the app'), false)
|
|
178
182
|
})
|
|
179
183
|
})
|
|
@@ -188,12 +192,9 @@ describe('hasSignificantEvent', () => {
|
|
|
188
192
|
assert.equal(mod.hasSignificantEvent(makeClassification({ hasSignificantEvent: false }), ''), false)
|
|
189
193
|
})
|
|
190
194
|
|
|
191
|
-
it('
|
|
192
|
-
assert.equal(mod.hasSignificantEvent(null, 'I just got promoted at work'),
|
|
193
|
-
assert.equal(mod.hasSignificantEvent(null, 'my graduation ceremony is on Friday'),
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('regex returns false for non-event text', () => {
|
|
195
|
+
it('returns false when classification is null', () => {
|
|
196
|
+
assert.equal(mod.hasSignificantEvent(null, 'I just got promoted at work'), false)
|
|
197
|
+
assert.equal(mod.hasSignificantEvent(null, 'my graduation ceremony is on Friday'), false)
|
|
197
198
|
assert.equal(mod.hasSignificantEvent(null, 'fix the login bug'), false)
|
|
198
199
|
})
|
|
199
200
|
})
|
|
@@ -208,9 +209,9 @@ describe('isResearchSynthesis', () => {
|
|
|
208
209
|
assert.equal(mod.isResearchSynthesis(makeClassification({ isResearchSynthesis: false }), null), false)
|
|
209
210
|
})
|
|
210
211
|
|
|
211
|
-
it('
|
|
212
|
-
assert.equal(mod.isResearchSynthesis(null, 'research'),
|
|
213
|
-
assert.equal(mod.isResearchSynthesis(null, 'browsing'),
|
|
212
|
+
it('returns false when classification is null', () => {
|
|
213
|
+
assert.equal(mod.isResearchSynthesis(null, 'research'), false)
|
|
214
|
+
assert.equal(mod.isResearchSynthesis(null, 'browsing'), false)
|
|
214
215
|
assert.equal(mod.isResearchSynthesis(null, 'coding'), false)
|
|
215
216
|
assert.equal(mod.isResearchSynthesis(null, null), false)
|
|
216
217
|
})
|
|
@@ -223,12 +224,14 @@ describe('isResearchSynthesis', () => {
|
|
|
223
224
|
describe('classifyMessage', () => {
|
|
224
225
|
it('returns valid classification from mock generateText', async () => {
|
|
225
226
|
const mockResponse = JSON.stringify({
|
|
227
|
+
taskIntent: 'coding',
|
|
226
228
|
isDeliverableTask: true,
|
|
227
229
|
isBroadGoal: false,
|
|
228
230
|
walletIntent: 'none',
|
|
229
231
|
hasHumanSignals: false,
|
|
230
232
|
hasSignificantEvent: false,
|
|
231
233
|
isResearchSynthesis: false,
|
|
234
|
+
workType: 'coding',
|
|
232
235
|
explicitToolRequests: ['shell'],
|
|
233
236
|
confidence: 0.95,
|
|
234
237
|
})
|
|
@@ -240,7 +243,9 @@ describe('classifyMessage', () => {
|
|
|
240
243
|
|
|
241
244
|
assert.ok(result)
|
|
242
245
|
assert.equal(result!.isDeliverableTask, true)
|
|
246
|
+
assert.equal(result!.taskIntent, 'coding')
|
|
243
247
|
assert.equal(result!.walletIntent, 'none')
|
|
248
|
+
assert.equal(result!.workType, 'coding')
|
|
244
249
|
assert.deepEqual(result!.explicitToolRequests, ['shell'])
|
|
245
250
|
})
|
|
246
251
|
|
|
@@ -276,12 +281,14 @@ describe('classifyMessage', () => {
|
|
|
276
281
|
it('caches results for the same message', async () => {
|
|
277
282
|
let callCount = 0
|
|
278
283
|
const mockResponse = JSON.stringify({
|
|
284
|
+
taskIntent: 'general',
|
|
279
285
|
isDeliverableTask: false,
|
|
280
286
|
isBroadGoal: false,
|
|
281
287
|
walletIntent: 'none',
|
|
282
288
|
hasHumanSignals: false,
|
|
283
289
|
hasSignificantEvent: false,
|
|
284
290
|
isResearchSynthesis: false,
|
|
291
|
+
workType: 'general',
|
|
285
292
|
explicitToolRequests: [],
|
|
286
293
|
confidence: 0.8,
|
|
287
294
|
})
|
|
@@ -316,12 +323,17 @@ describe('classifyMessage', () => {
|
|
|
316
323
|
|
|
317
324
|
function makeClassification(overrides: Partial<import('@/lib/server/chat-execution/message-classifier').MessageClassification>): import('@/lib/server/chat-execution/message-classifier').MessageClassification {
|
|
318
325
|
return {
|
|
326
|
+
taskIntent: 'general',
|
|
319
327
|
isDeliverableTask: false,
|
|
320
328
|
isBroadGoal: false,
|
|
321
329
|
walletIntent: 'none',
|
|
322
330
|
hasHumanSignals: false,
|
|
323
331
|
hasSignificantEvent: false,
|
|
324
332
|
isResearchSynthesis: false,
|
|
333
|
+
workType: 'general',
|
|
334
|
+
wantsScreenshots: false,
|
|
335
|
+
wantsOutboundDelivery: false,
|
|
336
|
+
wantsVoiceDelivery: false,
|
|
325
337
|
explicitToolRequests: [],
|
|
326
338
|
confidence: 0.9,
|
|
327
339
|
...overrides,
|
|
@@ -15,7 +15,8 @@ import { z } from 'zod'
|
|
|
15
15
|
import { buildLLM } from '@/lib/server/build-llm'
|
|
16
16
|
import { log } from '@/lib/server/logger'
|
|
17
17
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
18
|
-
import type { Message } from '@/types'
|
|
18
|
+
import type { Message, MessageSemanticsSummary, MessageTaskIntent } from '@/types'
|
|
19
|
+
import type { DelegationWorkType } from '@/lib/server/agents/delegation-advisory'
|
|
19
20
|
|
|
20
21
|
const TAG = 'message-classifier'
|
|
21
22
|
|
|
@@ -23,18 +24,40 @@ const TAG = 'message-classifier'
|
|
|
23
24
|
// Schema
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
|
|
27
|
+
const WorkTypeSchema = z.enum(['coding', 'research', 'writing', 'review', 'operations', 'general']).optional().default('general')
|
|
28
|
+
const TaskIntentSchema = z.enum(['coding', 'research', 'browsing', 'outreach', 'scheduling', 'general']).optional().default('general')
|
|
29
|
+
|
|
26
30
|
export const MessageClassificationSchema = z.object({
|
|
31
|
+
taskIntent: TaskIntentSchema,
|
|
27
32
|
isDeliverableTask: z.boolean(),
|
|
28
33
|
isBroadGoal: z.boolean(),
|
|
29
34
|
walletIntent: z.enum(['none', 'read_only', 'transactional']),
|
|
30
35
|
hasHumanSignals: z.boolean(),
|
|
31
36
|
hasSignificantEvent: z.boolean(),
|
|
32
37
|
isResearchSynthesis: z.boolean(),
|
|
38
|
+
workType: WorkTypeSchema,
|
|
39
|
+
wantsScreenshots: z.boolean().optional().default(false),
|
|
40
|
+
wantsOutboundDelivery: z.boolean().optional().default(false),
|
|
41
|
+
wantsVoiceDelivery: z.boolean().optional().default(false),
|
|
33
42
|
explicitToolRequests: z.array(z.string()),
|
|
34
43
|
confidence: z.number().min(0).max(1),
|
|
35
44
|
})
|
|
36
45
|
|
|
37
|
-
export
|
|
46
|
+
export interface MessageClassification {
|
|
47
|
+
taskIntent: MessageTaskIntent
|
|
48
|
+
isDeliverableTask: boolean
|
|
49
|
+
isBroadGoal: boolean
|
|
50
|
+
walletIntent: 'none' | 'read_only' | 'transactional'
|
|
51
|
+
hasHumanSignals: boolean
|
|
52
|
+
hasSignificantEvent: boolean
|
|
53
|
+
isResearchSynthesis: boolean
|
|
54
|
+
workType?: DelegationWorkType
|
|
55
|
+
wantsScreenshots?: boolean
|
|
56
|
+
wantsOutboundDelivery?: boolean
|
|
57
|
+
wantsVoiceDelivery?: boolean
|
|
58
|
+
explicitToolRequests: string[]
|
|
59
|
+
confidence: number
|
|
60
|
+
}
|
|
38
61
|
|
|
39
62
|
// ---------------------------------------------------------------------------
|
|
40
63
|
// LRU Cache (module-level, keyed on sha256 of message)
|
|
@@ -76,12 +99,17 @@ function buildClassificationPrompt(message: string, recentHistory: string): stri
|
|
|
76
99
|
'Classify the user message below across multiple dimensions. Return JSON only.',
|
|
77
100
|
'',
|
|
78
101
|
'Dimensions:',
|
|
102
|
+
'- taskIntent: The primary execution intent. Use exactly one of: "coding", "research", "browsing", "outreach", "scheduling", or "general". Choose "coding" for repo/code/build/debug/edit tasks. Choose "research" for gathering current info or synthesizing sources. Choose "browsing" for page navigation, rendered-page inspection, form work, or literal browser workflows. Choose "outreach" for sending/sharing/delivering updates to an external channel. Choose "scheduling" for reminders, recurring work, monitoring, or follow-up scheduling. Choose "general" when none of the above clearly fits.',
|
|
79
103
|
'- isDeliverableTask (bool): The user wants a concrete artifact produced — a document, report, plan, proposal, landing page, dashboard, HTML file, markdown file, brief, copy, screenshots, or similar deliverable. NOT simple Q&A, code fixes, or single-command tasks.',
|
|
80
104
|
'- isBroadGoal (bool): The message describes a broad, multi-step goal (50+ chars, no code blocks, no file paths, no numbered lists). Short questions ending with "?" are NOT broad goals.',
|
|
81
105
|
'- walletIntent: "none" if no crypto/wallet/trading context. "read_only" if mentioning wallet/crypto but only for checking balances, viewing transactions, or research. "transactional" if the user wants to swap, trade, buy, sell, mint, claim, deposit, withdraw, bridge, or execute a transaction.',
|
|
82
106
|
'- hasHumanSignals (bool): The message contains personal signals — preferences ("I prefer", "call me"), relationships ("my wife", "my partner", "my kid"), life events ("birthday", "wedding", "promotion", "moving", "graduation", "hospital"), or personal disclosures.',
|
|
83
107
|
'- hasSignificantEvent (bool): The message mentions a notable life/work event or milestone (birthday, anniversary, wedding, graduation, promotion, new job, relocation, illness, funeral, travel, house, deadline, launch).',
|
|
84
108
|
'- isResearchSynthesis (bool): The task requires gathering information from multiple sources and synthesizing it — research reports, competitive analysis, market overviews, literature reviews, multi-source comparisons. NOT simple factual lookups.',
|
|
109
|
+
'- workType: The primary work domain. Use exactly one of: "coding", "research", "writing", "review", "operations", or "general". Choose "general" when nothing else clearly fits.',
|
|
110
|
+
'- wantsScreenshots (bool): The user explicitly wants screenshots, visual capture, rendered proof, or page snapshots.',
|
|
111
|
+
'- wantsOutboundDelivery (bool): The user explicitly wants the result sent, shared, delivered, posted, or messaged to an external destination/channel.',
|
|
112
|
+
'- wantsVoiceDelivery (bool): The user explicitly wants a voice note, voice memo, audio note, or voice message.',
|
|
85
113
|
'- explicitToolRequests (string[]): Tool names the user explicitly asks to use. E.g. "use the shell", "run curl", "send an email", "ask the human", "use the browser". Return canonical tool names: "shell", "email", "ask_human", "browser", "files", "web". Empty array if none.',
|
|
86
114
|
'- confidence (0-1): How confident are you in this classification overall.',
|
|
87
115
|
'',
|
|
@@ -90,9 +118,10 @@ function buildClassificationPrompt(message: string, recentHistory: string): stri
|
|
|
90
118
|
'- A message can be both a deliverable task AND a broad goal.',
|
|
91
119
|
'- "walletIntent" should be "transactional" only if the user wants to execute a state-changing action, not just discuss crypto.',
|
|
92
120
|
'- For "explicitToolRequests", only include tools the user explicitly mentions by name or clear synonym. Do not infer tool needs from the task type.',
|
|
121
|
+
'- Prefer the most execution-relevant taskIntent. Example: "research this and send me a voice note" is "research", not "outreach".',
|
|
93
122
|
'',
|
|
94
123
|
'Output shape:',
|
|
95
|
-
'{"isDeliverableTask":bool,"isBroadGoal":bool,"walletIntent":"none|read_only|transactional","hasHumanSignals":bool,"hasSignificantEvent":bool,"isResearchSynthesis":bool,"explicitToolRequests":[],"confidence":0.0-1.0}',
|
|
124
|
+
'{"taskIntent":"coding|research|browsing|outreach|scheduling|general","isDeliverableTask":bool,"isBroadGoal":bool,"walletIntent":"none|read_only|transactional","hasHumanSignals":bool,"hasSignificantEvent":bool,"isResearchSynthesis":bool,"workType":"coding|research|writing|review|operations|general","wantsScreenshots":bool,"wantsOutboundDelivery":bool,"wantsVoiceDelivery":bool,"explicitToolRequests":[],"confidence":0.0-1.0}',
|
|
96
125
|
'',
|
|
97
126
|
recentHistory ? `Recent context:\n${recentHistory}\n` : '',
|
|
98
127
|
`User message: ${JSON.stringify(message)}`,
|
|
@@ -181,7 +210,7 @@ const CLASSIFIER_TIMEOUT_MS = 2_000
|
|
|
181
210
|
|
|
182
211
|
/**
|
|
183
212
|
* Classify a user message using a single LLM call.
|
|
184
|
-
* Returns null on failure/timeout
|
|
213
|
+
* Returns null on failure/timeout so callers can fail open to neutral behavior.
|
|
185
214
|
*/
|
|
186
215
|
export async function classifyMessage(
|
|
187
216
|
input: ClassifyMessageInput,
|
|
@@ -238,47 +267,60 @@ export async function classifyMessage(
|
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
|
|
270
|
+
export function toMessageSemanticsSummary(classification: MessageClassification | null | undefined): MessageSemanticsSummary | undefined {
|
|
271
|
+
if (!classification) return undefined
|
|
272
|
+
return {
|
|
273
|
+
taskIntent: classification.taskIntent,
|
|
274
|
+
workType: classification.workType || 'general',
|
|
275
|
+
walletIntent: classification.walletIntent,
|
|
276
|
+
isDeliverableTask: classification.isDeliverableTask,
|
|
277
|
+
isBroadGoal: classification.isBroadGoal,
|
|
278
|
+
isResearchSynthesis: classification.isResearchSynthesis,
|
|
279
|
+
hasHumanSignals: classification.hasHumanSignals,
|
|
280
|
+
hasSignificantEvent: classification.hasSignificantEvent,
|
|
281
|
+
wantsScreenshots: classification.wantsScreenshots === true,
|
|
282
|
+
wantsOutboundDelivery: classification.wantsOutboundDelivery === true,
|
|
283
|
+
wantsVoiceDelivery: classification.wantsVoiceDelivery === true,
|
|
284
|
+
explicitToolRequests: [...classification.explicitToolRequests],
|
|
285
|
+
confidence: classification.confidence,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
241
289
|
// ---------------------------------------------------------------------------
|
|
242
|
-
// Adapter functions —
|
|
290
|
+
// Adapter functions — neutral defaults when classification is unavailable
|
|
243
291
|
// ---------------------------------------------------------------------------
|
|
244
292
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
looksLikeBoundedExternalExecutionTask as regexLooksLikeBoundedExternalExecutionTask,
|
|
249
|
-
looksLikeOpenEndedDeliverableTask as regexLooksLikeOpenEndedDeliverableTask,
|
|
250
|
-
} from '@/lib/server/chat-execution/stream-continuation'
|
|
251
|
-
|
|
252
|
-
export function isDeliverableTask(classification: MessageClassification | null, message: string): boolean {
|
|
253
|
-
return classification?.isDeliverableTask ?? regexLooksLikeOpenEndedDeliverableTask(message)
|
|
293
|
+
export function isDeliverableTask(classification: MessageClassification | null, message?: string): boolean {
|
|
294
|
+
void message
|
|
295
|
+
return classification?.isDeliverableTask === true
|
|
254
296
|
}
|
|
255
297
|
|
|
256
|
-
export function isBroadGoal(classification: MessageClassification | null, message
|
|
257
|
-
|
|
298
|
+
export function isBroadGoal(classification: MessageClassification | null, message?: string): boolean {
|
|
299
|
+
void message
|
|
300
|
+
return classification?.isBroadGoal === true
|
|
258
301
|
}
|
|
259
302
|
|
|
260
|
-
export function hasWalletIntent(classification: MessageClassification | null, message
|
|
261
|
-
|
|
262
|
-
return
|
|
303
|
+
export function hasWalletIntent(classification: MessageClassification | null, message?: string): boolean {
|
|
304
|
+
void message
|
|
305
|
+
return classification?.walletIntent !== undefined && classification.walletIntent !== 'none'
|
|
263
306
|
}
|
|
264
307
|
|
|
265
|
-
export function hasTransactionalWalletIntent(classification: MessageClassification | null, message
|
|
266
|
-
|
|
267
|
-
return
|
|
308
|
+
export function hasTransactionalWalletIntent(classification: MessageClassification | null, message?: string): boolean {
|
|
309
|
+
void message
|
|
310
|
+
return classification?.walletIntent === 'transactional'
|
|
268
311
|
}
|
|
269
312
|
|
|
270
|
-
export function hasHumanSignals(classification: MessageClassification | null, transcript
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return /\b(?:prefer|please|call me|don't call me|do not call me|i like|i dislike|i hate|i love|my pronouns|my partner|my wife|my husband|my kid|my child|my mom|my dad|my sister|my brother|birthday|anniversary|wedding|married|divorc|pregnan|baby|moved|moving|relocat|promotion|promoted|laid off|new job|job change|graduat|hospital|sick|illness|diagnos|passed away|funeral|grief|bereave|deadline|launch|fundraising|closing|house|home|travel)\b/i.test(transcript)
|
|
313
|
+
export function hasHumanSignals(classification: MessageClassification | null, transcript?: string): boolean {
|
|
314
|
+
void transcript
|
|
315
|
+
return classification?.hasHumanSignals === true
|
|
274
316
|
}
|
|
275
317
|
|
|
276
|
-
export function hasSignificantEvent(classification: MessageClassification | null, text
|
|
277
|
-
|
|
278
|
-
return
|
|
318
|
+
export function hasSignificantEvent(classification: MessageClassification | null, text?: string): boolean {
|
|
319
|
+
void text
|
|
320
|
+
return classification?.hasSignificantEvent === true
|
|
279
321
|
}
|
|
280
322
|
|
|
281
|
-
export function isResearchSynthesis(classification: MessageClassification | null, routingIntent
|
|
282
|
-
|
|
283
|
-
return
|
|
323
|
+
export function isResearchSynthesis(classification: MessageClassification | null, routingIntent?: string | null): boolean {
|
|
324
|
+
void routingIntent
|
|
325
|
+
return classification?.isResearchSynthesis === true
|
|
284
326
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { buildAgenticExecutionPolicy } from '@/lib/server/chat-execution/prompt-builder'
|
|
5
|
+
|
|
6
|
+
describe('buildAgenticExecutionPolicy', () => {
|
|
7
|
+
it('adds a routing matrix that teaches session introspection, durable tracking, and direct routing', () => {
|
|
8
|
+
const prompt = buildAgenticExecutionPolicy({
|
|
9
|
+
enabledExtensions: ['memory', 'manage_sessions', 'manage_tasks', 'manage_skills', 'spawn_subagent'],
|
|
10
|
+
loopMode: 'bounded',
|
|
11
|
+
heartbeatPrompt: 'HEARTBEAT',
|
|
12
|
+
heartbeatIntervalSec: 120,
|
|
13
|
+
userMessage: 'Figure out what tools you have, then continue the task.',
|
|
14
|
+
history: [],
|
|
15
|
+
mode: 'minimal',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
assert.ok(prompt.includes('## Routing Matrix'))
|
|
19
|
+
assert.ok(prompt.includes('Current-thread facts already visible in this chat'))
|
|
20
|
+
assert.ok(prompt.includes('`memory_search`'))
|
|
21
|
+
assert.ok(prompt.includes('`sessions_tool` action `identity`'))
|
|
22
|
+
assert.ok(prompt.includes('`sessions_tool` action `history`'))
|
|
23
|
+
assert.ok(prompt.includes('`manage_tasks`'))
|
|
24
|
+
assert.ok(prompt.includes('`manage_skills`'))
|
|
25
|
+
assert.ok(prompt.includes('delegate or spawn a subagent'))
|
|
26
|
+
assert.ok(prompt.includes('use the concrete tool now'))
|
|
27
|
+
assert.ok(prompt.includes('prefer the direct `manage_*` tool'))
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -202,10 +202,11 @@ export function shouldForceAttachmentFollowthrough(params: {
|
|
|
202
202
|
enabledExtensions: string[]
|
|
203
203
|
hasToolCalls: boolean
|
|
204
204
|
hasAttachmentContext: boolean
|
|
205
|
+
classification?: MessageClassification | null
|
|
205
206
|
}): boolean {
|
|
206
207
|
if (!params.hasAttachmentContext) return false
|
|
207
208
|
if (params.hasToolCalls) return false
|
|
208
|
-
const decision = routeTaskIntent(params.userMessage, params.enabledExtensions, null)
|
|
209
|
+
const decision = routeTaskIntent(params.userMessage, params.enabledExtensions, null, params.classification ?? null)
|
|
209
210
|
if (decision.intent !== 'research' && decision.intent !== 'browsing') return false
|
|
210
211
|
return decision.preferredTools.some((toolName) => extensionIdMatches(params.enabledExtensions, toolName))
|
|
211
212
|
}
|
|
@@ -326,6 +327,13 @@ export function buildAgenticExecutionPolicy(opts: {
|
|
|
326
327
|
const extensionLines = isMinimal ? [] : buildExtensionCapabilityLines(opts.enabledExtensions, { delegationEnabled: opts.delegationEnabled, agentId: opts.agentId })
|
|
327
328
|
const toolDisciplineLines = buildToolSection(opts.enabledExtensions)
|
|
328
329
|
const hasMemoryTools = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'memory')
|
|
330
|
+
const hasManageSessions = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_sessions')
|
|
331
|
+
const hasManageTasks = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_tasks')
|
|
332
|
+
const hasManageSkills = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_skills')
|
|
333
|
+
const hasDelegationTools = opts.enabledExtensions.some((toolId) => {
|
|
334
|
+
const canonical = canonicalizeExtensionId(toolId) || toolId
|
|
335
|
+
return canonical === 'delegate' || canonical === 'spawn_subagent'
|
|
336
|
+
})
|
|
329
337
|
|
|
330
338
|
const parts: string[] = []
|
|
331
339
|
|
|
@@ -351,6 +359,33 @@ export function buildAgenticExecutionPolicy(opts: {
|
|
|
351
359
|
: 'Loop: BOUNDED — execute multiple steps but finish within recursion budget.',
|
|
352
360
|
)
|
|
353
361
|
|
|
362
|
+
if (hasTooling) {
|
|
363
|
+
parts.push(
|
|
364
|
+
'## Routing Matrix',
|
|
365
|
+
'Current-thread facts already visible in this chat: answer directly from the thread before using tools.',
|
|
366
|
+
hasMemoryTools
|
|
367
|
+
? 'Facts from previous conversations: start with `memory_search`, then `memory_get` only for a targeted follow-up read.'
|
|
368
|
+
: 'Facts from previous conversations: rely on the visible thread only and state when memory tools are unavailable.',
|
|
369
|
+
hasManageSessions
|
|
370
|
+
? 'Harness/session context, lineage, project attachment, or enabled-tool questions: use `sessions_tool` action `identity`.'
|
|
371
|
+
: 'Harness/session introspection is limited here; rely on the runtime orientation block and visible context.',
|
|
372
|
+
hasManageSessions
|
|
373
|
+
? 'Earlier messages from this same session that are not already visible in the thread: use `sessions_tool` action `history`.'
|
|
374
|
+
: 'Do not claim hidden session history is checked when `sessions_tool` is unavailable.',
|
|
375
|
+
hasManageTasks
|
|
376
|
+
? 'Durable backlog or resumable progress tracking: use `manage_tasks` for multi-turn work, delegation, or explicit task-board requests.'
|
|
377
|
+
: 'Do not create pseudo-task workflows in prose when task tooling is unavailable.',
|
|
378
|
+
hasManageSkills
|
|
379
|
+
? 'Missing capability, workflow, or environment setup blocker: use `manage_skills` before repeating generic exploration.'
|
|
380
|
+
: 'If a capability is genuinely missing, say so plainly instead of pretending a skill install happened.',
|
|
381
|
+
hasDelegationTools
|
|
382
|
+
? 'Multi-step specialist work: delegate or spawn a subagent instead of doing the whole chain yourself.'
|
|
383
|
+
: 'If delegation tools are unavailable, execute directly with the tools you do have.',
|
|
384
|
+
'For direct reversible execution, use the concrete tool now instead of creating a task or stopping at advice.',
|
|
385
|
+
'When both `manage_platform` and a direct `manage_*` tool are available, prefer the direct `manage_*` tool.',
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
354
389
|
// Sections skipped in minimal mode
|
|
355
390
|
if (!isMinimal) {
|
|
356
391
|
if (hasMemoryTools) {
|
|
@@ -374,7 +409,7 @@ export function buildAgenticExecutionPolicy(opts: {
|
|
|
374
409
|
'Prefer `use_skill` action `run` for executable skills and `use_skill` action `load` only when the skill is guidance-only.',
|
|
375
410
|
)
|
|
376
411
|
}
|
|
377
|
-
if (
|
|
412
|
+
if (hasManageSkills) {
|
|
378
413
|
parts.push(
|
|
379
414
|
'## Skill Resolution',
|
|
380
415
|
'When you are blocked on a missing capability, binary, or environment setup, call `manage_skills` before repeating generic exploration.',
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import { after, before, describe, it } from 'node:test'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
3
6
|
|
|
4
7
|
let mod: typeof import('@/lib/server/chat-execution/prompt-sections')
|
|
5
8
|
|
|
@@ -86,6 +89,59 @@ describe('prompt-sections', () => {
|
|
|
86
89
|
})
|
|
87
90
|
})
|
|
88
91
|
|
|
92
|
+
describe('buildRuntimeOrientationSection', () => {
|
|
93
|
+
it('includes delegated lineage, workspace markers, project context, and routing guidance', () => {
|
|
94
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-runtime-orientation-'))
|
|
95
|
+
try {
|
|
96
|
+
fs.writeFileSync(path.join(cwd, 'AGENTS.md'), '# Agent notes')
|
|
97
|
+
const result = mod.buildRuntimeOrientationSection({
|
|
98
|
+
session: {
|
|
99
|
+
id: 'child-session',
|
|
100
|
+
cwd,
|
|
101
|
+
provider: 'openai',
|
|
102
|
+
model: 'gpt-5',
|
|
103
|
+
parentSessionId: 'parent-session',
|
|
104
|
+
agentId: 'agent-1',
|
|
105
|
+
} as never,
|
|
106
|
+
promptMode: 'minimal',
|
|
107
|
+
sessionExtensions: ['files', 'manage_sessions', 'codex_cli'],
|
|
108
|
+
toolPolicy: {
|
|
109
|
+
mode: 'balanced',
|
|
110
|
+
requestedExtensions: ['files', 'manage_sessions', 'codex_cli', 'manage_secrets'],
|
|
111
|
+
enabledExtensions: ['files', 'manage_sessions', 'codex_cli'],
|
|
112
|
+
blockedExtensions: [{ tool: 'manage_secrets', reason: 'blocked by policy', source: 'policy' }],
|
|
113
|
+
},
|
|
114
|
+
agent: {
|
|
115
|
+
id: 'agent-1',
|
|
116
|
+
name: 'Builder',
|
|
117
|
+
delegationTargetMode: 'selected',
|
|
118
|
+
delegationTargetAgentIds: ['qa-1', 'ops-1'],
|
|
119
|
+
} as never,
|
|
120
|
+
activeProjectContext: {
|
|
121
|
+
projectId: 'project-1',
|
|
122
|
+
project: { name: 'Northstar' },
|
|
123
|
+
projectRoot: '/workspace/projects/project-1',
|
|
124
|
+
} as never,
|
|
125
|
+
rootSessionId: 'root-session',
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
assert.ok(result.includes('## Runtime Orientation'))
|
|
129
|
+
assert.ok(result.includes('delegated_child'))
|
|
130
|
+
assert.ok(result.includes('prompt=minimal'))
|
|
131
|
+
assert.ok(result.includes('root=root-session'))
|
|
132
|
+
assert.ok(result.includes('Workspace markers: AGENTS.md'))
|
|
133
|
+
assert.ok(result.includes('Active project: Northstar'))
|
|
134
|
+
assert.ok(result.includes('`manage_sessions`'))
|
|
135
|
+
assert.ok(result.includes('`codex_cli`'))
|
|
136
|
+
assert.ok(result.includes('Policy blocked:'))
|
|
137
|
+
assert.ok(result.includes('sessions_tool'))
|
|
138
|
+
assert.ok(result.includes('use `manage_platform` only as fallback'))
|
|
139
|
+
} finally {
|
|
140
|
+
fs.rmSync(cwd, { recursive: true, force: true })
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
89
145
|
// ---- buildProjectSection ----
|
|
90
146
|
describe('buildProjectSection', () => {
|
|
91
147
|
it('returns null for minimal mode', () => {
|