@swarmclawai/swarmclaw 1.9.15 → 1.9.16

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 CHANGED
@@ -399,6 +399,15 @@ Operational docs: https://swarmclaw.ai/docs/observability
399
399
 
400
400
  ## Releases
401
401
 
402
+ ### v1.9.16 Highlights
403
+
404
+ Agent planning controls release: strict planning is now a first-class agent setting instead of a hidden persisted field, so operators can decide which agents must expose machine-readable plans before multi-step work.
405
+
406
+ - **Agent editor control.** Advanced agent settings now include a Standard / Strict planning selector with inline behavior guidance.
407
+ - **Runtime prompt wiring.** Strict planning continues to inject the existing `[MAIN_LOOP_PLAN]` contract before multi-step tool work, and the test suite now keeps that prompt section in the runtime gate.
408
+ - **Portable agent packs.** Agent exports preserve `planningMode`, so planning discipline follows agents across installs.
409
+ - **API coverage.** Agent create and update route tests verify that strict planning persists without clobbering unrelated settings.
410
+
402
411
  ### v1.9.15 Highlights
403
412
 
404
413
  Run handoff release: SwarmClaw now turns completed, failed, queued, or running execution records into copyable handoff packets with outcome, evidence, artifacts, timeline, usage, resume commands, and recommended next actions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.9.15",
3
+ "version": "1.9.16",
4
4
  "description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
5
5
  "main": "electron-dist/main.js",
6
6
  "license": "MIT",
@@ -88,7 +88,7 @@
88
88
  "test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/ensure-sandbox-browser-image.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
89
89
  "test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
90
90
  "test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/gateways/gateway-topology.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/gateways/topology-route.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
91
- "test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/chats/session-context-pack.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/runs/run-handoff.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-pack-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/runs/run-handoff-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
91
+ "test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/agent-planning-mode.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/prompt-sections.planning-mode.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/chats/session-context-pack.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/runs/run-handoff.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-pack-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/runs/run-handoff-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
92
92
  "test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
93
93
  "test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
94
94
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
@@ -101,6 +101,27 @@ test('POST /api/agents accepts a valid provider and creates the agent', async ()
101
101
  saveAgents(agents)
102
102
  })
103
103
 
104
+ test('POST /api/agents persists strict planning mode for created agents', async () => {
105
+ const response = await createAgent(new Request('http://local/api/agents', {
106
+ method: 'POST',
107
+ headers: { 'content-type': 'application/json' },
108
+ body: JSON.stringify({
109
+ name: 'Planning Agent',
110
+ provider: 'ollama',
111
+ model: 'qwen3.5',
112
+ planningMode: 'strict',
113
+ }),
114
+ }))
115
+
116
+ assert.equal(response.status, 200)
117
+ const body = await response.json()
118
+ assert.equal(body.planningMode, 'strict')
119
+
120
+ const agents = loadAgents()
121
+ delete agents[body.id]
122
+ saveAgents(agents)
123
+ })
124
+
104
125
  test('POST /api/agents rejects missing required fields with a 400', async () => {
105
126
  const response = await createAgent(new Request('http://local/api/agents', {
106
127
  method: 'POST',
@@ -163,6 +184,28 @@ test('PUT /api/agents/:id does not clobber untouched fields with schema defaults
163
184
  assert.equal(body.proactiveMemory, false)
164
185
  })
165
186
 
187
+ test('PUT /api/agents/:id updates planning mode without clobbering other fields', async () => {
188
+ seedAgent('agent-planning-mode', {
189
+ name: 'Planner',
190
+ tools: ['memory'],
191
+ planningMode: 'off',
192
+ proactiveMemory: false,
193
+ })
194
+
195
+ const response = await putAgent(new Request('http://local/api/agents/agent-planning-mode', {
196
+ method: 'PUT',
197
+ headers: { 'content-type': 'application/json' },
198
+ body: JSON.stringify({ planningMode: 'strict' }),
199
+ }), routeParams('agent-planning-mode'))
200
+
201
+ assert.equal(response.status, 200)
202
+ const body = await response.json()
203
+ assert.equal(body.planningMode, 'strict')
204
+ assert.equal(body.name, 'Planner')
205
+ assert.deepEqual(body.tools, ['memory'])
206
+ assert.equal(body.proactiveMemory, false)
207
+ })
208
+
166
209
  test('PUT /api/agents/:id rejects non-string name', async () => {
167
210
  seedAgent('agent-bad-name', { name: 'Good' })
168
211
 
@@ -26,6 +26,7 @@ import { StatusDot } from '@/components/ui/status-dot'
26
26
  import { resolveStoredOllamaMode } from '@/lib/ollama-mode'
27
27
  import { errorMessage } from '@/lib/shared-utils'
28
28
  import { getDefaultAgentToolIds } from '@/lib/agent-default-tools'
29
+ import { AGENT_PLANNING_MODE_OPTIONS, describeAgentPlanningMode, normalizeAgentPlanningMode, type AgentPlanningMode } from '@/lib/agent-planning-mode'
29
30
  import { getEnabledExtensionIds, getEnabledToolIds } from '@/lib/capability-selection'
30
31
  import { buildAgentSelectableProviders, resolveAgentSelectableProviderCredentials } from '@/lib/agent-provider-options'
31
32
  import { AgentSocialSettings } from '@/features/swarmfeed/agent-social-settings'
@@ -238,6 +239,7 @@ export function AgentSheet() {
238
239
  const [memoryTierPreference, setMemoryTierPreference] = useState<'working' | 'durable' | 'archive' | 'blended'>('blended')
239
240
  const [proactiveMemory, setProactiveMemory] = useState(true)
240
241
  const [autoDraftSkillSuggestions, setAutoDraftSkillSuggestions] = useState(true)
242
+ const [planningMode, setPlanningMode] = useState<AgentPlanningMode>('off')
241
243
  const [autoRecovery, setAutoRecovery] = useState(false)
242
244
  const [disabled, setDisabled] = useState(false)
243
245
  const [filesystemScope, setFilesystemScope] = useState<'workspace' | 'machine'>('workspace')
@@ -444,6 +446,7 @@ export function AgentSheet() {
444
446
  setMemoryTierPreference(editing.memoryTierPreference || 'blended')
445
447
  setProactiveMemory(editing.proactiveMemory !== false)
446
448
  setAutoDraftSkillSuggestions(editing.autoDraftSkillSuggestions !== false)
449
+ setPlanningMode(normalizeAgentPlanningMode(editing.planningMode))
447
450
  setAutoRecovery(editing.autoRecovery || false)
448
451
  setDisabled(editing.disabled === true)
449
452
  setFilesystemScope(editing.filesystemScope === 'machine' ? 'machine' : 'workspace')
@@ -527,6 +530,7 @@ export function AgentSheet() {
527
530
  setMemoryTierPreference(src.memoryTierPreference || 'blended')
528
531
  setProactiveMemory(src.proactiveMemory !== false)
529
532
  setAutoDraftSkillSuggestions(src.autoDraftSkillSuggestions !== false)
533
+ setPlanningMode(normalizeAgentPlanningMode(src.planningMode))
530
534
  setAutoRecovery(src.autoRecovery || false)
531
535
  setDisabled(false)
532
536
  setFilesystemScope(src.filesystemScope === 'machine' ? 'machine' : 'workspace')
@@ -602,6 +606,7 @@ export function AgentSheet() {
602
606
  setMemoryTierPreference('blended')
603
607
  setProactiveMemory(true)
604
608
  setAutoDraftSkillSuggestions(true)
609
+ setPlanningMode('off')
605
610
  setAutoRecovery(false)
606
611
  setDisabled(false)
607
612
  setVoiceId('')
@@ -809,6 +814,7 @@ export function AgentSheet() {
809
814
  memoryTierPreference,
810
815
  proactiveMemory,
811
816
  autoDraftSkillSuggestions,
817
+ planningMode,
812
818
  autoRecovery,
813
819
  disabled,
814
820
  filesystemScope: filesystemScope === 'machine' ? 'machine' as const : undefined,
@@ -916,6 +922,7 @@ export function AgentSheet() {
916
922
  extensions: getEnabledExtensionIds(editing),
917
923
  capabilities: editing.capabilities,
918
924
  elevenLabsVoiceId: editing.elevenLabsVoiceId || null,
925
+ planningMode: normalizeAgentPlanningMode(editing.planningMode),
919
926
  soul: editing.soul,
920
927
  systemPrompt: editing.systemPrompt,
921
928
  }],
@@ -1043,6 +1050,7 @@ export function AgentSheet() {
1043
1050
  if (projectId) badges.push('Project')
1044
1051
  if (thinkingLevel) badges.push('Thinking')
1045
1052
  if (!autoDraftSkillSuggestions) badges.push('Skill drafting')
1053
+ if (planningMode === 'strict') badges.push('Planning')
1046
1054
  return Array.from(new Set(badges))
1047
1055
  }, [
1048
1056
  autoDraftSkillSuggestions,
@@ -1061,6 +1069,7 @@ export function AgentSheet() {
1061
1069
  memoryScopeMode,
1062
1070
  memoryTierPreference,
1063
1071
  proactiveMemory,
1072
+ planningMode,
1064
1073
  projectId,
1065
1074
  routingStrategy,
1066
1075
  routingTargets.length,
@@ -2145,7 +2154,15 @@ export function AgentSheet() {
2145
2154
  <option value="durable">Durable memory</option>
2146
2155
  <option value="archive">Archive memory</option>
2147
2156
  </select>
2157
+ <select value={planningMode} onChange={(e) => setPlanningMode(normalizeAgentPlanningMode(e.target.value))} className={inputClass}>
2158
+ {AGENT_PLANNING_MODE_OPTIONS.map((option) => (
2159
+ <option key={option.value} value={option.value}>{option.label}</option>
2160
+ ))}
2161
+ </select>
2148
2162
  </div>
2163
+ <p className="mb-4 text-[12px] leading-[1.6] text-text-3/70">
2164
+ {describeAgentPlanningMode(planningMode)}
2165
+ </p>
2149
2166
  <div className="space-y-3">
2150
2167
  <label className="flex items-center gap-3 cursor-pointer">
2151
2168
  <div
@@ -0,0 +1,32 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+
4
+ import {
5
+ AGENT_PLANNING_MODE_OPTIONS,
6
+ describeAgentPlanningMode,
7
+ isAgentPlanningModeEnabled,
8
+ normalizeAgentPlanningMode,
9
+ } from './agent-planning-mode'
10
+
11
+ test('normalizeAgentPlanningMode accepts only supported persisted values', () => {
12
+ assert.equal(normalizeAgentPlanningMode('strict'), 'strict')
13
+ assert.equal(normalizeAgentPlanningMode('off'), 'off')
14
+ assert.equal(normalizeAgentPlanningMode(null), 'off')
15
+ assert.equal(normalizeAgentPlanningMode(undefined), 'off')
16
+ assert.equal(normalizeAgentPlanningMode('unexpected'), 'off')
17
+ })
18
+
19
+ test('planning mode options include a safe default and strict mode', () => {
20
+ assert.deepEqual(
21
+ AGENT_PLANNING_MODE_OPTIONS.map((option) => option.value),
22
+ ['off', 'strict'],
23
+ )
24
+ assert.equal(isAgentPlanningModeEnabled('strict'), true)
25
+ assert.equal(isAgentPlanningModeEnabled('off'), false)
26
+ assert.equal(isAgentPlanningModeEnabled(null), false)
27
+ })
28
+
29
+ test('describeAgentPlanningMode returns operator-facing copy for each mode', () => {
30
+ assert.match(describeAgentPlanningMode('off'), /No extra plan contract/)
31
+ assert.match(describeAgentPlanningMode('strict'), /machine-readable plan block/)
32
+ })
@@ -0,0 +1,34 @@
1
+ import type { Agent } from '@/types'
2
+
3
+ export type AgentPlanningMode = NonNullable<Agent['planningMode']>
4
+
5
+ export const AGENT_PLANNING_MODE_OPTIONS: ReadonlyArray<{
6
+ value: AgentPlanningMode
7
+ label: string
8
+ description: string
9
+ }> = [
10
+ {
11
+ value: 'off',
12
+ label: 'Standard',
13
+ description: 'No extra plan contract. The agent can answer, plan, or act normally based on the task.',
14
+ },
15
+ {
16
+ value: 'strict',
17
+ label: 'Strict planning',
18
+ description: 'Require a machine-readable plan block before multi-step tool work so progress can be tracked.',
19
+ },
20
+ ]
21
+
22
+ export function normalizeAgentPlanningMode(value: unknown): AgentPlanningMode {
23
+ return value === 'strict' ? 'strict' : 'off'
24
+ }
25
+
26
+ export function isAgentPlanningModeEnabled(value: Agent['planningMode'] | undefined): boolean {
27
+ return normalizeAgentPlanningMode(value) === 'strict'
28
+ }
29
+
30
+ export function describeAgentPlanningMode(value: Agent['planningMode'] | undefined): string {
31
+ const mode = normalizeAgentPlanningMode(value)
32
+ return AGENT_PLANNING_MODE_OPTIONS.find((option) => option.value === mode)?.description
33
+ || AGENT_PLANNING_MODE_OPTIONS[0].description
34
+ }
@@ -301,6 +301,7 @@ export interface AgentPackEntry {
301
301
  extensions?: string[]
302
302
  capabilities?: string[]
303
303
  elevenLabsVoiceId?: string | null
304
+ planningMode?: 'off' | 'strict' | null
304
305
  soul?: string
305
306
  systemPrompt?: string
306
307
  }