@swarmclawai/swarmclaw 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +12 -10
  2. package/bundled-skills/google-workspace/SKILL.md +2 -0
  3. package/package.json +1 -1
  4. package/src/app/agents/page.tsx +2 -1
  5. package/src/app/api/chatrooms/[id]/chat/route.ts +1 -1
  6. package/src/app/api/clawhub/install/route.ts +2 -0
  7. package/src/app/api/skills/[id]/route.ts +4 -0
  8. package/src/app/api/skills/route.ts +4 -0
  9. package/src/app/globals.css +28 -0
  10. package/src/app/home/page.tsx +11 -0
  11. package/src/app/settings/page.tsx +12 -5
  12. package/src/components/agents/agent-sheet.tsx +5 -5
  13. package/src/components/connectors/connector-list.tsx +2 -5
  14. package/src/components/logs/log-list.tsx +2 -5
  15. package/src/components/providers/provider-list.tsx +2 -5
  16. package/src/components/runs/run-list.tsx +2 -6
  17. package/src/components/schedules/schedule-list.tsx +7 -1
  18. package/src/components/ui/full-screen-loader.tsx +0 -29
  19. package/src/components/ui/page-loader.tsx +69 -0
  20. package/src/lib/runtime/runtime-loop.ts +21 -1
  21. package/src/lib/server/agents/agent-thread-session.test.ts +64 -0
  22. package/src/lib/server/agents/agent-thread-session.ts +1 -1
  23. package/src/lib/server/agents/main-agent-loop-advanced.test.ts +77 -0
  24. package/src/lib/server/agents/main-agent-loop.ts +259 -0
  25. package/src/lib/server/agents/orchestrator-lg.ts +12 -8
  26. package/src/lib/server/agents/orchestrator.ts +11 -7
  27. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +11 -10
  28. package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +116 -3
  29. package/src/lib/server/chat-execution/chat-execution-utils.test.ts +56 -0
  30. package/src/lib/server/chat-execution/chat-execution-utils.ts +24 -0
  31. package/src/lib/server/chat-execution/chat-execution.ts +116 -29
  32. package/src/lib/server/chat-execution/chat-streaming-utils.ts +1 -38
  33. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +67 -76
  34. package/src/lib/server/chat-execution/stream-agent-chat.ts +119 -110
  35. package/src/lib/server/chat-execution/stream-continuation.ts +1 -1
  36. package/src/lib/server/chatrooms/chatroom-helpers.test.ts +26 -0
  37. package/src/lib/server/chatrooms/chatroom-helpers.ts +11 -8
  38. package/src/lib/server/connectors/contact-boundaries.ts +101 -0
  39. package/src/lib/server/connectors/manager.test.ts +504 -73
  40. package/src/lib/server/connectors/manager.ts +41 -10
  41. package/src/lib/server/connectors/session-consolidation.ts +2 -0
  42. package/src/lib/server/connectors/session-kind.ts +7 -0
  43. package/src/lib/server/connectors/session.test.ts +104 -0
  44. package/src/lib/server/connectors/session.ts +5 -2
  45. package/src/lib/server/identity-continuity.test.ts +4 -3
  46. package/src/lib/server/identity-continuity.ts +8 -4
  47. package/src/lib/server/memory/memory-policy.test.ts +5 -15
  48. package/src/lib/server/memory/memory-policy.ts +11 -41
  49. package/src/lib/server/memory/session-archive-memory.ts +2 -1
  50. package/src/lib/server/runtime/heartbeat-service.test.ts +46 -0
  51. package/src/lib/server/runtime/heartbeat-service.ts +5 -1
  52. package/src/lib/server/runtime/runtime-settings.test.ts +4 -4
  53. package/src/lib/server/runtime/runtime-settings.ts +4 -0
  54. package/src/lib/server/runtime/session-run-manager.ts +2 -0
  55. package/src/lib/server/session-reset-policy.test.ts +17 -3
  56. package/src/lib/server/session-reset-policy.ts +4 -2
  57. package/src/lib/server/session-tools/connector.ts +11 -10
  58. package/src/lib/server/session-tools/crud.ts +41 -7
  59. package/src/lib/server/session-tools/delegate.ts +3 -3
  60. package/src/lib/server/session-tools/index.ts +2 -0
  61. package/src/lib/server/session-tools/manage-skills.test.ts +194 -0
  62. package/src/lib/server/session-tools/memory.ts +209 -48
  63. package/src/lib/server/session-tools/skill-runtime.test.ts +175 -0
  64. package/src/lib/server/session-tools/skill-runtime.ts +382 -0
  65. package/src/lib/server/session-tools/skills.ts +575 -0
  66. package/src/lib/server/skills/runtime-skill-resolver.test.ts +162 -0
  67. package/src/lib/server/skills/runtime-skill-resolver.ts +750 -0
  68. package/src/lib/server/skills/skill-discovery.ts +4 -0
  69. package/src/lib/server/skills/skills-normalize.test.ts +28 -0
  70. package/src/lib/server/skills/skills-normalize.ts +93 -1
  71. package/src/lib/server/storage.ts +1 -1
  72. package/src/lib/server/tasks/task-followups.test.ts +124 -0
  73. package/src/lib/server/tasks/task-followups.ts +88 -13
  74. package/src/types/index.ts +30 -2
  75. package/src/views/settings/section-runtime-loop.tsx +38 -0
@@ -8,13 +8,11 @@ import {
8
8
  buildExternalWalletExecutionBlock,
9
9
  buildToolDisciplineLines,
10
10
  getExplicitRequiredToolNames,
11
- isNarrowDirectMemoryWriteTurn,
12
11
  isWalletSimulationResult,
13
12
  looksLikeOpenEndedDeliverableTask,
14
13
  resolveContinuationAssistantText,
15
14
  resolveFinalStreamResponseText,
16
- shouldAllowToolForDirectMemoryWrite,
17
- shouldAllowToolForCurrentThreadRecall,
15
+ shouldSkipToolSummaryForShortResponse,
18
16
  shouldForceAttachmentFollowthrough,
19
17
  shouldForceRecoverableToolErrorFollowthrough,
20
18
  shouldTerminateOnSuccessfulMemoryMutation,
@@ -55,72 +53,66 @@ describe('buildToolDisciplineLines', () => {
55
53
  assert.ok(lines.every((line) => !line.includes('Do not substitute `manage_platform`')))
56
54
  })
57
55
 
58
- it('includes concrete files-tool examples for revision work', () => {
56
+ it('includes concrete files-tool examples for file work', () => {
59
57
  const lines = buildToolDisciplineLines(['files'])
60
58
 
61
59
  assert.ok(lines.some((line) => line.includes('{"action":"read","filePath":"path/to/file.md"}')))
62
- assert.ok(lines.some((line) => line.includes('exactly N bullet points')))
63
- assert.ok(lines.some((line) => line.includes('Lower-priority logistics belong in FYI')))
60
+ assert.ok(lines.some((line) => line.includes('{"action":"list","dirPath":"."}')))
61
+ assert.ok(lines.some((line) => line.includes('{"action":"write","files":[{"path":"path/to/file.md","content":"..."}]}')))
64
62
  })
65
63
 
66
- it('adds schedule reuse and stop guidance when schedule tools are enabled', () => {
64
+ it('tells the agent to use direct schedule tools when manage_platform is absent', () => {
67
65
  const lines = buildToolDisciplineLines(['manage_schedules', 'schedule_wake'])
68
66
 
69
- assert.ok(lines.some((line) => line.includes('reuse or update matching agent-created schedules')))
70
- assert.ok(lines.some((line) => line.includes('pause or delete every matching schedule you created in this chat')))
71
- assert.ok(lines.some((line) => line.includes('prefer `schedule_wake` over creating a recurring schedule')))
67
+ assert.ok(lines.some((line) => line.includes('Use direct platform tools exactly as named (`manage_schedules`)')))
68
+ assert.ok(lines.some((line) => line.includes('Do not substitute `manage_platform` unless it is explicitly enabled.')))
72
69
  })
73
70
 
74
- it('warns browser tasks to use literal urls and the supported form schema', () => {
71
+ it('warns browser-capable sessions to use current supported tool inputs and sequencing', () => {
75
72
  const lines = buildToolDisciplineLines(['web_search', 'web_fetch', 'browser', 'manage_connectors', 'http_request', 'email', 'ask_human', 'manage_secrets'])
76
73
 
77
74
  assert.ok(lines.some((line) => line.includes('Do not invent placeholder URLs')))
78
75
  assert.ok(lines.some((line) => line.includes('A shorthand `form` object keyed by input id/name also works')))
79
- assert.ok(lines.some((line) => line.includes('prefer `fill_form` and `submit_form`')))
80
76
  assert.ok(lines.some((line) => line.includes('For current events, breaking news, or "latest" requests, start with `web_search`')))
81
77
  assert.ok(lines.some((line) => line.includes('Use `browser` when the user asks for screenshots')))
82
- assert.ok(lines.some((line) => line.includes('do not capture screenshots') && line.includes('`browser`')))
83
78
  assert.ok(lines.some((line) => line.includes('connector_message_tool') && line.includes('list_running')))
84
79
  assert.ok(lines.some((line) => line.includes('connector/channel setup is missing')))
85
- assert.ok(lines.some((line) => line.includes('capture the artifact first with `browser`') && line.includes('`connector_message_tool`')))
86
80
  assert.ok(lines.some((line) => line.includes('Keep JSON request bodies as raw JSON strings')))
87
- assert.ok(lines.some((line) => line.includes('try one other enabled acquisition path') && line.includes('`http_request`') && line.includes('`browser`')))
88
- assert.ok(lines.some((line) => line.includes('browser or web timeout is not final')))
81
+ assert.ok(lines.some((line) => line.includes('gather sources first, then capture')))
82
+ assert.ok(lines.some((line) => line.includes('If one research path is blocked, try another') && line.includes('`http_request`') && line.includes('`browser`')))
89
83
  assert.ok(lines.some((line) => line.includes('{"action":"send","to":"user@example.com","subject":"...","body":"..."}')))
90
84
  assert.ok(lines.some((line) => line.includes('do not guess or keep re-submitting blank forms')))
91
- assert.ok(lines.some((line) => line.includes('store it with `manage_secrets`') && line.includes('do not echo the raw value')))
92
- assert.ok(lines.some((line) => line.includes('Use `manage_secrets` only for sensitive credentials or tokens')))
85
+ assert.ok(lines.some((line) => line.includes('Store secrets (passwords, API keys, tokens) with `manage_secrets`')))
93
86
  })
94
87
 
95
88
  it('adds bounded execution guidance for wallet-connected external-service tasks', () => {
96
89
  const lines = buildToolDisciplineLines(['wallet', 'browser', 'http_request', 'manage_capabilities'])
97
90
 
98
- assert.ok(lines.some((line) => line.includes('inspect the available wallet first with `wallet_tool`')))
99
- assert.ok(lines.some((line) => line.includes('use a bounded loop') && line.includes('Do not keep browsing once the blocker is clear')))
100
- assert.ok(lines.some((line) => line.includes('do not shop across venues indefinitely')))
101
- assert.ok(lines.some((line) => line.includes('If a direct tool for the job is already enabled in this session, call that tool immediately')))
91
+ assert.ok(lines.some((line) => line.includes('inspect the wallet first with `wallet_tool`')))
92
+ assert.ok(lines.some((line) => line.includes('Use a bounded loop: verify, attempt one reversible step, then execute or state the blocker.')))
93
+ assert.ok(lines.some((line) => line.includes('stop venue-shopping') && line.includes('call_contract')))
102
94
  })
103
95
 
104
- it('tells agents to stay local when coding tools are already available', () => {
96
+ it('includes concrete local coding tool guidance when coding tools are already available', () => {
105
97
  const lines = buildToolDisciplineLines(['files', 'shell', 'delegate'])
106
98
 
107
- assert.ok(lines.some((line) => line.includes('prefer using them directly for straightforward coding and verification')))
99
+ assert.ok(lines.some((line) => line.includes('{"action":"read","filePath":"path/to/file.md"}')))
100
+ assert.ok(lines.some((line) => line.includes('For `shell`, use `{"action":"execute","command":"..."}`')))
108
101
  })
109
102
 
110
- it('adds explicit human-loop mailbox sequencing guidance when ask_human is enabled', () => {
103
+ it('adds explicit ask_human request and wait guidance when ask_human is enabled', () => {
111
104
  const lines = buildToolDisciplineLines(['browser', 'ask_human'])
112
105
 
113
- assert.ok(lines.some((line) => line.includes('request_input') && line.includes('wait_for_reply') && line.includes('list_mailbox')))
114
- assert.ok(lines.some((line) => line.includes('omit `envelopeId` to ack the newest unread human reply')))
115
- assert.ok(lines.some((line) => line.includes('Do not loop on `status` without a `watchJobId`')))
106
+ assert.ok(lines.some((line) => line.includes('request_input') && line.includes('wait_for_reply') && line.includes('correlationId')))
107
+ assert.ok(lines.some((line) => line.includes('do not guess or keep re-submitting blank forms')))
116
108
  })
117
109
 
118
- it('tells agents to draft email content directly and to use real file writes for named artifacts', () => {
110
+ it('tells agents how to send email and write files when those tools are enabled', () => {
119
111
  const lines = buildToolDisciplineLines(['files', 'email', 'spawn_subagent'])
120
112
 
121
- assert.ok(lines.some((line) => line.includes('draft, outline, critique, or revise email copy')))
122
- assert.ok(lines.some((line) => line.includes('make your first concrete step an actual file-writing tool call')))
123
- assert.ok(lines.some((line) => line.includes('capture the returned `swarmId`')))
113
+ assert.ok(lines.some((line) => line.includes('For `email`, send mail with `{"action":"send","to":"user@example.com","subject":"...","body":"..."}`')))
114
+ assert.ok(lines.some((line) => line.includes('If delivery depends on SMTP setup, check `{"action":"status"}` before claiming success.')))
115
+ assert.ok(lines.some((line) => line.includes('{"action":"write","files":[{"path":"path/to/file.md","content":"..."}]}')))
124
116
  })
125
117
 
126
118
  it('does not force capability-inferred tools — trusts the LLM to select tools', () => {
@@ -186,7 +178,7 @@ describe('buildToolDisciplineLines', () => {
186
178
  assert.ok(streamAgentChatSource.includes('If a task explicitly names an enabled tool, use that tool before declaring success.'))
187
179
  assert.ok(streamAgentChatSource.includes('collect required human input through the tool'))
188
180
  assert.ok(streamAgentChatSource.includes('## Attachments'))
189
- assert.ok(streamAgentChatSource.includes('Do not claim you cannot use images, attachments, or external tools when those capabilities are available in this session.'))
181
+ assert.ok(streamSources.includes('Do not claim that you cannot use images, attachments, or external tools when they are available in this session.'))
190
182
  assert.ok(streamSources.includes('You have not yet completed the required explicit tool step(s):'))
191
183
  assert.ok(streamSources.includes('attachment_followthrough'))
192
184
  assert.ok(streamSources.includes('unfinished_tool_followthrough'))
@@ -208,57 +200,16 @@ describe('buildToolDisciplineLines', () => {
208
200
  assert.ok(streamAgentChatSource.includes('did not start the required workspace tool step'))
209
201
  })
210
202
 
211
- it('adds a dedicated current-thread recall block and removes long-term memory tools for those turns', () => {
203
+ it('adds current-thread recall guidance and immediate memory routes in the system prompt', () => {
212
204
  assert.ok(streamAgentChatSource.includes('## Current Thread Recall'))
213
205
  assert.ok(streamAgentChatSource.includes('## Immediate Memory Routes'))
214
- assert.ok(streamAgentChatSource.includes('## Direct Memory Write'))
215
206
  assert.ok(streamAgentChatSource.includes('call `memory_store` or `memory_update` immediately before any planning, delegation, task creation, or agent management'))
216
- assert.ok(streamAgentChatSource.includes('Do not inspect skills, browse the workspace, request capabilities, manage tasks, manage agents, or delegate before the direct memory write is complete.'))
217
207
  assert.ok(streamAgentChatSource.includes('Do NOT call memory tools, web search, or session-history tools'))
218
- assert.ok(streamAgentChatSource.includes('const currentThreadRecallRequest = !directMemoryWriteOnlyTurn && isCurrentThreadRecallRequest(message)'))
219
- assert.ok(streamAgentChatSource.includes('const directMemoryWriteOnlyTurn = isNarrowDirectMemoryWriteTurn(message)'))
220
- assert.ok(streamAgentChatSource.includes('shouldAllowToolForDirectMemoryWrite(toolName)'))
221
- assert.ok(streamAgentChatSource.includes('shouldAllowToolForCurrentThreadRecall(toolName)'))
208
+ assert.ok(streamAgentChatSource.includes('const currentThreadRecallRequest = isCurrentThreadRecallRequest(message)'))
222
209
  assert.ok(streamSources.includes('Preserve hard structural constraints from the original request'))
223
210
  assert.ok(streamAgentChatSource.includes('## Exact Structural Constraints'))
224
211
  })
225
212
 
226
- it('blocks memory, session-history, web, and context tools during same-thread recall turns', () => {
227
- assert.equal(shouldAllowToolForCurrentThreadRecall('memory_tool'), false)
228
- assert.equal(shouldAllowToolForCurrentThreadRecall('memory_search'), false)
229
- assert.equal(shouldAllowToolForCurrentThreadRecall('memory_get'), false)
230
- assert.equal(shouldAllowToolForCurrentThreadRecall('memory_store'), false)
231
- assert.equal(shouldAllowToolForCurrentThreadRecall('memory_update'), false)
232
- assert.equal(shouldAllowToolForCurrentThreadRecall('search_history_tool'), false)
233
- assert.equal(shouldAllowToolForCurrentThreadRecall('sessions_tool'), false)
234
- assert.equal(shouldAllowToolForCurrentThreadRecall('web_search'), false)
235
- assert.equal(shouldAllowToolForCurrentThreadRecall('context_status'), false)
236
- assert.equal(shouldAllowToolForCurrentThreadRecall('files'), true)
237
- })
238
-
239
- it('only allows direct memory write tools during pure remember/store turns', () => {
240
- assert.equal(shouldAllowToolForDirectMemoryWrite('memory_store'), true)
241
- assert.equal(shouldAllowToolForDirectMemoryWrite('memory_update'), true)
242
- assert.equal(shouldAllowToolForDirectMemoryWrite('memory_tool'), false)
243
- assert.equal(shouldAllowToolForDirectMemoryWrite('manage_capabilities'), false)
244
- assert.equal(shouldAllowToolForDirectMemoryWrite('files'), false)
245
- })
246
-
247
- it('treats long remember-and-confirm turns as narrow direct memory writes', () => {
248
- assert.equal(
249
- isNarrowDirectMemoryWriteTurn('Remember that my favorite programming language is Rust and I prefer functional programming patterns. Then confirm what you just stored.'),
250
- true,
251
- )
252
- assert.equal(
253
- isNarrowDirectMemoryWriteTurn('Remember these facts for future conversations: My favorite programming language is Rust. My deploy target is Fly.io. My team size is 7 people. The project is codenamed "Neptune".'),
254
- true,
255
- )
256
- assert.equal(
257
- isNarrowDirectMemoryWriteTurn('Remember that my favorite programming language is Rust, then write a file summarizing it and send it to me.'),
258
- false,
259
- )
260
- })
261
-
262
213
  it('canonicalizes required tool names when checking completion', () => {
263
214
  // The requiredToolsPending filter must canonicalize tool names so that
264
215
  // alias names (e.g. ask_human) match canonical names from LangGraph events.
@@ -308,6 +259,46 @@ describe('isWalletSimulationResult', () => {
308
259
  })
309
260
  })
310
261
 
262
+ describe('shouldSkipToolSummaryForShortResponse', () => {
263
+ it('skips forced tool-summary continuation for short responses after pure use_skill calls', () => {
264
+ assert.equal(
265
+ shouldSkipToolSummaryForShortResponse({
266
+ fullText: 'HAL2K_RELEASE_LIVE_OK',
267
+ toolEvents: [
268
+ { name: 'use_skill', input: '{"action":"list"}', output: '{"ok":true}' },
269
+ { name: 'use_skill', input: '{"action":"load"}', output: '{"loaded":true}' },
270
+ ],
271
+ }),
272
+ true,
273
+ )
274
+ })
275
+
276
+ it('does not skip tool-summary continuation when substantive tools also ran', () => {
277
+ assert.equal(
278
+ shouldSkipToolSummaryForShortResponse({
279
+ fullText: 'Done.',
280
+ toolEvents: [
281
+ { name: 'use_skill', input: '{"action":"load"}', output: '{"loaded":true}' },
282
+ { name: 'web', input: '{"q":"latest"}', output: 'results' },
283
+ ],
284
+ }),
285
+ false,
286
+ )
287
+ })
288
+
289
+ it('does not skip tool-summary continuation for empty text', () => {
290
+ assert.equal(
291
+ shouldSkipToolSummaryForShortResponse({
292
+ fullText: '',
293
+ toolEvents: [
294
+ { name: 'use_skill', input: '{"action":"load"}', output: '{"loaded":true}' },
295
+ ],
296
+ }),
297
+ false,
298
+ )
299
+ })
300
+ })
301
+
311
302
  describe('looksLikeOpenEndedDeliverableTask', () => {
312
303
  it('detects open-ended deliverable prompts', () => {
313
304
  assert.equal(