@swarmclawai/swarmclaw 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/package.json +1 -1
- package/src/lib/runtime-loop.ts +1 -1
- package/src/lib/server/memory-policy.test.ts +4 -0
- package/src/lib/server/memory-policy.ts +1 -0
- package/src/lib/server/runtime-settings.test.ts +2 -2
- package/src/lib/server/session-tools/crud.test.ts +6 -4
- package/src/lib/server/session-tools/memory.ts +3 -1
- package/src/lib/server/stream-agent-chat.test.ts +22 -2
- package/src/lib/server/stream-agent-chat.ts +40 -9
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
|
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
The installer resolves the latest stable release tag and installs that version by default.
|
|
151
|
-
To pin a version: `SWARMCLAW_VERSION=v0.8.
|
|
151
|
+
To pin a version: `SWARMCLAW_VERSION=v0.8.2 curl ... | bash`
|
|
152
152
|
|
|
153
153
|
Or run locally from the repo (friendly for non-technical users):
|
|
154
154
|
|
|
@@ -701,7 +701,7 @@ npm run update:easy # safe update helper for local installs
|
|
|
701
701
|
SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
|
|
702
702
|
|
|
703
703
|
```bash
|
|
704
|
-
# example minor release (v0.8.
|
|
704
|
+
# example minor release (v0.8.2 style)
|
|
705
705
|
npm version minor
|
|
706
706
|
git push origin main --follow-tags
|
|
707
707
|
```
|
|
@@ -711,13 +711,14 @@ On `v*` tags, GitHub Actions will:
|
|
|
711
711
|
2. Create a GitHub Release
|
|
712
712
|
3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
|
|
713
713
|
|
|
714
|
-
#### v0.8.
|
|
714
|
+
#### v0.8.2 Release Readiness Notes
|
|
715
715
|
|
|
716
|
-
Before shipping `v0.8.
|
|
716
|
+
Before shipping `v0.8.2`, confirm the following user-facing changes are reflected in docs:
|
|
717
717
|
|
|
718
|
-
1.
|
|
719
|
-
2. Memory/tooling docs mention the
|
|
720
|
-
3.
|
|
718
|
+
1. Runtime/defaults docs mention the higher default agent recursion limit so long-running bounded turns get more headroom without custom tuning.
|
|
719
|
+
2. Memory/tooling docs mention the narrower direct-memory-write routing: remember-and-confirm turns stay on `memory_store`/`memory_update`, bundled related facts should be stored as one canonical write, and same-thread recall should not steal those turns.
|
|
720
|
+
3. File-output guidance notes that exact bullet-count and titled-section constraints are now treated as hard structure requirements during deliverable follow-through.
|
|
721
|
+
4. Site and README install/version strings are updated to `v0.8.2`, including install snippets, release notes index text, and sidebar/footer labels.
|
|
721
722
|
|
|
722
723
|
## CLI
|
|
723
724
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
package/src/lib/runtime-loop.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const CLAUDE_CODE_TIMEOUT_SEC_MAX = 7200
|
|
|
22
22
|
export const CLI_PROCESS_TIMEOUT_SEC_MIN = 10
|
|
23
23
|
export const CLI_PROCESS_TIMEOUT_SEC_MAX = 7200
|
|
24
24
|
|
|
25
|
-
export const DEFAULT_AGENT_LOOP_RECURSION_LIMIT =
|
|
25
|
+
export const DEFAULT_AGENT_LOOP_RECURSION_LIMIT = 120
|
|
26
26
|
export const DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT = 80
|
|
27
27
|
export const DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS = 16
|
|
28
28
|
export const DEFAULT_ONGOING_LOOP_MAX_ITERATIONS = 250
|
|
@@ -36,6 +36,10 @@ test('isCurrentThreadRecallRequest detects same-thread recall without matching s
|
|
|
36
36
|
isCurrentThreadRecallRequest('Remember that my favorite programming language is Rust and I prefer functional programming patterns.'),
|
|
37
37
|
false,
|
|
38
38
|
)
|
|
39
|
+
assert.equal(
|
|
40
|
+
isCurrentThreadRecallRequest('Remember that my favorite programming language is Rust and I prefer functional programming patterns. Then confirm what you just stored.'),
|
|
41
|
+
false,
|
|
42
|
+
)
|
|
39
43
|
})
|
|
40
44
|
|
|
41
45
|
test('isDirectMemoryWriteRequest detects remember-and-confirm turns without matching recall questions', () => {
|
|
@@ -29,6 +29,7 @@ export function isCurrentThreadRecallRequest(message: string): boolean {
|
|
|
29
29
|
const trimmed = normalizeWhitespace(message)
|
|
30
30
|
if (!trimmed) return false
|
|
31
31
|
if (!CURRENT_THREAD_RECALL_MARKER_RE.test(trimmed)) return false
|
|
32
|
+
if (DIRECT_MEMORY_WRITE_MARKER_RE.test(trimmed) && DIRECT_MEMORY_WRITE_FOLLOWUP_RE.test(trimmed)) return false
|
|
32
33
|
if (/\b(?:remember|store|save)\b/i.test(trimmed) && !/\?\s*$/.test(trimmed) && !/\b(?:what|which|who|when|where|did|confirm|recap|summarize|repeat|list|tell me|answer|recall)\b/i.test(trimmed)) {
|
|
33
34
|
return false
|
|
34
35
|
}
|
|
@@ -46,7 +46,7 @@ describe('runtime settings defaults', () => {
|
|
|
46
46
|
`)
|
|
47
47
|
|
|
48
48
|
assert.equal(output.settings.loopMode, 'bounded')
|
|
49
|
-
assert.equal(output.settings.agentLoopRecursionLimit,
|
|
49
|
+
assert.equal(output.settings.agentLoopRecursionLimit, 120)
|
|
50
50
|
assert.equal(output.settings.orchestratorLoopRecursionLimit, 80)
|
|
51
51
|
assert.equal(output.settings.legacyOrchestratorMaxTurns, 16)
|
|
52
52
|
assert.equal(output.settings.ongoingLoopMaxIterations, 250)
|
|
@@ -61,7 +61,7 @@ describe('runtime settings defaults', () => {
|
|
|
61
61
|
assert.equal(output.settings.heartbeatShowAlerts, true)
|
|
62
62
|
assert.equal(output.settings.heartbeatTarget, null)
|
|
63
63
|
assert.equal(output.settings.heartbeatPrompt, null)
|
|
64
|
-
assert.equal(output.runtime.agentLoopRecursionLimit,
|
|
64
|
+
assert.equal(output.runtime.agentLoopRecursionLimit, 120)
|
|
65
65
|
assert.equal(output.runtime.orchestratorLoopRecursionLimit, 80)
|
|
66
66
|
assert.equal(output.runtime.legacyOrchestratorMaxTurns, 16)
|
|
67
67
|
})
|
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import fs from 'node:fs'
|
|
4
4
|
import os from 'node:os'
|
|
5
5
|
import path from 'node:path'
|
|
6
|
+
import type { Agent } from '@/types'
|
|
6
7
|
|
|
7
8
|
const originalEnv = {
|
|
8
9
|
DATA_DIR: process.env.DATA_DIR,
|
|
@@ -30,11 +31,12 @@ before(async () => {
|
|
|
30
31
|
loadAgents = storageMod.loadAgents
|
|
31
32
|
saveAgents = storageMod.saveAgents
|
|
32
33
|
|
|
33
|
-
const agents = loadAgents({ includeTrashed: true })
|
|
34
|
+
const agents = loadAgents({ includeTrashed: true }) as Record<string, Agent>
|
|
34
35
|
agents['agent-soul-test'] = {
|
|
35
36
|
id: 'agent-soul-test',
|
|
36
37
|
name: 'Soul Test Agent',
|
|
37
38
|
description: 'Agent used for CRUD soul validation tests',
|
|
39
|
+
systemPrompt: '',
|
|
38
40
|
provider: 'ollama',
|
|
39
41
|
model: 'glm-5:cloud',
|
|
40
42
|
plugins: ['manage_agents'],
|
|
@@ -42,7 +44,7 @@ before(async () => {
|
|
|
42
44
|
platformAssignScope: 'self',
|
|
43
45
|
createdAt: Date.now(),
|
|
44
46
|
updatedAt: Date.now(),
|
|
45
|
-
}
|
|
47
|
+
}
|
|
46
48
|
saveAgents(agents)
|
|
47
49
|
})
|
|
48
50
|
|
|
@@ -124,8 +126,8 @@ describe('manage_agents soul validation', () => {
|
|
|
124
126
|
|
|
125
127
|
const first = JSON.parse(String(firstRaw)) as Record<string, unknown>
|
|
126
128
|
const second = JSON.parse(String(secondRaw)) as Record<string, unknown>
|
|
127
|
-
const created = Object.values(loadAgents({ includeTrashed: true }))
|
|
128
|
-
.filter((agent
|
|
129
|
+
const created = Object.values(loadAgents({ includeTrashed: true }) as Record<string, Agent & { createdInSessionId?: string }>)
|
|
130
|
+
.filter((agent) => agent.createdInSessionId === 'agent-dedupe-session')
|
|
129
131
|
|
|
130
132
|
assert.equal(created.length, 1)
|
|
131
133
|
assert.equal(second.id, first.id)
|
|
@@ -788,6 +788,7 @@ const MemoryPlugin: Plugin = {
|
|
|
788
788
|
'For info already in the current conversation, respond directly without calling any memory tool.',
|
|
789
789
|
'For questions about prior work, decisions, dates, people, preferences, or todos from earlier conversations: start with one durable `memory_search`, then use `memory_get` only if you need a more targeted read. Only use archive/session history when the user explicitly needs transcript-level detail or the durable search is insufficient.',
|
|
790
790
|
'When the user directly says to remember, store, or correct a fact, do one `memory_store` or `memory_update` call immediately. Treat the newest direct user statement as authoritative.',
|
|
791
|
+
'When one user message contains multiple related facts to remember, prefer one canonical `memory_store` write that captures the full set instead of many near-duplicate store calls.',
|
|
791
792
|
'If someone says "remember this", write it down; do not rely on RAM alone.',
|
|
792
793
|
'Memory writes merge canonical memories and retire superseded variants. After a successful store/update, do not keep re-searching unless the user explicitly asked you to verify.',
|
|
793
794
|
'By default, memory searches focus on durable memories. Only include archives or working execution notes when you explicitly need transcript or run-history context.',
|
|
@@ -867,7 +868,7 @@ const MemoryPlugin: Plugin = {
|
|
|
867
868
|
},
|
|
868
869
|
{
|
|
869
870
|
name: 'memory_store',
|
|
870
|
-
description: 'Store a durable fact, preference, decision, or correction from the user. Use this immediately when the user says to remember something.',
|
|
871
|
+
description: 'Store a durable fact, preference, decision, or correction from the user. Use this immediately when the user says to remember something. If several related facts arrive in one request, prefer one canonical write over many near-duplicate calls.',
|
|
871
872
|
parameters: {
|
|
872
873
|
type: 'object',
|
|
873
874
|
properties: {
|
|
@@ -884,6 +885,7 @@ const MemoryPlugin: Plugin = {
|
|
|
884
885
|
capabilities: ['memory.write'],
|
|
885
886
|
disciplineGuidance: [
|
|
886
887
|
'When the user says to remember or store a fact, call `memory_store` immediately. Do not delegate or use platform-management tools first.',
|
|
888
|
+
'If the user bundled multiple related facts into one remember request, store them together in one canonical write unless they asked for separate memories.',
|
|
887
889
|
],
|
|
888
890
|
},
|
|
889
891
|
execute: async (args, context) => executeNamedMemoryAction('store', args, context),
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
buildExternalWalletExecutionBlock,
|
|
8
8
|
buildToolDisciplineLines,
|
|
9
9
|
getExplicitRequiredToolNames,
|
|
10
|
+
isNarrowDirectMemoryWriteTurn,
|
|
10
11
|
isWalletSimulationResult,
|
|
11
12
|
looksLikeOpenEndedDeliverableTask,
|
|
12
13
|
resolveContinuationAssistantText,
|
|
@@ -39,6 +40,8 @@ describe('buildToolDisciplineLines', () => {
|
|
|
39
40
|
const lines = buildToolDisciplineLines(['files'])
|
|
40
41
|
|
|
41
42
|
assert.ok(lines.some((line) => line.includes('{"action":"read","filePath":"path/to/file.md"}')))
|
|
43
|
+
assert.ok(lines.some((line) => line.includes('exactly N bullet points')))
|
|
44
|
+
assert.ok(lines.some((line) => line.includes('Lower-priority logistics belong in FYI')))
|
|
42
45
|
})
|
|
43
46
|
|
|
44
47
|
it('adds schedule reuse and stop guidance when schedule tools are enabled', () => {
|
|
@@ -137,10 +140,12 @@ describe('buildToolDisciplineLines', () => {
|
|
|
137
140
|
assert.ok(streamAgentChatSource.includes('call `memory_store` or `memory_update` immediately before any planning, delegation, task creation, or agent management'))
|
|
138
141
|
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.'))
|
|
139
142
|
assert.ok(streamAgentChatSource.includes('Do NOT call memory tools, web search, or session-history tools'))
|
|
140
|
-
assert.ok(streamAgentChatSource.includes('const currentThreadRecallRequest = isCurrentThreadRecallRequest(message)'))
|
|
141
|
-
assert.ok(streamAgentChatSource.includes('const directMemoryWriteOnlyTurn =
|
|
143
|
+
assert.ok(streamAgentChatSource.includes('const currentThreadRecallRequest = !directMemoryWriteOnlyTurn && isCurrentThreadRecallRequest(message)'))
|
|
144
|
+
assert.ok(streamAgentChatSource.includes('const directMemoryWriteOnlyTurn = isNarrowDirectMemoryWriteTurn(message)'))
|
|
142
145
|
assert.ok(streamAgentChatSource.includes('shouldAllowToolForDirectMemoryWrite(toolName)'))
|
|
143
146
|
assert.ok(streamAgentChatSource.includes('shouldAllowToolForCurrentThreadRecall(toolName)'))
|
|
147
|
+
assert.ok(streamAgentChatSource.includes('Preserve hard structural constraints from the original request'))
|
|
148
|
+
assert.ok(streamAgentChatSource.includes('## Exact Structural Constraints'))
|
|
144
149
|
})
|
|
145
150
|
|
|
146
151
|
it('blocks memory, session-history, web, and context tools during same-thread recall turns', () => {
|
|
@@ -164,6 +169,21 @@ describe('buildToolDisciplineLines', () => {
|
|
|
164
169
|
assert.equal(shouldAllowToolForDirectMemoryWrite('files'), false)
|
|
165
170
|
})
|
|
166
171
|
|
|
172
|
+
it('treats long remember-and-confirm turns as narrow direct memory writes', () => {
|
|
173
|
+
assert.equal(
|
|
174
|
+
isNarrowDirectMemoryWriteTurn('Remember that my favorite programming language is Rust and I prefer functional programming patterns. Then confirm what you just stored.'),
|
|
175
|
+
true,
|
|
176
|
+
)
|
|
177
|
+
assert.equal(
|
|
178
|
+
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".'),
|
|
179
|
+
true,
|
|
180
|
+
)
|
|
181
|
+
assert.equal(
|
|
182
|
+
isNarrowDirectMemoryWriteTurn('Remember that my favorite programming language is Rust, then write a file summarizing it and send it to me.'),
|
|
183
|
+
false,
|
|
184
|
+
)
|
|
185
|
+
})
|
|
186
|
+
|
|
167
187
|
it('canonicalizes required tool names when checking completion', () => {
|
|
168
188
|
// The requiredToolsPending filter must canonicalize tool names so that
|
|
169
189
|
// alias names (e.g. ask_human) match canonical names from LangGraph events.
|
|
@@ -133,6 +133,11 @@ export function buildToolDisciplineLines(enabledPlugins: string[]): string[] {
|
|
|
133
133
|
lines.push('Use `manage_capabilities` only when a needed tool is actually unavailable. If a direct tool for the job is already enabled in this session, call that tool immediately instead of requesting access or re-running discovery.')
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
if (uniqueTools.includes('files') || uniqueTools.includes('edit_file')) {
|
|
137
|
+
lines.push('When the user specifies exact counts or exact section titles for file content, treat those as hard constraints. If a file must have exactly N bullet points, keep the total bullet count at N and put extra required detail into short prose under titled sections unless the user explicitly asked for more bullets.')
|
|
138
|
+
lines.push('When summarizing or restructuring a source document into named sections, make sure each top-level source section is represented somewhere in the output. Lower-priority logistics belong in FYI rather than being dropped.')
|
|
139
|
+
}
|
|
140
|
+
|
|
136
141
|
if (uniqueTools.includes('delegate') && (uniqueTools.includes('shell') || uniqueTools.includes('files') || uniqueTools.includes('edit_file'))) {
|
|
137
142
|
lines.push('When local workspace tools like `shell`, `files`, or `edit_file` are already enabled, prefer using them directly for straightforward coding and verification. Use `delegate` when you need a specialist backend, a second implementation pass, or parallel work.')
|
|
138
143
|
}
|
|
@@ -524,6 +529,7 @@ function buildDeliverableFollowthroughPrompt(params: {
|
|
|
524
529
|
'Do not stop after one partial batch. Finish every requested deliverable that is still outstanding before concluding.',
|
|
525
530
|
'If a requested artifact cannot be produced, say exactly which artifact is missing, what blocked it, and what you already completed.',
|
|
526
531
|
'Use the existing files, screenshots, and generated outputs first. Inspect them if needed, then complete the remaining work.',
|
|
532
|
+
'Preserve hard structural constraints from the original request: exact counts stay exact, required titled sections stay present, and source coverage gaps should be filled instead of skipped.',
|
|
527
533
|
'End with a concise grouped completion summary that lists exact file paths, upload URLs, localhost URLs/ports, and screenshots you produced.',
|
|
528
534
|
]
|
|
529
535
|
|
|
@@ -553,6 +559,18 @@ function buildDeliverableFollowthroughPrompt(params: {
|
|
|
553
559
|
return lines.join('\n')
|
|
554
560
|
}
|
|
555
561
|
|
|
562
|
+
function buildExactStructureBlock(userMessage: string): string {
|
|
563
|
+
const exactBulletMatch = userMessage.match(/\bexactly\s+(\d+)\s+bullet points?\b/i)
|
|
564
|
+
if (!exactBulletMatch) return ''
|
|
565
|
+
const bulletCount = exactBulletMatch[1]
|
|
566
|
+
return [
|
|
567
|
+
'## Exact Structural Constraints',
|
|
568
|
+
`The user required exactly ${bulletCount} bullet points.`,
|
|
569
|
+
'Treat that as a hard file-wide constraint unless the user explicitly says later sections get their own separate bullets.',
|
|
570
|
+
'If the file also needs titled sections such as Owners or Risks, use short prose under those headings instead of adding more bullet lines.',
|
|
571
|
+
].join('\n')
|
|
572
|
+
}
|
|
573
|
+
|
|
556
574
|
/** Detect whether a user message is a broad, high-level goal that benefits from decomposition. */
|
|
557
575
|
function isBroadGoal(text: string): boolean {
|
|
558
576
|
if (text.length < 50) return false
|
|
@@ -591,10 +609,7 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
591
609
|
const pluginLines = buildPluginCapabilityLines(opts.enabledPlugins, { platformAssignScope: opts.platformAssignScope })
|
|
592
610
|
const toolDisciplineLines = buildToolDisciplineLines(opts.enabledPlugins)
|
|
593
611
|
const hasMemoryTools = opts.enabledPlugins.some((toolId) => (canonicalizePluginId(toolId) || toolId) === 'memory')
|
|
594
|
-
const
|
|
595
|
-
const directMemoryWriteOnlyTurn = directMemoryWriteRequest
|
|
596
|
-
&& !isBroadGoal(opts.userMessage || '')
|
|
597
|
-
&& !looksLikeOpenEndedDeliverableTask(opts.userMessage || '')
|
|
612
|
+
const directMemoryWriteOnlyTurn = Boolean(opts.userMessage && isNarrowDirectMemoryWriteTurn(opts.userMessage))
|
|
598
613
|
|
|
599
614
|
const parts: string[] = []
|
|
600
615
|
|
|
@@ -657,6 +672,10 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
657
672
|
if (opts.userMessage && looksLikeOpenEndedDeliverableTask(opts.userMessage) && opts.enabledPlugins.some((toolId) => toolId === 'files' || toolId === 'edit_file')) {
|
|
658
673
|
parts.push(OPEN_ENDED_REVISION_BLOCK)
|
|
659
674
|
}
|
|
675
|
+
if (opts.userMessage) {
|
|
676
|
+
const exactStructureBlock = buildExactStructureBlock(opts.userMessage)
|
|
677
|
+
if (exactStructureBlock) parts.push(exactStructureBlock)
|
|
678
|
+
}
|
|
660
679
|
if (opts.userMessage && isCurrentThreadRecallRequest(opts.userMessage)) {
|
|
661
680
|
parts.push(buildCurrentThreadRecallBlock(opts.history || []))
|
|
662
681
|
}
|
|
@@ -711,10 +730,24 @@ function buildDirectMemoryWriteBlock(): string {
|
|
|
711
730
|
'## Direct Memory Write',
|
|
712
731
|
'This turn is a direct request to remember, store, or correct a durable fact.',
|
|
713
732
|
'Call `memory_store` or `memory_update` immediately, then confirm the stored value succinctly.',
|
|
733
|
+
'If the user bundled several related facts into one remember request, store them together in one canonical memory write unless they explicitly asked for separate entries.',
|
|
714
734
|
'Do not inspect skills, browse the workspace, request capabilities, manage tasks, manage agents, or delegate before the direct memory write is complete.',
|
|
715
735
|
].join('\n')
|
|
716
736
|
}
|
|
717
737
|
|
|
738
|
+
const DIRECT_MEMORY_WRITE_CONFIRMATION_ONLY_RE = /\b(?:then|and then|after that)?\s*(?:confirm|recap|repeat|summarize|tell me|say)\b[\s\S]{0,120}\b(?:stored|saved|updated|remembered|wrote|write)\b/i
|
|
739
|
+
const DIRECT_MEMORY_WRITE_EXTRA_ACTION_RE = /\b(?:then|and then|after that|also)\b[\s\S]{0,160}\b(?:write|create|send|email|message|delegate|research|search|browse|open|edit|build|schedule|plan|review|analy[sz]e)\b/i
|
|
740
|
+
|
|
741
|
+
export function isNarrowDirectMemoryWriteTurn(message: string): boolean {
|
|
742
|
+
const trimmed = String(message || '').trim()
|
|
743
|
+
if (!trimmed || !isDirectMemoryWriteRequest(trimmed)) return false
|
|
744
|
+
if (looksLikeOpenEndedDeliverableTask(trimmed)) return false
|
|
745
|
+
if (DIRECT_MEMORY_WRITE_EXTRA_ACTION_RE.test(trimmed) && !DIRECT_MEMORY_WRITE_CONFIRMATION_ONLY_RE.test(trimmed)) {
|
|
746
|
+
return false
|
|
747
|
+
}
|
|
748
|
+
return !isBroadGoal(trimmed) || DIRECT_MEMORY_WRITE_CONFIRMATION_ONLY_RE.test(trimmed) || !/[?]$/.test(trimmed)
|
|
749
|
+
}
|
|
750
|
+
|
|
718
751
|
const CURRENT_THREAD_RECALL_BLOCKED_TOOL_IDS = new Set([
|
|
719
752
|
'memory',
|
|
720
753
|
'manage_sessions',
|
|
@@ -840,7 +873,8 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
840
873
|
|
|
841
874
|
const stateModifierParts: string[] = []
|
|
842
875
|
const hasProvidedSystemPrompt = typeof systemPrompt === 'string' && systemPrompt.trim().length > 0
|
|
843
|
-
const
|
|
876
|
+
const directMemoryWriteOnlyTurn = isNarrowDirectMemoryWriteTurn(message)
|
|
877
|
+
const currentThreadRecallRequest = !directMemoryWriteOnlyTurn && isCurrentThreadRecallRequest(message)
|
|
844
878
|
|
|
845
879
|
if (hasProvidedSystemPrompt) {
|
|
846
880
|
stateModifierParts.push(systemPrompt!.trim())
|
|
@@ -1054,9 +1088,6 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
1054
1088
|
projectDescription: activeProjectContext.project?.description || null,
|
|
1055
1089
|
memoryScopeMode: agentMemoryScopeMode,
|
|
1056
1090
|
})
|
|
1057
|
-
const directMemoryWriteOnlyTurn = isDirectMemoryWriteRequest(message)
|
|
1058
|
-
&& !isBroadGoal(message)
|
|
1059
|
-
&& !looksLikeOpenEndedDeliverableTask(message)
|
|
1060
1091
|
const toolsForTurn = currentThreadRecallRequest
|
|
1061
1092
|
? tools.filter((tool) => {
|
|
1062
1093
|
const toolName = typeof (tool as { name?: unknown }).name === 'string'
|
|
@@ -1268,7 +1299,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
1268
1299
|
const MAX_REQUIRED_TOOL_CONTINUES = 2
|
|
1269
1300
|
const MAX_EXECUTION_FOLLOWTHROUGHS = 1
|
|
1270
1301
|
const MAX_DELIVERABLE_FOLLOWTHROUGHS = 2
|
|
1271
|
-
const MAX_TOOL_SUMMARY_RETRIES =
|
|
1302
|
+
const MAX_TOOL_SUMMARY_RETRIES = 2
|
|
1272
1303
|
let autoContinueCount = 0
|
|
1273
1304
|
let transientRetryCount = 0
|
|
1274
1305
|
let requiredToolContinueCount = 0
|