@swarmclawai/swarmclaw 1.5.59 → 1.5.61
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 +15 -15
- package/package.json +1 -1
- package/src/app/api/chats/[id]/turns/[index]/snapshot/route.ts +90 -0
- package/src/cli/index.js +1 -0
- package/src/lib/server/agents/agent-service.ts +1 -0
- package/src/lib/server/chat-execution/prompt-sections.planning-mode.test.ts +63 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +36 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +3 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/types/agent.ts +8 -0
package/README.md
CHANGED
|
@@ -399,6 +399,21 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
+
### v1.5.61 Highlights
|
|
403
|
+
|
|
404
|
+
Adds an opt-in per-agent planning mode that rides on the existing `[MAIN_LOOP_PLAN]` token machinery.
|
|
405
|
+
|
|
406
|
+
- **`Agent.planningMode: 'off' | 'strict' | null`** — new optional field on the Agent type. Defaults to `null` (off) so existing agents are unaffected. Validated by `AgentCreateSchema` / `AgentUpdateSchema` and surfaced through `createAgent` in `agent-service.ts`.
|
|
407
|
+
- **Strict planning prompt section.** New `buildPlanningModeSection` in `prompt-sections.ts` injects a short contract into the system prompt when `planningMode === 'strict'`: before any multi-step work, emit a single-line `[MAIN_LOOP_PLAN]{"steps":...}` block. The existing parser in `main-agent-loop.ts` reads these blocks into `MainLoopState.planSteps` / `currentPlanStep` / `completedPlanSteps` with no additional wiring. Skipped in minimal prompt mode and for heartbeat turns.
|
|
408
|
+
- **Test coverage.** `prompt-sections.planning-mode.test.ts` covers the null / off / strict / minimal / missing-agent paths (6 cases).
|
|
409
|
+
|
|
410
|
+
### v1.5.60 Highlights
|
|
411
|
+
|
|
412
|
+
Adds a turn-snapshot primitive for external replay and comparison tooling, without touching the execution flow.
|
|
413
|
+
|
|
414
|
+
- **Turn snapshot endpoint.** New `GET /api/chats/:id/turns/:index/snapshot` returns the input state of a prior user turn: the message (text + optional imagePath + time), all prior messages in order, the session's effective provider/model/endpoint/credential at snapshot time, and the bound agent's provider/model/systemPrompt when available. Invalid or non-user indices return `400`, out-of-range indices return `404`. CLI: `swarmclaw chats turn-snapshot <chatId> <index>`.
|
|
415
|
+
- **Use case.** External CLIs, notebooks, and comparison harnesses can now capture the exact inputs that produced a given turn and replay them against a different model, provider, or system prompt to compare outputs — without mutating the original session. Pairs with the existing `edit-resend` path (destructive in-session replay) and the new share-link infrastructure in v1.5.59 (share the original turn's context, replay on another instance).
|
|
416
|
+
|
|
402
417
|
### v1.5.59 Highlights
|
|
403
418
|
|
|
404
419
|
Viral-loop release. Adds public share links for missions, skills, and sessions, plus a complementary raw-markdown endpoint so any shared skill installs directly through the existing `POST /api/skills/import`.
|
|
@@ -432,21 +447,6 @@ This release closes the org-orchestration feature gap with Paperclip while keepi
|
|
|
432
447
|
- **Multi-workspace scaffolding.** New `Workspace` registry with `GET|POST|PATCH|DELETE /api/workspaces` and `GET|POST /api/workspaces/active`. The default workspace seeds itself on first read; switching the active workspace persists to `workspace-registry.json`. **Note:** this is metadata only in v1.5.57 — actual data-dir forking per workspace is intentionally deferred (low-risk shipping).
|
|
433
448
|
- **CLI manifest expanded.** New top-level groups: `workspaces`, `workflow-states`, `config-versions`, `cost-attribution`, `chatroom-policy`. Run `swarmclaw workspaces list`, `swarmclaw cost-attribution by-code --query codes=client-a,range=30d`, `swarmclaw config-versions list --query entityKind=agent,entityId=...`, etc. CLI route-coverage test passes.
|
|
434
449
|
|
|
435
|
-
### v1.5.56 Highlights
|
|
436
|
-
|
|
437
|
-
- **Fix: TTS error responses are now proper JSON instead of a raw Buffer blob.** `POST /api/tts` and `POST /api/tts/stream` previously returned `500` with the error message wrapped in a `new NextResponse(string, ...)` that the CLI JSON-decoded into `{"type":"Buffer","data":[78,111,...]}`. Both routes now return `NextResponse.json({error}, {status: 500})`. Regression test added.
|
|
438
|
-
- **Zod-validated PUT/PATCH endpoints — hardening sweep.** Extends the v1.5.55 work (agents, tasks, webhooks) to close the same silent-corruption bug class on the remaining vulnerable routes: `PUT /api/secrets/:id`, `POST /api/secrets`, `PATCH /api/goals/:id`, `PUT /api/providers/:id`, `PUT /api/documents/:id`, `PUT /api/external-agents/:id`, and `PUT /api/chatrooms/:id`. Each route validates against a dedicated schema (`SecretUpdateSchema`, `SecretCreateSchema`, `GoalUpdateSchema`, `ProviderUpdateSchema`, `DocumentUpdateSchema`, `ExternalAgentUpdateSchema`, `ChatroomUpdateSchema`) in `src/lib/validation/schemas.ts`, then filters parsed data to the keys actually present in the raw body so Zod defaults can't overwrite untouched stored fields. Endpoints already doing per-field `typeof` guards (knowledge, gateways, projects) were left as-is.
|
|
439
|
-
|
|
440
|
-
### v1.5.55 Highlights
|
|
441
|
-
|
|
442
|
-
- **Fix: mission budget updates with decimal values no longer silently fail with a 400.** The mission UI's `numOrNull` parsed user input with `Number.parseFloat`, but the API requires `int()` for `maxTokens`, `maxToolCalls`, `maxWallclockSec`, and `maxTurns`. Typing `1000.5` returned a cryptic Zod error to the toast and the update was lost. Added `intOrNull` (rounds) in `mission-edit-sheet.tsx`, `mission-template-install-dialog.tsx`, and `app/missions/page.tsx`. `maxUsd` still accepts decimals.
|
|
443
|
-
- **Fix: mission edit sheet's connectors dropdown was always empty.** The sheet fetched `/connectors` expecting a `Connector[]`, but the endpoint returns `Record<string, Connector>`. The defensive `Array.isArray` fallback quietly rendered an empty list, so users could not attach report connectors when editing a running mission. Now typed as `Record<string, Connector>` and projected with `Object.values`.
|
|
444
|
-
- **Fix: memory search returns results for short (3-4 char) words like `cats`, `blue`, `dog`.** `buildFtsQuery` had a `unique[0].length >= 5` guard that returned an empty FTS query for any single-token search shorter than 5 chars, silently dropping valid searches. The upstream filter already requires ≥3 chars, so the extra guard just excluded useful queries. Removed; regression tests cover `cats`, `blue`, and `dog`.
|
|
445
|
-
- **Fix: `PUT /api/agents/:id` now validates its body with a Zod schema.** Previously the route did `{...current, ...body}` without validation, so sending `{"tools": "not_an_array"}` silently wiped the agent's tool list to `[]`. Added `AgentUpdateSchema = AgentCreateSchema.partial()` and a filter step that keeps only keys present in the raw body (so Zod defaults do not overwrite untouched fields). Bad types now return a 400 with field-level errors. `updateAgent()` keeps a `current.tools` / `current.extensions` fallback as defense-in-depth for internal callers.
|
|
446
|
-
- **Fix: `PUT /api/tasks/:id` now validates its body with a Zod schema.** Same class of bug: a numeric `title` silently corrupted the stored field. Added `TaskUpdateSchema = TaskCreateSchema.partial().extend({...})` with the update-only fields (`appendComment`, `result`, `error`, lifecycle timestamps) and the same raw-key filter pattern. Bad types now 400 with untouched storage.
|
|
447
|
-
- **Fix: `PUT /api/webhooks/:id` now validates its body with a Zod schema.** Previously `{"events": "not_an_array"}` wiped the events list. Added `WebhookUpdateSchema` and explicit `rawKeys.has(...)` guards in the mutate closure so only fields actually present in the body are applied.
|
|
448
|
-
- **Fix: classifier JSON no longer leaks into assistant responses.** Some Ollama / Ollama Cloud turns were emitting the internal `MessageClassification` object directly into the stream (e.g. `{"taskIntent":"research",...}` prepended to the real reply). The existing stripper only matched when `isDeliverableTask` was the first key, so leaks starting with `taskIntent` sailed through to the user. Replaced the regex with a principled detector that brace-matches candidate JSON (string-quote aware) and validates against `MessageClassificationSchema.safeParse` — the schema itself is the source of truth, so future schema changes can't break detection.
|
|
449
|
-
|
|
450
450
|
Older releases: https://swarmclaw.ai/docs/release-notes
|
|
451
451
|
|
|
452
452
|
- GitHub releases: https://github.com/swarmclawai/swarmclaw/releases
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.61",
|
|
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",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getSession } from '@/lib/server/sessions/session-repository'
|
|
3
|
+
import { getMessages } from '@/lib/server/messages/message-repository'
|
|
4
|
+
import { loadAgent } from '@/lib/server/agents/agent-repository'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Turn snapshot — returns the input state of the turn at :index so external
|
|
10
|
+
* tools (CLIs, notebooks, comparison harnesses) can replay the same turn
|
|
11
|
+
* against a different model, provider, or system prompt without mutating
|
|
12
|
+
* the original session.
|
|
13
|
+
*
|
|
14
|
+
* Shape is intentionally minimal and stable:
|
|
15
|
+
* - `userMessage`: the message that opened the turn (text + optional imagePath)
|
|
16
|
+
* - `priorMessages`: everything before that turn, in order
|
|
17
|
+
* - `route`: the session's effective provider/model/endpoint at snapshot time
|
|
18
|
+
* - `agent`: the agent's provider/model/systemPrompt (if bound), for reference
|
|
19
|
+
*/
|
|
20
|
+
export async function GET(
|
|
21
|
+
_req: Request,
|
|
22
|
+
ctx: { params: Promise<{ id: string; index: string }> },
|
|
23
|
+
) {
|
|
24
|
+
const { id, index } = await ctx.params
|
|
25
|
+
const session = getSession(id)
|
|
26
|
+
if (!session) {
|
|
27
|
+
return NextResponse.json({ error: 'session_not_found' }, { status: 404 })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const i = Number.parseInt(index, 10)
|
|
31
|
+
if (!Number.isInteger(i) || i < 0) {
|
|
32
|
+
return NextResponse.json({ error: 'invalid_index' }, { status: 400 })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const messages = getMessages(id)
|
|
36
|
+
if (i >= messages.length) {
|
|
37
|
+
return NextResponse.json({ error: 'index_out_of_range' }, { status: 404 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const target = messages[i]
|
|
41
|
+
if (!target || target.role !== 'user') {
|
|
42
|
+
return NextResponse.json({ error: 'not_a_user_turn' }, { status: 400 })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const priorMessages = messages.slice(0, i).map((m) => ({
|
|
46
|
+
role: m.role,
|
|
47
|
+
text: m.text || '',
|
|
48
|
+
at: typeof m.time === 'number' ? m.time : null,
|
|
49
|
+
}))
|
|
50
|
+
|
|
51
|
+
const userMessage = {
|
|
52
|
+
text: target.text || '',
|
|
53
|
+
imagePath: target.imagePath || null,
|
|
54
|
+
at: typeof target.time === 'number' ? target.time : null,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const route = {
|
|
58
|
+
provider: session.provider ?? null,
|
|
59
|
+
model: session.model ?? null,
|
|
60
|
+
apiEndpoint: session.apiEndpoint ?? null,
|
|
61
|
+
credentialId: session.credentialId ?? null,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let agent: null | {
|
|
65
|
+
id: string
|
|
66
|
+
provider: string | null
|
|
67
|
+
model: string | null
|
|
68
|
+
systemPrompt: string | null
|
|
69
|
+
} = null
|
|
70
|
+
if (session.agentId) {
|
|
71
|
+
const a = loadAgent(session.agentId)
|
|
72
|
+
if (a) {
|
|
73
|
+
agent = {
|
|
74
|
+
id: a.id,
|
|
75
|
+
provider: (a.provider as string) ?? null,
|
|
76
|
+
model: (a.model as string) ?? null,
|
|
77
|
+
systemPrompt: typeof a.systemPrompt === 'string' ? a.systemPrompt : null,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return NextResponse.json({
|
|
83
|
+
sessionId: id,
|
|
84
|
+
index: i,
|
|
85
|
+
userMessage,
|
|
86
|
+
priorMessages,
|
|
87
|
+
route,
|
|
88
|
+
agent,
|
|
89
|
+
})
|
|
90
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -579,6 +579,7 @@ const COMMAND_GROUPS = [
|
|
|
579
579
|
cmd('messages-send', 'POST', '/chats/:id/messages', 'Append a user/system message to a chat', { expectsJsonBody: true }),
|
|
580
580
|
cmd('messages-delete', 'DELETE', '/chats/:id/messages', 'Delete a message from a chat', { expectsJsonBody: true }),
|
|
581
581
|
cmd('edit-resend', 'POST', '/chats/:id/edit-resend', 'Edit and resend from a specific message index', { expectsJsonBody: true }),
|
|
582
|
+
cmd('turn-snapshot', 'GET', '/chats/:id/turns/:index/snapshot', 'Snapshot the input state of a prior user turn (for external replay)'),
|
|
582
583
|
cmd('chat', 'POST', '/chats/:id/chat', 'Send chat message (streaming)', {
|
|
583
584
|
expectsJsonBody: true,
|
|
584
585
|
responseType: 'sse',
|
|
@@ -178,6 +178,7 @@ export function createAgent(input: {
|
|
|
178
178
|
memoryTierPreference: (body.memoryTierPreference as Agent['memoryTierPreference']) || undefined,
|
|
179
179
|
proactiveMemory: body.proactiveMemory !== false,
|
|
180
180
|
autoDraftSkillSuggestions: body.autoDraftSkillSuggestions as Agent['autoDraftSkillSuggestions'],
|
|
181
|
+
planningMode: (body.planningMode as Agent['planningMode']) ?? null,
|
|
181
182
|
projectId: typeof body.projectId === 'string' && body.projectId.trim() ? body.projectId.trim() : undefined,
|
|
182
183
|
avatarSeed: typeof body.avatarSeed === 'string' ? body.avatarSeed : undefined,
|
|
183
184
|
avatarUrl: typeof body.avatarUrl === 'string' ? body.avatarUrl : undefined,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { buildPlanningModeSection } from './prompt-sections'
|
|
4
|
+
import type { Agent } from '@/types'
|
|
5
|
+
|
|
6
|
+
function agentWith(partial: Partial<Agent>): Agent {
|
|
7
|
+
return {
|
|
8
|
+
id: 'test',
|
|
9
|
+
name: 'Test',
|
|
10
|
+
provider: 'anthropic',
|
|
11
|
+
model: 'claude-sonnet-4-5',
|
|
12
|
+
credentialId: null,
|
|
13
|
+
apiEndpoint: null,
|
|
14
|
+
soul: null,
|
|
15
|
+
systemPrompt: null,
|
|
16
|
+
description: null,
|
|
17
|
+
tools: [],
|
|
18
|
+
extensions: [],
|
|
19
|
+
heartbeatEnabled: false,
|
|
20
|
+
delegationEnabled: false,
|
|
21
|
+
delegationTargetMode: 'all',
|
|
22
|
+
delegationTargetAgentIds: [],
|
|
23
|
+
skillIds: [],
|
|
24
|
+
createdAt: 0,
|
|
25
|
+
updatedAt: 0,
|
|
26
|
+
...partial,
|
|
27
|
+
} as unknown as Agent
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('buildPlanningModeSection returns null when planningMode is undefined', () => {
|
|
31
|
+
const out = buildPlanningModeSection(agentWith({}), false)
|
|
32
|
+
assert.equal(out, null)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('buildPlanningModeSection returns null when planningMode is "off"', () => {
|
|
36
|
+
const out = buildPlanningModeSection(agentWith({ planningMode: 'off' }), false)
|
|
37
|
+
assert.equal(out, null)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('buildPlanningModeSection returns null when planningMode is null', () => {
|
|
41
|
+
const out = buildPlanningModeSection(agentWith({ planningMode: null }), false)
|
|
42
|
+
assert.equal(out, null)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('buildPlanningModeSection returns null in minimal prompt mode', () => {
|
|
46
|
+
const out = buildPlanningModeSection(agentWith({ planningMode: 'strict' }), true)
|
|
47
|
+
assert.equal(out, null)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('buildPlanningModeSection returns null when agent is missing', () => {
|
|
51
|
+
assert.equal(buildPlanningModeSection(null, false), null)
|
|
52
|
+
assert.equal(buildPlanningModeSection(undefined, false), null)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('buildPlanningModeSection emits plan block guidance when strict', () => {
|
|
56
|
+
const out = buildPlanningModeSection(agentWith({ planningMode: 'strict' }), false)
|
|
57
|
+
assert.ok(out, 'should return a non-empty block')
|
|
58
|
+
assert.match(out!, /## Planning Mode: Strict/)
|
|
59
|
+
assert.match(out!, /\[MAIN_LOOP_PLAN\]/)
|
|
60
|
+
assert.match(out!, /"steps":/)
|
|
61
|
+
assert.match(out!, /current_step/)
|
|
62
|
+
assert.match(out!, /completed_steps/)
|
|
63
|
+
})
|
|
@@ -81,6 +81,42 @@ export function buildIdentitySection(
|
|
|
81
81
|
return parts
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Planning Mode — opt-in, per-agent
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* When `agent.planningMode === 'strict'`, inject a plan-enforcement section
|
|
90
|
+
* that tells the model to emit a [MAIN_LOOP_PLAN]{...} block before tool use
|
|
91
|
+
* on any multi-step turn. The existing main-agent-loop parser in
|
|
92
|
+
* `parseMainLoopPlan()` consumes these tokens and populates planSteps /
|
|
93
|
+
* currentPlanStep / completedPlanSteps in MainLoopState.
|
|
94
|
+
*
|
|
95
|
+
* Returns null when planning mode is off or minimal prompt mode is active.
|
|
96
|
+
*/
|
|
97
|
+
export function buildPlanningModeSection(
|
|
98
|
+
agent: Agent | null | undefined,
|
|
99
|
+
isMinimalPrompt: boolean,
|
|
100
|
+
): string | null {
|
|
101
|
+
if (!agent || isMinimalPrompt) return null
|
|
102
|
+
if (agent.planningMode !== 'strict') return null
|
|
103
|
+
return [
|
|
104
|
+
'## Planning Mode: Strict',
|
|
105
|
+
'',
|
|
106
|
+
'Before any multi-step work (two or more tool calls or file edits), emit a single machine-readable plan block on its own line:',
|
|
107
|
+
'',
|
|
108
|
+
'```',
|
|
109
|
+
'[MAIN_LOOP_PLAN]{"steps":["step 1","step 2","step 3"],"current_step":"step 1","completed_steps":[]}',
|
|
110
|
+
'```',
|
|
111
|
+
'',
|
|
112
|
+
'Rules:',
|
|
113
|
+
'- Each step should be a short imperative phrase (≤80 chars).',
|
|
114
|
+
'- Update `current_step` as you advance, and append finished steps to `completed_steps`.',
|
|
115
|
+
'- Skip the block for trivial single-tool responses or pure Q&A.',
|
|
116
|
+
'- The block is parsed by the runtime; do not wrap it in prose, code fences, or extra punctuation.',
|
|
117
|
+
].join('\n')
|
|
118
|
+
}
|
|
119
|
+
|
|
84
120
|
// ---------------------------------------------------------------------------
|
|
85
121
|
// Thinking Level Guidance
|
|
86
122
|
// ---------------------------------------------------------------------------
|
|
@@ -17,6 +17,7 @@ import { loadRuntimeSettings, getAgentLoopRecursionLimit } from '@/lib/server/ru
|
|
|
17
17
|
import { truncateToolResultText } from '@/lib/server/chat-execution/tool-result-guard'
|
|
18
18
|
import {
|
|
19
19
|
buildIdentitySection,
|
|
20
|
+
buildPlanningModeSection,
|
|
20
21
|
buildThinkingSection,
|
|
21
22
|
buildRuntimeOrientationSection,
|
|
22
23
|
buildWorkspaceSection,
|
|
@@ -384,6 +385,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
384
385
|
}
|
|
385
386
|
|
|
386
387
|
// Composable prompt sections — each builder returns string | null (or string[])
|
|
388
|
+
const planningBlock = buildPlanningModeSection(sessionAgent, isMinimalPrompt)
|
|
389
|
+
if (planningBlock) promptParts.push(planningBlock)
|
|
387
390
|
const thinkingBlock = buildThinkingSection(agentThinkingLevel, isMinimalPrompt)
|
|
388
391
|
if (thinkingBlock) promptParts.push(thinkingBlock)
|
|
389
392
|
const { rootSessionId } = resolveSessionLineageIds(session)
|
|
@@ -122,6 +122,7 @@ export const AgentCreateSchema = z.object({
|
|
|
122
122
|
memoryTierPreference: z.enum(['working', 'durable', 'archive', 'blended']).nullable().optional().default(null),
|
|
123
123
|
proactiveMemory: z.boolean().optional().default(true),
|
|
124
124
|
autoDraftSkillSuggestions: z.boolean().optional().default(true),
|
|
125
|
+
planningMode: z.enum(['off', 'strict']).nullable().optional().default(null),
|
|
125
126
|
projectId: z.string().optional(),
|
|
126
127
|
avatarSeed: z.string().optional(),
|
|
127
128
|
avatarUrl: z.string().nullable().optional().default(null),
|
package/src/types/agent.ts
CHANGED
|
@@ -125,6 +125,14 @@ export interface Agent {
|
|
|
125
125
|
proactiveMemory?: boolean
|
|
126
126
|
/** Auto-refresh a reviewed skill draft from meaningful chat turns for this agent. */
|
|
127
127
|
autoDraftSkillSuggestions?: boolean
|
|
128
|
+
/**
|
|
129
|
+
* Planning enforcement mode.
|
|
130
|
+
* - 'off' (default): no extra planning guidance
|
|
131
|
+
* - 'strict': instruct the model to emit a [MAIN_LOOP_PLAN] block before any
|
|
132
|
+
* tool call on multi-step turns. The existing main-agent-loop plan parser
|
|
133
|
+
* reads these blocks into MainLoopState.planSteps.
|
|
134
|
+
*/
|
|
135
|
+
planningMode?: 'off' | 'strict' | null
|
|
128
136
|
/** Controls whether file operations are confined to the workspace or allowed anywhere on the host. Default: 'workspace'. */
|
|
129
137
|
filesystemScope?: 'workspace' | 'machine' | null
|
|
130
138
|
/** Per-agent filesystem restrictions. Globs matched against resolved paths. */
|