@swarmclawai/swarmclaw 1.2.4 → 1.2.6
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 +23 -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/providers/index.test.ts +108 -0
- package/src/lib/providers/index.ts +38 -15
- 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
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunContext — Persistent structured working memory for agents.
|
|
3
|
+
*
|
|
4
|
+
* Survives compaction, flows through delegation, and accumulates learnings
|
|
5
|
+
* from reflections. The session message history is the primary "memory" but
|
|
6
|
+
* it's fragile (compaction drops old messages) and isolated (workers don't
|
|
7
|
+
* see coordinator context). RunContext fixes both.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Message, RunContext, RunReflection, Session } from '@/types'
|
|
11
|
+
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
12
|
+
import { log } from '@/lib/server/logger'
|
|
13
|
+
|
|
14
|
+
const TAG = 'run-context'
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Array caps — enforced by pruneRunContext
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const CAPS: Record<string, number> = {
|
|
21
|
+
keyFacts: 20,
|
|
22
|
+
discoveries: 16,
|
|
23
|
+
failedApproaches: 16,
|
|
24
|
+
constraints: 12,
|
|
25
|
+
currentPlan: 12,
|
|
26
|
+
completedSteps: 12,
|
|
27
|
+
blockers: 8,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const PARENT_CONTEXT_BUDGET = 600
|
|
31
|
+
const RUN_CONTEXT_SECTION_BUDGET = 3000
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Core helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/** Return the existing RunContext or create a fresh empty one. */
|
|
38
|
+
export function ensureRunContext(existing: RunContext | null | undefined): RunContext {
|
|
39
|
+
if (existing && typeof existing === 'object' && typeof existing.version === 'number') {
|
|
40
|
+
// Backfill any missing array fields from malformed persisted data
|
|
41
|
+
if (!Array.isArray(existing.constraints)) existing.constraints = []
|
|
42
|
+
if (!Array.isArray(existing.keyFacts)) existing.keyFacts = []
|
|
43
|
+
if (!Array.isArray(existing.discoveries)) existing.discoveries = []
|
|
44
|
+
if (!Array.isArray(existing.failedApproaches)) existing.failedApproaches = []
|
|
45
|
+
if (!Array.isArray(existing.currentPlan)) existing.currentPlan = []
|
|
46
|
+
if (!Array.isArray(existing.completedSteps)) existing.completedSteps = []
|
|
47
|
+
if (!Array.isArray(existing.blockers)) existing.blockers = []
|
|
48
|
+
return existing
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
objective: null,
|
|
52
|
+
constraints: [],
|
|
53
|
+
keyFacts: [],
|
|
54
|
+
discoveries: [],
|
|
55
|
+
failedApproaches: [],
|
|
56
|
+
currentPlan: [],
|
|
57
|
+
completedSteps: [],
|
|
58
|
+
blockers: [],
|
|
59
|
+
parentContext: null,
|
|
60
|
+
updatedAt: Date.now(),
|
|
61
|
+
version: 0,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Normalize whitespace, trim, and case-insensitive dedup an array of strings. */
|
|
66
|
+
export function dedup(arr: string[]): string[] {
|
|
67
|
+
const seen = new Set<string>()
|
|
68
|
+
const result: string[] = []
|
|
69
|
+
for (const raw of arr) {
|
|
70
|
+
const normalized = raw.replace(/\s+/g, ' ').trim()
|
|
71
|
+
if (!normalized) continue
|
|
72
|
+
const key = normalized.toLowerCase()
|
|
73
|
+
if (seen.has(key)) continue
|
|
74
|
+
seen.add(key)
|
|
75
|
+
result.push(normalized)
|
|
76
|
+
}
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Enforce array caps on a RunContext, keeping the most recent entries. */
|
|
81
|
+
export function pruneRunContext(ctx: RunContext): RunContext {
|
|
82
|
+
const record = ctx as unknown as Record<string, unknown>
|
|
83
|
+
for (const [field, cap] of Object.entries(CAPS)) {
|
|
84
|
+
const arr = record[field]
|
|
85
|
+
if (Array.isArray(arr) && arr.length > cap) {
|
|
86
|
+
record[field] = arr.slice(-cap)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return ctx
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Reflection folding
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Map reflection note fields into RunContext fields.
|
|
98
|
+
*
|
|
99
|
+
* | RunReflection field | RunContext target |
|
|
100
|
+
* |---------------------------|---------------------|
|
|
101
|
+
* | invariantNotes | keyFacts |
|
|
102
|
+
* | lessonNotes | keyFacts |
|
|
103
|
+
* | derivedNotes | discoveries |
|
|
104
|
+
* | significantEventNotes | discoveries |
|
|
105
|
+
* | failureNotes | failedApproaches |
|
|
106
|
+
* | openLoopNotes | blockers |
|
|
107
|
+
* | boundaryNotes | constraints |
|
|
108
|
+
*/
|
|
109
|
+
export function foldReflectionIntoRunContext(
|
|
110
|
+
current: RunContext | null | undefined,
|
|
111
|
+
reflection: RunReflection,
|
|
112
|
+
): RunContext {
|
|
113
|
+
const ctx = ensureRunContext(current)
|
|
114
|
+
|
|
115
|
+
// keyFacts <- invariantNotes + lessonNotes
|
|
116
|
+
ctx.keyFacts = dedup([
|
|
117
|
+
...ctx.keyFacts,
|
|
118
|
+
...(reflection.invariantNotes || []),
|
|
119
|
+
...(reflection.lessonNotes || []),
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
// discoveries <- derivedNotes + significantEventNotes
|
|
123
|
+
ctx.discoveries = dedup([
|
|
124
|
+
...ctx.discoveries,
|
|
125
|
+
...(reflection.derivedNotes || []),
|
|
126
|
+
...(reflection.significantEventNotes || []),
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
// failedApproaches <- failureNotes
|
|
130
|
+
ctx.failedApproaches = dedup([
|
|
131
|
+
...ctx.failedApproaches,
|
|
132
|
+
...(reflection.failureNotes || []),
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
// blockers <- openLoopNotes
|
|
136
|
+
ctx.blockers = dedup([
|
|
137
|
+
...ctx.blockers,
|
|
138
|
+
...(reflection.openLoopNotes || []),
|
|
139
|
+
])
|
|
140
|
+
|
|
141
|
+
// constraints <- boundaryNotes
|
|
142
|
+
ctx.constraints = dedup([
|
|
143
|
+
...ctx.constraints,
|
|
144
|
+
...(reflection.boundaryNotes || []),
|
|
145
|
+
])
|
|
146
|
+
|
|
147
|
+
ctx.version++
|
|
148
|
+
ctx.updatedAt = Date.now()
|
|
149
|
+
return pruneRunContext(ctx)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Delegation serialization
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
/** Serialize a RunContext into a budget-capped summary string for delegation handoff. */
|
|
157
|
+
export function serializeParentContext(ctx: RunContext | null | undefined): string | null {
|
|
158
|
+
if (!ctx) return null
|
|
159
|
+
|
|
160
|
+
const parts: string[] = []
|
|
161
|
+
let budget = PARENT_CONTEXT_BUDGET
|
|
162
|
+
|
|
163
|
+
const append = (line: string): boolean => {
|
|
164
|
+
if (budget - line.length - 1 < 0) return false
|
|
165
|
+
parts.push(line)
|
|
166
|
+
budget -= line.length + 1
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (ctx.objective) append(`Objective: ${ctx.objective}`)
|
|
171
|
+
if (ctx.constraints.length > 0) append(`Constraints: ${ctx.constraints.join('; ')}`)
|
|
172
|
+
if (ctx.keyFacts.length > 0) append(`Key facts: ${ctx.keyFacts.slice(-6).join('; ')}`)
|
|
173
|
+
if (ctx.failedApproaches.length > 0) append(`Already tried (failed): ${ctx.failedApproaches.slice(-4).join('; ')}`)
|
|
174
|
+
if (ctx.blockers.length > 0) append(`Blockers: ${ctx.blockers.join('; ')}`)
|
|
175
|
+
if (ctx.discoveries.length > 0) append(`Discoveries: ${ctx.discoveries.slice(-4).join('; ')}`)
|
|
176
|
+
|
|
177
|
+
return parts.length > 0 ? parts.join('\n') : null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Pre-compaction fact extraction (regex-based, no LLM call)
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
const FACT_PATTERNS: RegExp[] = [
|
|
185
|
+
/\b(?:important|critical|key|note|remember|must|always|never|constraint|requirement|blocker|discovered|found that|turns out)\b[:\s]+(.{10,200})/gi,
|
|
186
|
+
/\b(?:error|failed|doesn't work|won't work|can't|cannot|broken)\b[:\s]+(.{10,200})/gi,
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
/** Extract lightweight facts from messages about to be compacted. */
|
|
190
|
+
export function extractFactsFromMessages(messages: Message[]): { keyFacts: string[]; failedApproaches: string[] } {
|
|
191
|
+
const keyFacts: string[] = []
|
|
192
|
+
const failedApproaches: string[] = []
|
|
193
|
+
|
|
194
|
+
for (const msg of messages) {
|
|
195
|
+
const text = msg.text || ''
|
|
196
|
+
if (!text || text.length < 20) continue
|
|
197
|
+
|
|
198
|
+
for (const pattern of FACT_PATTERNS) {
|
|
199
|
+
pattern.lastIndex = 0
|
|
200
|
+
let match: RegExpExecArray | null = pattern.exec(text)
|
|
201
|
+
while (match !== null) {
|
|
202
|
+
const fact = match[1]?.trim()
|
|
203
|
+
if (!fact || fact.length < 10) {
|
|
204
|
+
match = pattern.exec(text)
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (/\b(?:error|failed|doesn't work|won't work|can't|cannot|broken)\b/i.test(match[0])) {
|
|
209
|
+
failedApproaches.push(fact.slice(0, 200))
|
|
210
|
+
} else {
|
|
211
|
+
keyFacts.push(fact.slice(0, 200))
|
|
212
|
+
}
|
|
213
|
+
match = pattern.exec(text)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
keyFacts: dedup(keyFacts).slice(-10),
|
|
220
|
+
failedApproaches: dedup(failedApproaches).slice(-8),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Session-level update helper
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
/** Load-modify-save a session's RunContext with a version bump. */
|
|
229
|
+
export function updateSessionRunContext(
|
|
230
|
+
sessionId: string,
|
|
231
|
+
updater: (ctx: RunContext) => RunContext,
|
|
232
|
+
): void {
|
|
233
|
+
try {
|
|
234
|
+
const session = getSession(sessionId) as Session | undefined
|
|
235
|
+
if (!session) return
|
|
236
|
+
|
|
237
|
+
const ctx = ensureRunContext(session.runContext)
|
|
238
|
+
const updated = updater(ctx)
|
|
239
|
+
updated.version++
|
|
240
|
+
updated.updatedAt = Date.now()
|
|
241
|
+
session.runContext = pruneRunContext(updated)
|
|
242
|
+
saveSession(sessionId, session)
|
|
243
|
+
} catch (err: unknown) {
|
|
244
|
+
log.warn(TAG, `Failed to update RunContext for session ${sessionId}:`, err instanceof Error ? err.message : String(err))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// MainLoopState -> RunContext projection
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
/** Project orchestrator MainLoopState into the session's RunContext. */
|
|
253
|
+
export function syncMainLoopToRunContext(
|
|
254
|
+
sessionId: string,
|
|
255
|
+
mainLoopState: {
|
|
256
|
+
goal?: string | null
|
|
257
|
+
planSteps?: string[]
|
|
258
|
+
completedPlanSteps?: string[]
|
|
259
|
+
workingMemoryNotes?: string[]
|
|
260
|
+
},
|
|
261
|
+
): void {
|
|
262
|
+
try {
|
|
263
|
+
const session = getSession(sessionId) as Session | undefined
|
|
264
|
+
if (!session) return
|
|
265
|
+
|
|
266
|
+
const ctx = ensureRunContext(session.runContext)
|
|
267
|
+
|
|
268
|
+
if (mainLoopState.goal) ctx.objective = mainLoopState.goal
|
|
269
|
+
if (Array.isArray(mainLoopState.planSteps)) ctx.currentPlan = mainLoopState.planSteps
|
|
270
|
+
if (Array.isArray(mainLoopState.completedPlanSteps)) ctx.completedSteps = mainLoopState.completedPlanSteps
|
|
271
|
+
if (Array.isArray(mainLoopState.workingMemoryNotes) && mainLoopState.workingMemoryNotes.length > 0) {
|
|
272
|
+
ctx.keyFacts = dedup([...ctx.keyFacts, ...mainLoopState.workingMemoryNotes])
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
ctx.version++
|
|
276
|
+
ctx.updatedAt = Date.now()
|
|
277
|
+
session.runContext = pruneRunContext(ctx)
|
|
278
|
+
saveSession(sessionId, session)
|
|
279
|
+
} catch (err: unknown) {
|
|
280
|
+
log.warn(TAG, `Failed to sync MainLoopState to RunContext for ${sessionId}:`, err instanceof Error ? err.message : String(err))
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Prompt section rendering
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/** Build the RunContext prompt section. Returns null if nothing to render or minimal prompt. */
|
|
289
|
+
export function buildRunContextSection(
|
|
290
|
+
runContext: RunContext | null | undefined,
|
|
291
|
+
isMinimalPrompt: boolean,
|
|
292
|
+
): string | null {
|
|
293
|
+
if (isMinimalPrompt || !runContext) return null
|
|
294
|
+
|
|
295
|
+
const lines: string[] = []
|
|
296
|
+
let budget = RUN_CONTEXT_SECTION_BUDGET
|
|
297
|
+
|
|
298
|
+
const append = (line: string): boolean => {
|
|
299
|
+
if (budget - line.length - 1 < 0) return false
|
|
300
|
+
lines.push(line)
|
|
301
|
+
budget -= line.length + 1
|
|
302
|
+
return true
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Parent coordinator context
|
|
306
|
+
if (runContext.parentContext) {
|
|
307
|
+
append('## Coordinator Context')
|
|
308
|
+
append(runContext.parentContext)
|
|
309
|
+
append('')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Objective
|
|
313
|
+
if (runContext.objective) {
|
|
314
|
+
append('## Current Objective')
|
|
315
|
+
append(runContext.objective)
|
|
316
|
+
append('')
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Constraints
|
|
320
|
+
if (runContext.constraints.length > 0) {
|
|
321
|
+
append('## Constraints')
|
|
322
|
+
for (const c of runContext.constraints) append(`- ${c}`)
|
|
323
|
+
append('')
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Key facts — survive compaction
|
|
327
|
+
if (runContext.keyFacts.length > 0) {
|
|
328
|
+
append('## Key Facts')
|
|
329
|
+
for (const f of runContext.keyFacts) append(`- ${f}`)
|
|
330
|
+
append('')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Failed approaches — don't repeat these
|
|
334
|
+
if (runContext.failedApproaches.length > 0) {
|
|
335
|
+
append('## Already Tried (Failed)')
|
|
336
|
+
for (const f of runContext.failedApproaches) append(`- ${f}`)
|
|
337
|
+
append('')
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Current plan
|
|
341
|
+
if (runContext.currentPlan.length > 0) {
|
|
342
|
+
append('## Current Plan')
|
|
343
|
+
const completedSet = new Set(runContext.completedSteps.map((s) => s.toLowerCase()))
|
|
344
|
+
for (const step of runContext.currentPlan) {
|
|
345
|
+
const done = completedSet.has(step.toLowerCase())
|
|
346
|
+
append(`- [${done ? 'x' : ' '}] ${step}`)
|
|
347
|
+
}
|
|
348
|
+
append('')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Blockers
|
|
352
|
+
if (runContext.blockers.length > 0) {
|
|
353
|
+
append('## Blockers')
|
|
354
|
+
for (const b of runContext.blockers) append(`- ${b}`)
|
|
355
|
+
append('')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Discoveries
|
|
359
|
+
if (runContext.discoveries.length > 0) {
|
|
360
|
+
append('## Discoveries')
|
|
361
|
+
for (const d of runContext.discoveries) append(`- ${d}`)
|
|
362
|
+
append('')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (lines.length === 0) return null
|
|
366
|
+
return ['## Working Memory (RunContext)', '', ...lines].join('\n')
|
|
367
|
+
}
|
|
@@ -21,6 +21,7 @@ import { log } from '@/lib/server/logger'
|
|
|
21
21
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
22
22
|
import { drainSystemEvents, drainOrchestratorEvents } from '@/lib/server/runtime/system-events'
|
|
23
23
|
import { buildMissionContextBlock } from '@/lib/server/missions/mission-service'
|
|
24
|
+
import { getMessages, getRecentMessages, clearMessages } from '@/lib/server/messages/message-repository'
|
|
24
25
|
import type { Agent, AppSettings, ApprovalRequest, Chatroom, Message, Session } from '@/types'
|
|
25
26
|
import { isOrchestratorEligible } from '@/lib/orchestrator-config'
|
|
26
27
|
import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
|
|
@@ -384,7 +385,7 @@ export function buildAgentHeartbeatPrompt(
|
|
|
384
385
|
const effectiveFileContent = isHeartbeatContentEffectivelyEmpty(strippedContent) ? '' : strippedContent
|
|
385
386
|
if (effectiveFileContent) sections.push(`\nHEARTBEAT.md contents:\n${effectiveFileContent.slice(0, 2000)}`)
|
|
386
387
|
|
|
387
|
-
const recentMessages = (
|
|
388
|
+
const recentMessages = (session.id ? getRecentMessages(session.id, 5) : []) as HeartbeatPromptMessage[]
|
|
388
389
|
const recentContext = recentMessages
|
|
389
390
|
.map((m) => {
|
|
390
391
|
const text = (m.text || '').slice(0, 200)
|
|
@@ -520,9 +521,10 @@ export function heartbeatConfigForSession(
|
|
|
520
521
|
}
|
|
521
522
|
|
|
522
523
|
function lastUserMessageAt(session: HeartbeatPromptSession): number {
|
|
523
|
-
if (!
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
if (!session?.id) return 0
|
|
525
|
+
const messages = getMessages(session.id)
|
|
526
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
527
|
+
const msg = messages[i]
|
|
526
528
|
if (msg?.role === 'user' && typeof msg.time === 'number' && msg.time > 0) {
|
|
527
529
|
return msg.time
|
|
528
530
|
}
|
|
@@ -742,9 +744,9 @@ export async function tickHeartbeats() {
|
|
|
742
744
|
// Isolated mode: clear message history before each heartbeat for a fresh context
|
|
743
745
|
const resetMode = session.sessionResetMode ?? agent?.sessionResetMode
|
|
744
746
|
if (resetMode === 'isolated') {
|
|
747
|
+
clearMessages(session.id)
|
|
745
748
|
patchSession(session.id, (s) => {
|
|
746
749
|
if (!s) return s
|
|
747
|
-
s.messages = []
|
|
748
750
|
s.updatedAt = Date.now()
|
|
749
751
|
return s
|
|
750
752
|
})
|