@swarmclawai/swarmclaw 1.2.4 → 1.2.5
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 +15 -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/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,60 @@
|
|
|
1
|
+
import { cleanText } from '@/lib/server/text-normalization'
|
|
2
|
+
import type { SessionWorkingState } from '@/types'
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// buildWorkingStatePromptBlock
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
function buildListSection(title: string, values: string[]): string | null {
|
|
9
|
+
if (values.length === 0) return null
|
|
10
|
+
return [title, ...values.map((value) => `- ${value}`)].join('\n')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildWorkingStatePromptBlockFromState(
|
|
14
|
+
state: SessionWorkingState | null,
|
|
15
|
+
): string {
|
|
16
|
+
if (!state) return ''
|
|
17
|
+
const activePlan = state.planSteps
|
|
18
|
+
.filter((item) => item.status === 'active')
|
|
19
|
+
.map((item) => item.text)
|
|
20
|
+
.slice(0, 8)
|
|
21
|
+
const confirmedFacts = state.confirmedFacts
|
|
22
|
+
.filter((item) => item.status === 'active')
|
|
23
|
+
.map((item) => item.statement)
|
|
24
|
+
.slice(0, 8)
|
|
25
|
+
const blockers = state.blockers
|
|
26
|
+
.filter((item) => item.status === 'active')
|
|
27
|
+
.map((item) => item.nextAction ? `${item.summary} | next: ${item.nextAction}` : item.summary)
|
|
28
|
+
.slice(0, 6)
|
|
29
|
+
const questions = state.openQuestions
|
|
30
|
+
.filter((item) => item.status === 'active')
|
|
31
|
+
.map((item) => item.question)
|
|
32
|
+
.slice(0, 6)
|
|
33
|
+
const hypotheses = state.hypotheses
|
|
34
|
+
.filter((item) => item.status === 'active')
|
|
35
|
+
.map((item) => item.confidence ? `${item.statement} (${item.confidence})` : item.statement)
|
|
36
|
+
.slice(0, 6)
|
|
37
|
+
const artifacts = state.artifacts
|
|
38
|
+
.filter((item) => item.status === 'active')
|
|
39
|
+
.map((item) => cleanText(item.path || item.url || item.label, 220))
|
|
40
|
+
.slice(0, 6)
|
|
41
|
+
|
|
42
|
+
const sections = [
|
|
43
|
+
'## Active Working State',
|
|
44
|
+
state.objective ? `Objective: ${state.objective}` : '',
|
|
45
|
+
state.summary ? `Summary: ${state.summary}` : '',
|
|
46
|
+
`Status: ${state.status}`,
|
|
47
|
+
state.nextAction ? `Next action: ${state.nextAction}` : '',
|
|
48
|
+
state.successCriteria.length > 0 ? `Success criteria: ${state.successCriteria.join(' | ')}` : '',
|
|
49
|
+
state.constraints.length > 0 ? `Constraints: ${state.constraints.join(' | ')}` : '',
|
|
50
|
+
buildListSection('Plan', activePlan),
|
|
51
|
+
buildListSection('Confirmed facts', confirmedFacts),
|
|
52
|
+
buildListSection('Blockers', blockers),
|
|
53
|
+
buildListSection('Open questions', questions),
|
|
54
|
+
buildListSection('Hypotheses', hypotheses),
|
|
55
|
+
buildListSection('Artifacts', artifacts),
|
|
56
|
+
'Trust this structured state before reconstructing status from the raw transcript.',
|
|
57
|
+
].filter(Boolean)
|
|
58
|
+
|
|
59
|
+
return sections.join('\n')
|
|
60
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { perf } from '@/lib/server/runtime/perf'
|
|
2
|
+
import {
|
|
3
|
+
deletePersistedWorkingState as deleteStoredWorkingState,
|
|
4
|
+
loadPersistedWorkingState as loadStoredWorkingState,
|
|
5
|
+
upsertPersistedWorkingState as upsertStoredWorkingState,
|
|
6
|
+
} from '@/lib/server/storage'
|
|
7
|
+
|
|
8
|
+
export type PersistedWorkingState = Record<string, unknown>
|
|
9
|
+
|
|
10
|
+
export function loadPersistedWorkingState(sessionId: string): PersistedWorkingState | null {
|
|
11
|
+
return perf.measureSync(
|
|
12
|
+
'repository',
|
|
13
|
+
'working-state.get',
|
|
14
|
+
() => loadStoredWorkingState(sessionId) as PersistedWorkingState | null,
|
|
15
|
+
{ sessionId },
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function upsertPersistedWorkingState(
|
|
20
|
+
sessionId: string,
|
|
21
|
+
value: PersistedWorkingState,
|
|
22
|
+
): void {
|
|
23
|
+
perf.measureSync(
|
|
24
|
+
'repository',
|
|
25
|
+
'working-state.upsert',
|
|
26
|
+
() => upsertStoredWorkingState(sessionId, value),
|
|
27
|
+
{ sessionId },
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function deletePersistedWorkingState(sessionId: string): void {
|
|
32
|
+
perf.measureSync(
|
|
33
|
+
'repository',
|
|
34
|
+
'working-state.delete',
|
|
35
|
+
() => deleteStoredWorkingState(sessionId),
|
|
36
|
+
{ sessionId },
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { describe, it } from 'node:test'
|
|
7
|
+
|
|
8
|
+
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../..')
|
|
9
|
+
|
|
10
|
+
function runWithTempDataDir(script: string) {
|
|
11
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-working-state-'))
|
|
12
|
+
try {
|
|
13
|
+
const result = spawnSync(
|
|
14
|
+
process.execPath,
|
|
15
|
+
['--import', 'tsx', '--input-type=module', '--eval', script],
|
|
16
|
+
{
|
|
17
|
+
cwd: repoRoot,
|
|
18
|
+
env: {
|
|
19
|
+
...process.env,
|
|
20
|
+
DATA_DIR: tempDir,
|
|
21
|
+
WORKSPACE_DIR: path.join(tempDir, 'workspace'),
|
|
22
|
+
SWARMCLAW_BUILD_MODE: '1',
|
|
23
|
+
},
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
timeout: 20000,
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
|
|
29
|
+
const lines = (result.stdout || '')
|
|
30
|
+
.trim()
|
|
31
|
+
.split('\n')
|
|
32
|
+
.map((line) => line.trim())
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
|
|
35
|
+
return JSON.parse(jsonLine || '{}') as Record<string, unknown>
|
|
36
|
+
} finally {
|
|
37
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('working-state service', () => {
|
|
42
|
+
it('merges deterministic evidence with structured extraction and renders a prompt block', () => {
|
|
43
|
+
const output = runWithTempDataDir(`
|
|
44
|
+
const storageMod = await import('@/lib/server/storage')
|
|
45
|
+
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
46
|
+
const serviceMod = await import('@/lib/server/working-state/service')
|
|
47
|
+
const service = serviceMod.default || serviceMod['module.exports'] || serviceMod
|
|
48
|
+
|
|
49
|
+
storage.saveAgents({
|
|
50
|
+
'agent-a': {
|
|
51
|
+
id: 'agent-a',
|
|
52
|
+
name: 'Agent A',
|
|
53
|
+
provider: 'openai',
|
|
54
|
+
model: 'gpt-test',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
storage.saveSessions({
|
|
59
|
+
main: {
|
|
60
|
+
id: 'main',
|
|
61
|
+
name: 'Main Thread',
|
|
62
|
+
shortcutForAgentId: 'agent-a',
|
|
63
|
+
cwd: process.cwd(),
|
|
64
|
+
user: 'tester',
|
|
65
|
+
provider: 'openai',
|
|
66
|
+
model: 'gpt-test',
|
|
67
|
+
claudeSessionId: null,
|
|
68
|
+
messages: [
|
|
69
|
+
{ role: 'user', text: 'Please fix the migration and write the summary to docs/migration.md.', time: 1 },
|
|
70
|
+
],
|
|
71
|
+
createdAt: 1,
|
|
72
|
+
lastActiveAt: 1,
|
|
73
|
+
sessionType: 'human',
|
|
74
|
+
agentId: 'agent-a',
|
|
75
|
+
heartbeatEnabled: true,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const mission = {
|
|
80
|
+
id: 'mission-1',
|
|
81
|
+
source: 'chat',
|
|
82
|
+
objective: 'Ship the migration fix safely.',
|
|
83
|
+
successCriteria: ['docs written', 'type-check green'],
|
|
84
|
+
status: 'active',
|
|
85
|
+
phase: 'executing',
|
|
86
|
+
currentStep: 'Run deploy after approval',
|
|
87
|
+
plannerSummary: 'Fix the migration, verify it, then deploy.',
|
|
88
|
+
verifierSummary: null,
|
|
89
|
+
blockerSummary: null,
|
|
90
|
+
createdAt: 1,
|
|
91
|
+
updatedAt: 2,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const state = await service.synchronizeWorkingStateForTurn({
|
|
95
|
+
sessionId: 'main',
|
|
96
|
+
agentId: 'agent-a',
|
|
97
|
+
mission,
|
|
98
|
+
source: 'chat',
|
|
99
|
+
runId: 'run-1',
|
|
100
|
+
message: 'Please fix the migration and write the summary to docs/migration.md.',
|
|
101
|
+
assistantText: 'I fixed the migration and wrote docs/migration.md.',
|
|
102
|
+
toolEvents: [
|
|
103
|
+
{
|
|
104
|
+
name: 'files',
|
|
105
|
+
input: JSON.stringify({ action: 'write', files: [{ path: 'docs/migration.md', content: '# migration' }] }),
|
|
106
|
+
output: JSON.stringify({ ok: true, files: [{ path: 'docs/migration.md' }] }),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'shell',
|
|
110
|
+
input: 'npm run type-check',
|
|
111
|
+
output: 'Type check passed. Evidence file: /api/uploads/typecheck.txt',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'deploy_release',
|
|
115
|
+
input: JSON.stringify({ env: 'prod' }),
|
|
116
|
+
output: JSON.stringify({ requiresApproval: true, approvalId: 'apr-123' }),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
}, {
|
|
120
|
+
generateText: async () => JSON.stringify({
|
|
121
|
+
status: 'blocked',
|
|
122
|
+
nextAction: 'Wait for approval apr-123, then run the deploy step.',
|
|
123
|
+
planSteps: [
|
|
124
|
+
{ text: 'Fix the migration build issue', status: 'resolved' },
|
|
125
|
+
{ text: 'Wait for approval apr-123', status: 'active' },
|
|
126
|
+
],
|
|
127
|
+
factsUpsert: [
|
|
128
|
+
{ statement: 'Type check passed after the migration update.', source: 'tool' },
|
|
129
|
+
],
|
|
130
|
+
decisionsAppend: [
|
|
131
|
+
{ summary: 'Capture the migration summary in docs/migration.md.', rationale: 'Keep the fix durable.' },
|
|
132
|
+
],
|
|
133
|
+
blockersUpsert: [
|
|
134
|
+
{ summary: 'Approval apr-123 is pending.', kind: 'approval', nextAction: 'Wait for approval apr-123', status: 'active' },
|
|
135
|
+
],
|
|
136
|
+
hypothesesUpsert: [
|
|
137
|
+
{ statement: 'After approval, deploy should be the only remaining step.', confidence: 'medium', status: 'active' },
|
|
138
|
+
],
|
|
139
|
+
}),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const prompt = service.buildWorkingStatePromptBlock('main', { mission })
|
|
143
|
+
console.log(JSON.stringify({
|
|
144
|
+
status: state.status,
|
|
145
|
+
nextAction: state.nextAction,
|
|
146
|
+
planSteps: state.planSteps.map((step) => ({ text: step.text, status: step.status })),
|
|
147
|
+
facts: state.confirmedFacts.map((fact) => fact.statement),
|
|
148
|
+
blockers: state.blockers.map((blocker) => blocker.summary),
|
|
149
|
+
artifacts: state.artifacts.map((artifact) => artifact.path || artifact.url || artifact.label),
|
|
150
|
+
prompt,
|
|
151
|
+
}))
|
|
152
|
+
`)
|
|
153
|
+
|
|
154
|
+
assert.equal(output.status, 'blocked')
|
|
155
|
+
assert.match(String(output.nextAction), /approval apr-123/i)
|
|
156
|
+
assert.ok(Array.isArray(output.planSteps) && output.planSteps.some((step) => /wait for approval apr-123/i.test(String((step as Record<string, unknown>).text))))
|
|
157
|
+
assert.ok(Array.isArray(output.facts) && output.facts.some((fact) => /type check passed/i.test(String(fact))))
|
|
158
|
+
assert.ok(Array.isArray(output.blockers) && output.blockers.some((blocker) => /approval apr-123 is pending/i.test(String(blocker))))
|
|
159
|
+
assert.ok(Array.isArray(output.artifacts) && output.artifacts.some((artifact) => /docs\/migration\.md/i.test(String(artifact))))
|
|
160
|
+
assert.match(String(output.prompt), /Active Working State/)
|
|
161
|
+
assert.match(String(output.prompt), /docs\/migration\.md/)
|
|
162
|
+
assert.match(String(output.prompt), /approval apr-123/i)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('keeps the main loop hydrated from working state and mirrors main-loop updates back', () => {
|
|
166
|
+
const output = runWithTempDataDir(`
|
|
167
|
+
const storageMod = await import('@/lib/server/storage')
|
|
168
|
+
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
169
|
+
const mainLoopMod = await import('@/lib/server/agents/main-agent-loop')
|
|
170
|
+
const mainLoop = mainLoopMod.default || mainLoopMod['module.exports'] || mainLoopMod
|
|
171
|
+
const serviceMod = await import('@/lib/server/working-state/service')
|
|
172
|
+
const service = serviceMod.default || serviceMod['module.exports'] || serviceMod
|
|
173
|
+
|
|
174
|
+
storage.saveAgents({
|
|
175
|
+
'agent-a': {
|
|
176
|
+
id: 'agent-a',
|
|
177
|
+
name: 'Agent A',
|
|
178
|
+
provider: 'openai',
|
|
179
|
+
model: 'gpt-test',
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
storage.saveSessions({
|
|
184
|
+
main: {
|
|
185
|
+
id: 'main',
|
|
186
|
+
name: 'Main Thread',
|
|
187
|
+
shortcutForAgentId: 'agent-a',
|
|
188
|
+
cwd: process.cwd(),
|
|
189
|
+
user: 'tester',
|
|
190
|
+
provider: 'openai',
|
|
191
|
+
model: 'gpt-test',
|
|
192
|
+
claudeSessionId: null,
|
|
193
|
+
messages: [
|
|
194
|
+
{ role: 'user', text: 'Ship the release checklist.', time: 1 },
|
|
195
|
+
],
|
|
196
|
+
createdAt: 1,
|
|
197
|
+
lastActiveAt: 1,
|
|
198
|
+
sessionType: 'human',
|
|
199
|
+
agentId: 'agent-a',
|
|
200
|
+
heartbeatEnabled: true,
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
service.applyWorkingStatePatch('main', {
|
|
205
|
+
objective: 'Ship the release checklist.',
|
|
206
|
+
status: 'progress',
|
|
207
|
+
nextAction: 'Verify the changelog output.',
|
|
208
|
+
planSteps: [
|
|
209
|
+
{ text: 'Verify the changelog output.', status: 'active' },
|
|
210
|
+
{ text: 'Publish the release notes.', status: 'resolved' },
|
|
211
|
+
],
|
|
212
|
+
blockersUpsert: [
|
|
213
|
+
{ summary: 'Waiting on release manager approval.', kind: 'approval', status: 'active' },
|
|
214
|
+
],
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const before = mainLoop.getMainLoopStateForSession('main')
|
|
218
|
+
const prompt = mainLoop.buildMainLoopHeartbeatPrompt(storage.loadSessions().main, 'fallback heartbeat')
|
|
219
|
+
|
|
220
|
+
mainLoop.handleMainLoopRunResult({
|
|
221
|
+
sessionId: 'main',
|
|
222
|
+
message: 'Continue the release checklist.',
|
|
223
|
+
internal: true,
|
|
224
|
+
source: 'heartbeat',
|
|
225
|
+
resultText: [
|
|
226
|
+
'Updated the changelog and prepared the release notes.',
|
|
227
|
+
'[MAIN_LOOP_PLAN]{"steps":["publish the release notes"],"current_step":"publish the release notes","completed_steps":["verify the changelog output"]}',
|
|
228
|
+
'[MAIN_LOOP_REVIEW]{"note":"changelog verified","confidence":0.91,"needs_replan":false}',
|
|
229
|
+
'[AGENT_HEARTBEAT_META]{"goal":"Ship the release checklist","status":"progress","next_action":"publish the release notes"}',
|
|
230
|
+
].join('\\n'),
|
|
231
|
+
toolEvents: [{ name: 'shell', input: 'npm run type-check', output: 'ok' }],
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const after = mainLoop.getMainLoopStateForSession('main')
|
|
235
|
+
const working = service.loadSessionWorkingState('main')
|
|
236
|
+
|
|
237
|
+
console.log(JSON.stringify({
|
|
238
|
+
beforeNextAction: before?.nextAction || null,
|
|
239
|
+
prompt,
|
|
240
|
+
afterNextAction: after?.nextAction || null,
|
|
241
|
+
workingNextAction: working?.nextAction || null,
|
|
242
|
+
workingPlanSteps: working?.planSteps?.map((step) => ({ text: step.text, status: step.status })) || [],
|
|
243
|
+
}))
|
|
244
|
+
`)
|
|
245
|
+
|
|
246
|
+
assert.match(String(output.beforeNextAction), /verify the changelog output/i)
|
|
247
|
+
assert.match(String(output.prompt), /verify the changelog output/i)
|
|
248
|
+
assert.match(String(output.prompt), /waiting on release manager approval/i)
|
|
249
|
+
assert.match(String(output.afterNextAction), /publish the release notes/i)
|
|
250
|
+
assert.match(String(output.workingNextAction), /publish the release notes/i)
|
|
251
|
+
assert.ok(Array.isArray(output.workingPlanSteps) && output.workingPlanSteps.some((step) => /publish the release notes/i.test(String((step as Record<string, unknown>).text))))
|
|
252
|
+
})
|
|
253
|
+
})
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
|
+
import { cleanText, cleanMultiline, normalizeList } from '@/lib/server/text-normalization'
|
|
3
|
+
import type {
|
|
4
|
+
Mission,
|
|
5
|
+
SessionWorkingState,
|
|
6
|
+
WorkingBlocker,
|
|
7
|
+
WorkingPlanStepPatch,
|
|
8
|
+
WorkingStatePatch,
|
|
9
|
+
WorkingStateItemStatus,
|
|
10
|
+
WorkingStateStatus,
|
|
11
|
+
} from '@/types'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
deletePersistedWorkingState,
|
|
15
|
+
loadPersistedWorkingState,
|
|
16
|
+
upsertPersistedWorkingState,
|
|
17
|
+
} from './repository'
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
normalizeWorkingState,
|
|
21
|
+
defaultWorkingState,
|
|
22
|
+
compactWorkingStateObject,
|
|
23
|
+
syncWorkingStateWithMission,
|
|
24
|
+
normalizeItemStatus,
|
|
25
|
+
normalizeStateStatus,
|
|
26
|
+
upsertItems,
|
|
27
|
+
factUpsertConfig,
|
|
28
|
+
artifactUpsertConfig,
|
|
29
|
+
decisionUpsertConfig,
|
|
30
|
+
blockerUpsertConfig,
|
|
31
|
+
questionUpsertConfig,
|
|
32
|
+
hypothesisUpsertConfig,
|
|
33
|
+
appendEvidenceRefs,
|
|
34
|
+
markSuperseded,
|
|
35
|
+
now,
|
|
36
|
+
MAX_PLAN_STEPS,
|
|
37
|
+
compactPlanSteps,
|
|
38
|
+
} from '@/lib/server/working-state/normalization'
|
|
39
|
+
import type {
|
|
40
|
+
WorkingStateDeterministicUpdateInput,
|
|
41
|
+
SynchronizeWorkingStateForTurnInput,
|
|
42
|
+
} from '@/lib/server/working-state/normalization'
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
deterministicEvidencePatch,
|
|
46
|
+
extractWorkingStatePatch,
|
|
47
|
+
shouldExtractStructuredPatch,
|
|
48
|
+
} from '@/lib/server/working-state/extraction'
|
|
49
|
+
|
|
50
|
+
import { buildWorkingStatePromptBlockFromState } from '@/lib/server/working-state/prompt'
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Re-exports for consumer compatibility
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
// normalization.ts re-exports
|
|
57
|
+
export {
|
|
58
|
+
// Constants
|
|
59
|
+
MAX_PLAN_STEPS,
|
|
60
|
+
MAX_CONFIRMED_FACTS,
|
|
61
|
+
MAX_ARTIFACTS,
|
|
62
|
+
MAX_DECISIONS,
|
|
63
|
+
MAX_BLOCKERS,
|
|
64
|
+
MAX_OPEN_QUESTIONS,
|
|
65
|
+
MAX_HYPOTHESES,
|
|
66
|
+
MAX_EVIDENCE_REFS,
|
|
67
|
+
EXTRACTION_TIMEOUT_MS,
|
|
68
|
+
ACTIVE_STATUS,
|
|
69
|
+
// Schemas
|
|
70
|
+
WorkingPlanStepPatchSchema,
|
|
71
|
+
WorkingFactPatchSchema,
|
|
72
|
+
WorkingArtifactPatchSchema,
|
|
73
|
+
WorkingDecisionPatchSchema,
|
|
74
|
+
WorkingBlockerPatchSchema,
|
|
75
|
+
WorkingQuestionPatchSchema,
|
|
76
|
+
WorkingHypothesisPatchSchema,
|
|
77
|
+
WorkingStatePatchSchema,
|
|
78
|
+
// Normalize functions
|
|
79
|
+
normalizeItemStatus,
|
|
80
|
+
normalizeStateStatus,
|
|
81
|
+
normalizeEvidenceIds,
|
|
82
|
+
normalizeEvidenceRef,
|
|
83
|
+
normalizePlanStep,
|
|
84
|
+
normalizeFact,
|
|
85
|
+
normalizeArtifact,
|
|
86
|
+
normalizeDecision,
|
|
87
|
+
normalizeBlocker,
|
|
88
|
+
normalizeQuestion,
|
|
89
|
+
normalizeHypothesis,
|
|
90
|
+
normalizeWorkingState,
|
|
91
|
+
normalizeMatchKey,
|
|
92
|
+
// Utility helpers
|
|
93
|
+
now,
|
|
94
|
+
itemSortRank,
|
|
95
|
+
genericCompact,
|
|
96
|
+
compactPlanSteps,
|
|
97
|
+
defaultWorkingState,
|
|
98
|
+
compactWorkingStateObject,
|
|
99
|
+
missionStatusToWorkingStateStatus,
|
|
100
|
+
syncWorkingStateWithMission,
|
|
101
|
+
// Upsert
|
|
102
|
+
upsertItems,
|
|
103
|
+
factUpsertConfig,
|
|
104
|
+
artifactUpsertConfig,
|
|
105
|
+
decisionUpsertConfig,
|
|
106
|
+
blockerUpsertConfig,
|
|
107
|
+
questionUpsertConfig,
|
|
108
|
+
hypothesisUpsertConfig,
|
|
109
|
+
appendEvidenceRefs,
|
|
110
|
+
markSuperseded,
|
|
111
|
+
} from '@/lib/server/working-state/normalization'
|
|
112
|
+
export type {
|
|
113
|
+
TimedWorkingItem,
|
|
114
|
+
UpsertConfig,
|
|
115
|
+
WorkingStateDeterministicUpdateInput,
|
|
116
|
+
WorkingStateExtractionInput,
|
|
117
|
+
SynchronizeWorkingStateForTurnInput,
|
|
118
|
+
} from '@/lib/server/working-state/normalization'
|
|
119
|
+
|
|
120
|
+
// extraction.ts re-exports
|
|
121
|
+
export {
|
|
122
|
+
parseStructuredObject,
|
|
123
|
+
extractFirstJsonObject,
|
|
124
|
+
parseWorkingStatePatchResponse,
|
|
125
|
+
renderStateForExtraction,
|
|
126
|
+
summarizeToolEvents,
|
|
127
|
+
buildWorkingStatePatchPrompt,
|
|
128
|
+
collectJsonCandidates,
|
|
129
|
+
uniqueByKey,
|
|
130
|
+
looksLikeUrl,
|
|
131
|
+
looksLikeFilePath,
|
|
132
|
+
extractPlainTextArtifacts,
|
|
133
|
+
deterministicEvidencePatch,
|
|
134
|
+
extractWorkingStatePatch,
|
|
135
|
+
shouldExtractStructuredPatch,
|
|
136
|
+
} from '@/lib/server/working-state/extraction'
|
|
137
|
+
|
|
138
|
+
// prompt.ts re-exports
|
|
139
|
+
export { buildWorkingStatePromptBlockFromState } from '@/lib/server/working-state/prompt'
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// CRUD / coordination layer
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
export function loadSessionWorkingState(sessionId: string, options?: { mission?: Mission | null }): SessionWorkingState | null {
|
|
146
|
+
const stored = loadPersistedWorkingState(sessionId)
|
|
147
|
+
if (!stored && !options?.mission) return null
|
|
148
|
+
return normalizeWorkingState(stored, sessionId, options?.mission || null)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getOrCreateSessionWorkingState(sessionId: string, options?: { mission?: Mission | null }): SessionWorkingState {
|
|
152
|
+
return loadSessionWorkingState(sessionId, options) || defaultWorkingState(sessionId, options?.mission || null)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function saveSessionWorkingState(state: SessionWorkingState): SessionWorkingState {
|
|
156
|
+
const normalized = compactWorkingStateObject(normalizeWorkingState(state, state.sessionId))
|
|
157
|
+
upsertPersistedWorkingState(normalized.sessionId, normalized as unknown as Record<string, unknown>)
|
|
158
|
+
return normalized
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function deleteSessionWorkingState(sessionId: string): void {
|
|
162
|
+
deletePersistedWorkingState(sessionId)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function applyWorkingStatePatch(
|
|
166
|
+
sessionId: string,
|
|
167
|
+
patch: WorkingStatePatch,
|
|
168
|
+
options?: { mission?: Mission | null },
|
|
169
|
+
): SessionWorkingState {
|
|
170
|
+
const current = getOrCreateSessionWorkingState(sessionId, options)
|
|
171
|
+
const next: SessionWorkingState = {
|
|
172
|
+
...current,
|
|
173
|
+
missionId: options?.mission?.id || current.missionId || null,
|
|
174
|
+
objective: patch.objective !== undefined ? (cleanMultiline(patch.objective, 900) || null) : current.objective,
|
|
175
|
+
summary: patch.summary !== undefined ? (cleanMultiline(patch.summary, 600) || null) : current.summary,
|
|
176
|
+
constraints: patch.constraints !== undefined ? normalizeList(patch.constraints, 12, 240) : current.constraints,
|
|
177
|
+
successCriteria: patch.successCriteria !== undefined ? normalizeList(patch.successCriteria, 12, 240) : current.successCriteria,
|
|
178
|
+
status: patch.status !== undefined && patch.status !== null ? normalizeStateStatus(patch.status, current.status) : current.status,
|
|
179
|
+
nextAction: patch.nextAction !== undefined ? (cleanText(patch.nextAction, 240) || null) : current.nextAction,
|
|
180
|
+
planSteps: upsertItems(current.planSteps, patch.planSteps, {
|
|
181
|
+
max: MAX_PLAN_STEPS,
|
|
182
|
+
getPatchId: (item) => cleanText(item.id, 120) || null,
|
|
183
|
+
getPatchKey: (item) => cleanText(item.text, 240),
|
|
184
|
+
getItemKey: (item) => item.text,
|
|
185
|
+
create: (item, nowTs) => ({
|
|
186
|
+
id: cleanText(item.id, 120) || genId(12),
|
|
187
|
+
text: cleanText(item.text, 240),
|
|
188
|
+
status: normalizeItemStatus(item.status),
|
|
189
|
+
createdAt: nowTs,
|
|
190
|
+
updatedAt: nowTs,
|
|
191
|
+
}),
|
|
192
|
+
merge: (item, patchItem, nowTs) => ({
|
|
193
|
+
...item,
|
|
194
|
+
text: cleanText(patchItem.text, 240) || item.text,
|
|
195
|
+
status: normalizeItemStatus(patchItem.status, item.status),
|
|
196
|
+
updatedAt: nowTs,
|
|
197
|
+
}),
|
|
198
|
+
compact: compactPlanSteps,
|
|
199
|
+
}),
|
|
200
|
+
confirmedFacts: upsertItems(current.confirmedFacts, patch.factsUpsert, factUpsertConfig()),
|
|
201
|
+
artifacts: upsertItems(current.artifacts, patch.artifactsUpsert, artifactUpsertConfig()),
|
|
202
|
+
decisions: upsertItems(current.decisions, patch.decisionsAppend, decisionUpsertConfig()),
|
|
203
|
+
blockers: upsertItems(current.blockers, patch.blockersUpsert, blockerUpsertConfig()),
|
|
204
|
+
openQuestions: upsertItems(current.openQuestions, patch.questionsUpsert, questionUpsertConfig()),
|
|
205
|
+
hypotheses: upsertItems(current.hypotheses, patch.hypothesesUpsert, hypothesisUpsertConfig()),
|
|
206
|
+
evidenceRefs: appendEvidenceRefs(current.evidenceRefs, patch.evidenceAppend),
|
|
207
|
+
updatedAt: now(),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
next.planSteps = markSuperseded(next.planSteps, patch.supersedeIds)
|
|
211
|
+
next.confirmedFacts = markSuperseded(next.confirmedFacts, patch.supersedeIds)
|
|
212
|
+
next.artifacts = markSuperseded(next.artifacts, patch.supersedeIds)
|
|
213
|
+
next.decisions = markSuperseded(next.decisions, patch.supersedeIds)
|
|
214
|
+
next.blockers = markSuperseded(next.blockers, patch.supersedeIds)
|
|
215
|
+
next.openQuestions = markSuperseded(next.openQuestions, patch.supersedeIds)
|
|
216
|
+
next.hypotheses = markSuperseded(next.hypotheses, patch.supersedeIds)
|
|
217
|
+
|
|
218
|
+
const synced = compactWorkingStateObject(syncWorkingStateWithMission(next, options?.mission || null))
|
|
219
|
+
upsertPersistedWorkingState(sessionId, synced as unknown as Record<string, unknown>)
|
|
220
|
+
return synced
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function recordWorkingStateEvidence(input: WorkingStateDeterministicUpdateInput): SessionWorkingState {
|
|
224
|
+
return applyWorkingStatePatch(
|
|
225
|
+
input.sessionId,
|
|
226
|
+
deterministicEvidencePatch(input),
|
|
227
|
+
{ mission: input.mission || null },
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function synchronizeWorkingStateForTurn(
|
|
232
|
+
input: SynchronizeWorkingStateForTurnInput,
|
|
233
|
+
options?: {
|
|
234
|
+
generateText?: (prompt: string) => Promise<string>
|
|
235
|
+
},
|
|
236
|
+
): Promise<SessionWorkingState> {
|
|
237
|
+
const deterministic = recordWorkingStateEvidence(input)
|
|
238
|
+
if (!shouldExtractStructuredPatch(input)) return deterministic
|
|
239
|
+
const patch = await extractWorkingStatePatch({
|
|
240
|
+
...input,
|
|
241
|
+
currentState: deterministic,
|
|
242
|
+
}, options)
|
|
243
|
+
if (!patch) return deterministic
|
|
244
|
+
return applyWorkingStatePatch(input.sessionId, patch, { mission: input.mission || null })
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function syncWorkingStateFromMainLoopState(input: {
|
|
248
|
+
sessionId: string
|
|
249
|
+
mission?: Mission | null
|
|
250
|
+
goal?: string | null
|
|
251
|
+
summary?: string | null
|
|
252
|
+
status?: WorkingStateStatus | null
|
|
253
|
+
nextAction?: string | null
|
|
254
|
+
planSteps?: string[]
|
|
255
|
+
blockers?: Array<{ summary: string; kind?: WorkingBlocker['kind'] | null }>
|
|
256
|
+
facts?: string[]
|
|
257
|
+
}): SessionWorkingState {
|
|
258
|
+
const planSteps = Array.isArray(input.planSteps)
|
|
259
|
+
? input.planSteps.map((step, index) => ({
|
|
260
|
+
text: step,
|
|
261
|
+
status: index === 0 && input.status !== 'completed' ? 'active' : (input.status === 'completed' ? 'resolved' : 'resolved'),
|
|
262
|
+
} satisfies WorkingPlanStepPatch))
|
|
263
|
+
: undefined
|
|
264
|
+
return applyWorkingStatePatch(input.sessionId, {
|
|
265
|
+
objective: cleanMultiline(input.goal, 900) || undefined,
|
|
266
|
+
summary: cleanMultiline(input.summary, 600) || undefined,
|
|
267
|
+
status: input.status || undefined,
|
|
268
|
+
nextAction: cleanText(input.nextAction, 240) || undefined,
|
|
269
|
+
planSteps,
|
|
270
|
+
blockersUpsert: Array.isArray(input.blockers)
|
|
271
|
+
? input.blockers.map((blocker) => ({
|
|
272
|
+
summary: cleanText(blocker.summary, 280),
|
|
273
|
+
kind: blocker.kind || undefined,
|
|
274
|
+
status: (input.status === 'completed' ? 'resolved' : 'active') as WorkingStateItemStatus,
|
|
275
|
+
})).filter((blocker) => blocker.summary)
|
|
276
|
+
: undefined,
|
|
277
|
+
factsUpsert: Array.isArray(input.facts)
|
|
278
|
+
? input.facts.map((fact) => ({
|
|
279
|
+
statement: cleanText(fact, 280),
|
|
280
|
+
source: 'system' as const,
|
|
281
|
+
status: (input.status === 'completed' ? 'resolved' : 'active') as WorkingStateItemStatus,
|
|
282
|
+
})).filter((fact) => fact.statement)
|
|
283
|
+
: undefined,
|
|
284
|
+
}, { mission: input.mission || null })
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function buildWorkingStatePromptBlock(
|
|
288
|
+
sessionId: string,
|
|
289
|
+
options?: { mission?: Mission | null },
|
|
290
|
+
): string {
|
|
291
|
+
const state = loadSessionWorkingState(sessionId, options)
|
|
292
|
+
return buildWorkingStatePromptBlockFromState(state)
|
|
293
|
+
}
|
|
@@ -189,6 +189,7 @@ export const ChatroomCreateSchema = z.object({
|
|
|
189
189
|
description: z.string().optional().default(''),
|
|
190
190
|
chatMode: z.enum(['sequential', 'parallel']).optional(),
|
|
191
191
|
autoAddress: z.boolean().optional(),
|
|
192
|
+
routingGuidance: z.string().nullable().optional(),
|
|
192
193
|
routingRules: z.array(z.object({
|
|
193
194
|
id: z.string(),
|
|
194
195
|
type: z.enum(['keyword', 'capability']),
|
package/src/lib/ws-client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jitteredBackoff } from '@/lib/shared-utils'
|
|
1
|
+
import { jitteredBackoff, hmrSingleton } from '@/lib/shared-utils'
|
|
2
2
|
|
|
3
3
|
type WsCallback = () => void
|
|
4
4
|
|
|
@@ -7,9 +7,9 @@ let wsEnabled = false
|
|
|
7
7
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
8
8
|
let reconnectAttempt = 0
|
|
9
9
|
const MAX_RECONNECT_DELAY = 30_000
|
|
10
|
-
const listeners = new Map<string, Set<WsCallback>>()
|
|
10
|
+
const listeners = hmrSingleton('wsClient_listeners', () => new Map<string, Set<WsCallback>>())
|
|
11
11
|
let connected = false
|
|
12
|
-
const connectionStateListeners = new Set<() => void>()
|
|
12
|
+
const connectionStateListeners = hmrSingleton('wsClient_connectionStateListeners', () => new Set<() => void>())
|
|
13
13
|
|
|
14
14
|
function getWsUrl(): string {
|
|
15
15
|
if (typeof window === 'undefined') return 'ws://localhost:3457/ws'
|
|
@@ -56,8 +56,5 @@ export const createTaskSlice: StateCreator<AppState, [], [], TaskSlice> = (set,
|
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
58
|
showArchivedTasks: false,
|
|
59
|
-
setShowArchivedTasks: (show) => {
|
|
60
|
-
set({ showArchivedTasks: show })
|
|
61
|
-
get().loadTasks(show)
|
|
62
|
-
}
|
|
59
|
+
setShowArchivedTasks: (show) => set({ showArchivedTasks: show })
|
|
63
60
|
})
|