@swarmclawai/swarmclaw 1.2.6 → 1.2.9
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 +54 -23
- package/next.config.ts +1 -0
- package/package.json +4 -3
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/settings/route.ts +0 -2
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +21 -33
- package/src/cli/spec.js +19 -30
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +165 -131
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +1 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +11 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
- package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +1 -10
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -17
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -8
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +10 -0
- package/src/lib/server/storage.ts +18 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +2 -2
- package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -35
- package/src/lib/server/tool-planning.ts +11 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +1 -1
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +49 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +264 -0
- package/src/types/app-settings.ts +173 -0
- package/src/types/approval.ts +25 -0
- package/src/types/connector.ts +188 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +56 -0
- package/src/types/misc.ts +737 -0
- package/src/types/protocol.ts +420 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +180 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +215 -0
- package/src/types/skill.ts +157 -0
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +144 -0
- package/src/types/working-state.ts +204 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/sandbox.ts +0 -281
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/views/settings/section-wallets.tsx +0 -35
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
BoardTask,
|
|
7
7
|
Chatroom,
|
|
8
8
|
MessageToolEvent,
|
|
9
|
-
Mission,
|
|
10
9
|
ProtocolBranchCase,
|
|
11
10
|
ProtocolPhaseDefinition,
|
|
12
11
|
ProtocolRepeatConfig,
|
|
@@ -31,7 +30,6 @@ export interface ProtocolRunDetail {
|
|
|
31
30
|
template: ProtocolTemplate | null
|
|
32
31
|
transcript: Chatroom | null
|
|
33
32
|
parentChatroom: Chatroom | null
|
|
34
|
-
linkedMission: Mission | null
|
|
35
33
|
linkedTask: BoardTask | null
|
|
36
34
|
events: ProtocolRunEvent[]
|
|
37
35
|
}
|
|
@@ -42,7 +42,7 @@ function commandExists(binary: string): boolean {
|
|
|
42
42
|
pruneTTLCache(cliCheckCache, CLI_CHECK_TTL_MS, CLI_CHECK_CACHE_MAX)
|
|
43
43
|
const probe = isWindows
|
|
44
44
|
? spawnSync('where', [binary], { timeout: 2000, stdio: 'pipe' })
|
|
45
|
-
: spawnSync('/bin/
|
|
45
|
+
: spawnSync(process.env.SHELL || '/bin/bash', ['-lc', `command -v ${binary} >/dev/null 2>&1`], { timeout: 2000 })
|
|
46
46
|
const ok = (probe.status ?? 1) === 0
|
|
47
47
|
cliCheckCache.set(binary, { at: now, ok })
|
|
48
48
|
return ok
|
|
@@ -92,15 +92,6 @@ export function markProviderSuccess(providerId: string, credentialId?: string |
|
|
|
92
92
|
const { upsertStoredItem } = require('@/lib/server/storage')
|
|
93
93
|
upsertStoredItem('provider_health', key, states.get(key)!)
|
|
94
94
|
} catch {}
|
|
95
|
-
queueMicrotask(() => {
|
|
96
|
-
import('@/lib/server/missions/mission-service')
|
|
97
|
-
.then(({ requestMissionTicksForProviderRecovery }) => {
|
|
98
|
-
requestMissionTicksForProviderRecovery(providerId)
|
|
99
|
-
})
|
|
100
|
-
.catch(() => {
|
|
101
|
-
// Mission recovery is best-effort only.
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
95
|
}
|
|
105
96
|
|
|
106
97
|
export function isProviderCoolingDown(providerId: string, credentialId?: string | null): boolean {
|
|
@@ -66,7 +66,6 @@ import { loadEstopState } from '@/lib/server/runtime/estop'
|
|
|
66
66
|
import { classifyRuntimeFailure, recordSupervisorIncident } from '@/lib/server/autonomy/supervisor-reflection'
|
|
67
67
|
import { getMemoryDb } from '@/lib/server/memory/memory-db'
|
|
68
68
|
import { clearLogsByAge } from '@/lib/server/execution-log'
|
|
69
|
-
import { runMissionControllerStartupRecovery } from '@/lib/server/missions/mission-service'
|
|
70
69
|
|
|
71
70
|
const TAG = 'daemon-state'
|
|
72
71
|
|
|
@@ -293,14 +292,6 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
|
|
|
293
292
|
if (lost > 0) log.info(TAG, `[daemon] Marked ${lost} in-flight swarm(s) as lost after restart`)
|
|
294
293
|
} catch { /* best-effort */ }
|
|
295
294
|
resumeQueue()
|
|
296
|
-
const missionRecovery = runMissionControllerStartupRecovery()
|
|
297
|
-
if (missionRecovery.recovered > 0 || missionRecovery.rerunVerification > 0) {
|
|
298
|
-
log.info(
|
|
299
|
-
TAG,
|
|
300
|
-
`[daemon] Recovered ${missionRecovery.recovered} mission(s) on startup`
|
|
301
|
-
+ ` (${missionRecovery.rerunVerification} queued for verification replay)`,
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
295
|
startScheduler()
|
|
305
296
|
startQueueProcessor()
|
|
306
297
|
startBrowserSweep()
|
|
@@ -245,41 +245,6 @@ describe('daemon start/stop lifecycle', () => {
|
|
|
245
245
|
}
|
|
246
246
|
})
|
|
247
247
|
|
|
248
|
-
it('startDaemon runs mission startup recovery once the daemon owns startup', async () => {
|
|
249
|
-
const storage = await import('@/lib/server/storage')
|
|
250
|
-
storage.saveMissions({
|
|
251
|
-
missionA: {
|
|
252
|
-
id: 'missionA',
|
|
253
|
-
source: 'chat',
|
|
254
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
255
|
-
objective: 'Recover after daemon start',
|
|
256
|
-
status: 'active',
|
|
257
|
-
phase: 'executing',
|
|
258
|
-
sessionId: 'sessionA',
|
|
259
|
-
taskIds: [],
|
|
260
|
-
controllerState: {
|
|
261
|
-
activeRunId: 'run-stale',
|
|
262
|
-
currentTaskId: 'task-stale',
|
|
263
|
-
},
|
|
264
|
-
createdAt: 1,
|
|
265
|
-
updatedAt: 1,
|
|
266
|
-
},
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
await mod.stopDaemon({ source: 'test-prestart' })
|
|
270
|
-
mod.startDaemon({ source: 'test-mission-recovery', manualStart: true })
|
|
271
|
-
try {
|
|
272
|
-
const missions = await import('@/lib/server/missions/mission-service')
|
|
273
|
-
const mission = missions.loadMissionById('missionA')
|
|
274
|
-
assert.equal(mission?.status, 'active')
|
|
275
|
-
assert.equal(mission?.phase, 'planning')
|
|
276
|
-
const events = missions.listMissionEventsForMission('missionA')
|
|
277
|
-
assert.ok(events.some((event) => event.type === 'interrupted'))
|
|
278
|
-
} finally {
|
|
279
|
-
await mod.stopDaemon({ source: 'test-mission-recovery' })
|
|
280
|
-
}
|
|
281
|
-
})
|
|
282
|
-
|
|
283
248
|
it('stopDaemon sets running to false', async () => {
|
|
284
249
|
mod.startDaemon({ source: 'test', manualStart: true })
|
|
285
250
|
await mod.stopDaemon({ source: 'test' })
|
|
@@ -10,17 +10,15 @@ import { logActivity } from '@/lib/server/activity/activity-log'
|
|
|
10
10
|
import { loadApprovals } from '@/lib/server/approvals/approval-repository'
|
|
11
11
|
import { loadAgents, patchAgent } from '@/lib/server/agents/agent-repository'
|
|
12
12
|
import { loadChatrooms } from '@/lib/server/chatrooms/chatroom-repository'
|
|
13
|
-
import { loadMission } from '@/lib/server/missions/mission-repository'
|
|
14
13
|
import { loadSessions, patchSession } from '@/lib/server/sessions/session-repository'
|
|
15
14
|
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
16
|
-
import {
|
|
15
|
+
import { buildPlatformStatusSummary } from '@/lib/server/chat-execution/situational-awareness'
|
|
17
16
|
import { drainDeferredWakes, hasDeferredWakes } from '@/lib/server/runtime/wake-dispatcher'
|
|
18
17
|
import { buildWakeTriggerContext } from '@/lib/server/runtime/heartbeat-wake'
|
|
19
18
|
import { enqueueSessionRun, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
20
19
|
import { log } from '@/lib/server/logger'
|
|
21
20
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
22
21
|
import { drainSystemEvents, drainOrchestratorEvents } from '@/lib/server/runtime/system-events'
|
|
23
|
-
import { buildMissionContextBlock } from '@/lib/server/missions/mission-service'
|
|
24
22
|
import { getMessages, getRecentMessages, clearMessages } from '@/lib/server/messages/message-repository'
|
|
25
23
|
import type { Agent, AppSettings, ApprovalRequest, Chatroom, Message, Session } from '@/types'
|
|
26
24
|
import { isOrchestratorEligible } from '@/lib/orchestrator-config'
|
|
@@ -357,12 +355,7 @@ export function buildAgentHeartbeatPrompt(
|
|
|
357
355
|
}
|
|
358
356
|
}
|
|
359
357
|
|
|
360
|
-
// ── Phase 3:
|
|
361
|
-
const missionId = (session.missionId || (agent as Record<string, unknown>).missionId || null) as string | null
|
|
362
|
-
const goalAncestry = buildGoalAncestrySection(missionId)
|
|
363
|
-
if (goalAncestry) sections.push(goalAncestry)
|
|
364
|
-
|
|
365
|
-
// ── Phase 4: Active task checkout & events ──
|
|
358
|
+
// ── Phase 3: Active task checkout & events ──
|
|
366
359
|
const events = drainSystemEvents(session.id!)
|
|
367
360
|
if (events.length > 0) {
|
|
368
361
|
const eventBlock = events.map((e) => `- [${new Date(e.timestamp).toISOString()}] ${e.text}`).join('\n')
|
|
@@ -983,20 +976,7 @@ export function buildOrchestratorWakePrompt(session: any, agent: Agent): string
|
|
|
983
976
|
addSection(`## System Events\n${eventBlock}`)
|
|
984
977
|
}
|
|
985
978
|
|
|
986
|
-
// 7.
|
|
987
|
-
const missionId = session.missionId || null
|
|
988
|
-
if (missionId) {
|
|
989
|
-
try {
|
|
990
|
-
const missionBlock = buildMissionContextBlock(loadMission(missionId))
|
|
991
|
-
if (missionBlock) addSection(missionBlock)
|
|
992
|
-
} catch { /* ignore */ }
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// 8. Goal ancestry
|
|
996
|
-
const goalAncestry = buildGoalAncestrySection(missionId)
|
|
997
|
-
if (goalAncestry) addSection(goalAncestry)
|
|
998
|
-
|
|
999
|
-
// 9. Chatroom membership
|
|
979
|
+
// 7. Chatroom membership
|
|
1000
980
|
try {
|
|
1001
981
|
const chatrooms = Object.values(loadChatrooms()) as Chatroom[]
|
|
1002
982
|
const myChatrooms = chatrooms.filter((c) => !c.archivedAt && c.agentIds?.includes(agent.id))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
|
-
import { hmrSingleton, sleep } from '@/lib/shared-utils'
|
|
2
|
+
import { hmrSingleton, sleep, errorMessage } from '@/lib/shared-utils'
|
|
3
3
|
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
|
|
4
4
|
import { detectDocker } from '@/lib/server/sandbox/docker-detect'
|
|
5
5
|
import { log } from '@/lib/server/logger'
|
|
@@ -169,7 +169,8 @@ export function buildDockerExecArgs(params: {
|
|
|
169
169
|
|
|
170
170
|
export function getShellCommand(command: string, processId?: string, sandbox?: SandboxOptions): { shell: string; args: string[]; containerName?: string } {
|
|
171
171
|
if (!sandbox) {
|
|
172
|
-
|
|
172
|
+
const shell = process.env.SHELL || '/bin/bash'
|
|
173
|
+
return { shell, args: ['-lc', command] }
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
if (sandbox.kind === 'persistent') {
|
|
@@ -247,9 +248,12 @@ export async function startManagedProcess(opts: StartProcessOptions): Promise<St
|
|
|
247
248
|
state.records.set(id, record)
|
|
248
249
|
|
|
249
250
|
const { shell, args } = getShellCommand(opts.command, id, opts.sandbox)
|
|
250
|
-
// Strip
|
|
251
|
-
//
|
|
252
|
-
|
|
251
|
+
// Strip env vars that should not leak into child processes:
|
|
252
|
+
// - PORT, ACCESS_KEY, NEXTAUTH_SECRET: SwarmClaw-specific bindings
|
|
253
|
+
// - npm_config_prefix: injected by npm when running scripts; conflicts with nvm
|
|
254
|
+
// in login shell subprocesses (nvm refuses to initialize when this is set,
|
|
255
|
+
// breaking node PATH resolution for all nvm users)
|
|
256
|
+
const stripKeys = new Set(['PORT', 'ACCESS_KEY', 'NEXTAUTH_SECRET', 'npm_config_prefix'])
|
|
253
257
|
const cleanEnv = Object.fromEntries(Object.entries(process.env).filter(([k]) => !stripKeys.has(k))) as NodeJS.ProcessEnv
|
|
254
258
|
const child = spawn(shell, args, {
|
|
255
259
|
cwd: opts.sandbox ? undefined : opts.cwd,
|
|
@@ -405,8 +409,8 @@ export function writeManagedProcessStdin(processId: string, data: string, eof?:
|
|
|
405
409
|
if (data) child.stdin.write(data)
|
|
406
410
|
if (eof) child.stdin.end()
|
|
407
411
|
return { ok: true }
|
|
408
|
-
} catch (err:
|
|
409
|
-
return { ok: false, error: err
|
|
412
|
+
} catch (err: unknown) {
|
|
413
|
+
return { ok: false, error: errorMessage(err) || String(err) }
|
|
410
414
|
}
|
|
411
415
|
}
|
|
412
416
|
|
|
@@ -418,8 +422,8 @@ export function killManagedProcess(processId: string, signal: NodeJS.Signals = '
|
|
|
418
422
|
rec.status = 'killed'
|
|
419
423
|
child.kill(signal)
|
|
420
424
|
return { ok: true }
|
|
421
|
-
} catch (err:
|
|
422
|
-
return { ok: false, error: err
|
|
425
|
+
} catch (err: unknown) {
|
|
426
|
+
return { ok: false, error: errorMessage(err) || String(err) }
|
|
423
427
|
}
|
|
424
428
|
}
|
|
425
429
|
|
|
@@ -23,6 +23,7 @@ import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-exe
|
|
|
23
23
|
import { checkAgentBudgetLimits } from '@/lib/server/cost'
|
|
24
24
|
import { enqueueExecution } from '@/lib/server/execution-engine'
|
|
25
25
|
import { extractTaskResult, formatResultBody } from '@/lib/server/tasks/task-result'
|
|
26
|
+
import { checkoutTask } from '@/lib/server/tasks/task-checkout'
|
|
26
27
|
import {
|
|
27
28
|
classifyRuntimeFailure,
|
|
28
29
|
observeAutonomyRunOutcome,
|
|
@@ -51,7 +52,6 @@ import {
|
|
|
51
52
|
markValidatedTaskCompleted,
|
|
52
53
|
refreshTaskCompletionValidation,
|
|
53
54
|
} from '@/lib/server/tasks/task-lifecycle'
|
|
54
|
-
import { noteMissionTaskFinished, noteMissionTaskStarted } from '@/lib/server/missions/mission-service'
|
|
55
55
|
|
|
56
56
|
const TAG = 'queue'
|
|
57
57
|
|
|
@@ -1210,25 +1210,16 @@ export async function processNext() {
|
|
|
1210
1210
|
} catch {}
|
|
1211
1211
|
}
|
|
1212
1212
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// Mark as running
|
|
1213
|
+
// Atomic checkout — prevents two runners from starting the same task
|
|
1214
|
+
const runId = genId()
|
|
1215
|
+
task = checkoutTask(taskId, runId) as BoardTask | undefined
|
|
1216
|
+
if (!task) return
|
|
1220
1217
|
applyTaskPolicyDefaults(task)
|
|
1221
|
-
task.status = 'running'
|
|
1222
|
-
task.startedAt = Date.now()
|
|
1223
|
-
task.lastActivityAt = Date.now()
|
|
1224
|
-
task.retryScheduledAt = null
|
|
1225
|
-
task.deadLetteredAt = null
|
|
1226
|
-
// Clear transient failure fields so validation/error state reflects only this attempt.
|
|
1227
|
-
task.error = null
|
|
1228
|
-
task.validation = null
|
|
1229
|
-
task.updatedAt = Date.now()
|
|
1230
1218
|
logActivity({ entityType: 'task', entityId: taskId, action: 'running', actor: 'system', actorId: task.agentId, summary: `Task started: "${task.title}"` })
|
|
1231
1219
|
|
|
1220
|
+
// Reload tasks map for resolution functions and final save (checkoutTask already saved the running status)
|
|
1221
|
+
const allTasks = loadTasks() as Record<string, BoardTask>
|
|
1222
|
+
allTasks[taskId] = task
|
|
1232
1223
|
const sessionsForCwd = loadSessions() as Record<string, SessionLike>
|
|
1233
1224
|
const taskCwd = resolveTaskExecutionCwd(task as ScheduleTaskMeta, sessionsForCwd)
|
|
1234
1225
|
task.cwd = taskCwd
|
|
@@ -1238,8 +1229,8 @@ export async function processNext() {
|
|
|
1238
1229
|
const sourceScheduleId = typeof scheduleTask.sourceScheduleId === 'string'
|
|
1239
1230
|
? scheduleTask.sourceScheduleId
|
|
1240
1231
|
: ''
|
|
1241
|
-
const reusableTaskSessionId = resolveReusableTaskSessionId(task,
|
|
1242
|
-
const resumeContext = resolveTaskResumeContext(task,
|
|
1232
|
+
const reusableTaskSessionId = resolveReusableTaskSessionId(task, allTasks, sessionsForCwd)
|
|
1233
|
+
const resumeContext = resolveTaskResumeContext(task, allTasks, sessionsForCwd as Record<string, SessionLike | Session>)
|
|
1243
1234
|
|
|
1244
1235
|
// Resolve the agent's persistent thread session to use as parentSessionId
|
|
1245
1236
|
const agentThreadSessionId = agent.threadSessionId || null
|
|
@@ -1307,8 +1298,7 @@ export async function processNext() {
|
|
|
1307
1298
|
note: `Attempt ${(task.attempts || 0) + 1}/${task.maxAttempts || '?'} started${continuationBits.length ? ` (${continuationBits.join('; ')})` : ''}`,
|
|
1308
1299
|
updatedAt: Date.now(),
|
|
1309
1300
|
}
|
|
1310
|
-
saveTasks(
|
|
1311
|
-
noteMissionTaskStarted(task, task.id)
|
|
1301
|
+
saveTasks(allTasks)
|
|
1312
1302
|
pushMainLoopEventToMainSessions({
|
|
1313
1303
|
type: 'task_running',
|
|
1314
1304
|
text: `Task running: "${task.title}" (${task.id}) with ${agent.name}`,
|
|
@@ -1485,13 +1475,6 @@ export async function processNext() {
|
|
|
1485
1475
|
disableSessionHeartbeat(t2[taskId].sessionId)
|
|
1486
1476
|
}
|
|
1487
1477
|
const doneTask = t2[taskId]
|
|
1488
|
-
if (doneTask?.status === 'completed') {
|
|
1489
|
-
noteMissionTaskFinished(doneTask, 'completed', taskRunId)
|
|
1490
|
-
} else if (doneTask?.status === 'failed') {
|
|
1491
|
-
noteMissionTaskFinished(doneTask, 'failed', taskRunId)
|
|
1492
|
-
} else if (doneTask?.status === 'cancelled') {
|
|
1493
|
-
noteMissionTaskFinished(doneTask, 'cancelled', taskRunId)
|
|
1494
|
-
}
|
|
1495
1478
|
queueTaskAutonomyObservation({
|
|
1496
1479
|
runId: taskRunId,
|
|
1497
1480
|
sessionId,
|
|
@@ -1636,11 +1619,6 @@ export async function processNext() {
|
|
|
1636
1619
|
})
|
|
1637
1620
|
}
|
|
1638
1621
|
saveTasks(t3)
|
|
1639
|
-
if (t3[taskId].status === 'failed') {
|
|
1640
|
-
noteMissionTaskFinished(t3[taskId], 'failed', taskRunId)
|
|
1641
|
-
} else if (t3[taskId].status === 'cancelled') {
|
|
1642
|
-
noteMissionTaskFinished(t3[taskId], 'cancelled', taskRunId)
|
|
1643
|
-
}
|
|
1644
1622
|
notify('tasks')
|
|
1645
1623
|
notify('runs')
|
|
1646
1624
|
disableSessionHeartbeat(t3[taskId].sessionId)
|
|
@@ -9,7 +9,7 @@ function readRepoSource(relativePath: string): string {
|
|
|
9
9
|
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf-8')
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
test('runtime hot paths use row-level task, schedule, and
|
|
12
|
+
test('runtime hot paths use row-level task, schedule, and wallet writes', () => {
|
|
13
13
|
const expectations = [
|
|
14
14
|
{
|
|
15
15
|
file: 'src/lib/server/runtime/scheduler.ts',
|
|
@@ -17,14 +17,14 @@ test('runtime hot paths use row-level task, schedule, and agent writes', () => {
|
|
|
17
17
|
forbidden: ['saveTasks(', 'saveSchedules('],
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
file: 'src/
|
|
21
|
-
required: ['
|
|
20
|
+
file: 'src/lib/server/schedules/schedule-route-service.ts',
|
|
21
|
+
required: ['saveTask(', 'upsertSchedule('],
|
|
22
22
|
forbidden: ['saveTasks(', 'saveSchedules('],
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
file: 'src/lib/server/
|
|
26
|
-
required: ['
|
|
27
|
-
forbidden: ['
|
|
25
|
+
file: 'src/lib/server/wallets/wallet-service.ts',
|
|
26
|
+
required: ['saveWallet(', 'deleteWallet('],
|
|
27
|
+
forbidden: ['saveWallets('],
|
|
28
28
|
},
|
|
29
29
|
] as const
|
|
30
30
|
|
|
@@ -10,7 +10,6 @@ import { processDueWatchJobs } from '@/lib/server/runtime/watch-jobs'
|
|
|
10
10
|
import { isAgentDisabled } from '@/lib/server/agents/agent-availability'
|
|
11
11
|
import { prepareScheduledTaskRun } from '@/lib/server/tasks/task-lifecycle'
|
|
12
12
|
import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
|
|
13
|
-
import { ensureMissionForTask, noteScheduleMissionTriggered } from '@/lib/server/missions/mission-service'
|
|
14
13
|
import { hasActiveProtocolRunForSchedule, launchProtocolRunForSchedule } from '@/lib/server/protocols/protocol-service'
|
|
15
14
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
16
15
|
import { log } from '@/lib/server/logger'
|
|
@@ -168,10 +167,6 @@ async function tick(now = Date.now()) {
|
|
|
168
167
|
// Wake-only: no board task, just heartbeat the agent
|
|
169
168
|
upsertSchedule(schedule.id, schedule)
|
|
170
169
|
const wakeSessionId = resolveScheduleWakeSessionId(schedule, agents as Record<string, unknown>)
|
|
171
|
-
noteScheduleMissionTriggered(schedule, {
|
|
172
|
-
wakeOnly: true,
|
|
173
|
-
sessionId: wakeSessionId || schedule.createdInSessionId || null,
|
|
174
|
-
})
|
|
175
170
|
|
|
176
171
|
const wakeMessage = schedule.message || `Schedule triggered: ${schedule.name}`
|
|
177
172
|
pushMainLoopEventToMainSessions({
|
|
@@ -205,16 +200,8 @@ async function tick(now = Date.now()) {
|
|
|
205
200
|
now,
|
|
206
201
|
scheduleSignature,
|
|
207
202
|
})
|
|
208
|
-
const mission = noteScheduleMissionTriggered(schedule, {
|
|
209
|
-
taskId,
|
|
210
|
-
sessionId: schedule.createdInSessionId || null,
|
|
211
|
-
})
|
|
212
|
-
if (mission) {
|
|
213
|
-
tasks[taskId].missionId = mission.id
|
|
214
|
-
}
|
|
215
203
|
|
|
216
204
|
upsertTask(taskId, tasks[taskId])
|
|
217
|
-
ensureMissionForTask(tasks[taskId], { source: 'schedule' })
|
|
218
205
|
upsertSchedule(schedule.id, schedule)
|
|
219
206
|
|
|
220
207
|
enqueueTask(taskId)
|
|
@@ -90,7 +90,6 @@ export async function drainExecution(
|
|
|
90
90
|
})
|
|
91
91
|
|
|
92
92
|
let runtimeTimer: ReturnType<typeof setTimeout> | null = null
|
|
93
|
-
let finishedMissionId: string | null = null
|
|
94
93
|
if (next.maxRuntimeMs && next.maxRuntimeMs > 0) {
|
|
95
94
|
runtimeTimer = setTimeout(() => {
|
|
96
95
|
next.signalController.abort()
|
|
@@ -119,8 +118,6 @@ export async function drainExecution(
|
|
|
119
118
|
next.run.status = aborted ? 'cancelled' : (failed ? 'failed' : 'completed')
|
|
120
119
|
next.run.endedAt = next.run.endedAt || now()
|
|
121
120
|
next.run.error = aborted ? (next.run.error || 'Cancelled') : result.error
|
|
122
|
-
next.run.missionId = result.missionId || next.run.missionId || null
|
|
123
|
-
finishedMissionId = next.run.missionId || null
|
|
124
121
|
next.run.resultPreview = result.text?.slice(0, 280)
|
|
125
122
|
if (typeof result.inputTokens === 'number') next.run.totalInputTokens = result.inputTokens
|
|
126
123
|
if (typeof result.outputTokens === 'number') next.run.totalOutputTokens = result.outputTokens
|
|
@@ -186,7 +183,6 @@ export async function drainExecution(
|
|
|
186
183
|
next.run.status = aborted ? 'cancelled' : 'failed'
|
|
187
184
|
next.run.endedAt = now()
|
|
188
185
|
next.run.error = errorMessage(err)
|
|
189
|
-
finishedMissionId = next.run.missionId || null
|
|
190
186
|
syncRunRecord(next.run)
|
|
191
187
|
emitRunMeta(next, next.run.status, { error: next.run.error })
|
|
192
188
|
log.error('session-run', `Run failed ${next.run.id}`, {
|
|
@@ -216,26 +212,6 @@ export async function drainExecution(
|
|
|
216
212
|
decrementNonHeartbeatWork(next)
|
|
217
213
|
reconcileSessionActivityLease(next.run.sessionId)
|
|
218
214
|
notify(`stream-end:${next.run.sessionId}`)
|
|
219
|
-
if (finishedMissionId && next.run.source !== 'chat') {
|
|
220
|
-
const missionId = finishedMissionId
|
|
221
|
-
queueMicrotask(() => {
|
|
222
|
-
import('@/lib/server/missions/mission-service')
|
|
223
|
-
.then(({ loadMissionById, requestMissionTick }) => {
|
|
224
|
-
const mission = loadMissionById(missionId)
|
|
225
|
-
if (!mission) return
|
|
226
|
-
if (mission.status !== 'active') return
|
|
227
|
-
if (mission.phase === 'dispatching' || mission.phase === 'executing') return
|
|
228
|
-
requestMissionTick(missionId, 'run_drained', {
|
|
229
|
-
runId: next.run.id,
|
|
230
|
-
source: next.run.source,
|
|
231
|
-
status: next.run.status,
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
.catch((err: unknown) => {
|
|
235
|
-
log.warn('session-run', 'Mission tick failed', { missionId, runId: next.run.id, error: errorMessage(err) })
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
}
|
|
239
215
|
void drainExecution(executionKey, deps)
|
|
240
216
|
}
|
|
241
217
|
} finally {
|
|
@@ -215,7 +215,6 @@ export function enqueueSessionRun(
|
|
|
215
215
|
const run: SessionRunRecord = {
|
|
216
216
|
id: runId,
|
|
217
217
|
sessionId: input.sessionId,
|
|
218
|
-
missionId: input.missionId ?? getSession(input.sessionId)?.missionId ?? null,
|
|
219
218
|
kind: 'session_turn',
|
|
220
219
|
ownerType: 'session',
|
|
221
220
|
ownerId: input.sessionId,
|
|
@@ -37,7 +37,6 @@ function toQueuedTurn(entry: SessionRunQueueEntry, index: number): SessionQueued
|
|
|
37
37
|
return {
|
|
38
38
|
runId: entry.run.id,
|
|
39
39
|
sessionId: entry.run.sessionId,
|
|
40
|
-
missionId: entry.run.missionId || null,
|
|
41
40
|
text: entry.message,
|
|
42
41
|
queuedAt: entry.run.queuedAt,
|
|
43
42
|
position: index + 1,
|
|
@@ -49,12 +48,27 @@ function toQueuedTurn(entry: SessionRunQueueEntry, index: number): SessionQueued
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
function toActiveTurn(entry: SessionRunQueueEntry): SessionQueuedTurn {
|
|
52
|
+
return {
|
|
53
|
+
...toQueuedTurn(entry, 0),
|
|
54
|
+
position: 0,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function visibleActiveTurnForSession(sessionId: string): SessionQueuedTurn | null {
|
|
59
|
+
const running = Array.from(state.runningByExecution.values())
|
|
60
|
+
.find((entry) => entry.run.sessionId === sessionId && entry.run.status === 'running')
|
|
61
|
+
if (!running || running.run.internal === true) return null
|
|
62
|
+
return toActiveTurn(running)
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
export function getSessionQueueSnapshot(sessionId: string): SessionQueueSnapshot {
|
|
53
66
|
const execution = getSessionExecutionState(sessionId)
|
|
54
67
|
const visibleQueued = visibleQueuedEntriesForSession(sessionId)
|
|
55
68
|
return {
|
|
56
69
|
sessionId,
|
|
57
70
|
activeRunId: execution.runningRunId || null,
|
|
71
|
+
activeTurn: visibleActiveTurnForSession(sessionId),
|
|
58
72
|
queueLength: visibleQueued.length,
|
|
59
73
|
items: visibleQueued.map((entry, index) => toQueuedTurn(entry, index)),
|
|
60
74
|
}
|
|
@@ -48,7 +48,6 @@ function resolveRecoveredQueuedEntry(entry: SessionRunQueueEntry, reason: string
|
|
|
48
48
|
entry.resolve({
|
|
49
49
|
runId: entry.run.id,
|
|
50
50
|
sessionId: entry.run.sessionId,
|
|
51
|
-
...(entry.run.missionId ? { missionId: entry.run.missionId } : {}),
|
|
52
51
|
text: '',
|
|
53
52
|
persisted: false,
|
|
54
53
|
toolEvents: [],
|
|
@@ -249,34 +249,6 @@ describe('session-run-manager', () => {
|
|
|
249
249
|
assert.ok(run.queuedAt > 0)
|
|
250
250
|
})
|
|
251
251
|
|
|
252
|
-
it('copies the session mission id into queued run snapshots', () => {
|
|
253
|
-
storage.upsertSession('sess-mission', {
|
|
254
|
-
id: 'sess-mission',
|
|
255
|
-
cwd: process.cwd(),
|
|
256
|
-
user: 'tester',
|
|
257
|
-
provider: 'ollama',
|
|
258
|
-
model: 'test-model',
|
|
259
|
-
claudeSessionId: null,
|
|
260
|
-
messages: [],
|
|
261
|
-
createdAt: Date.now(),
|
|
262
|
-
lastActiveAt: Date.now(),
|
|
263
|
-
agentId: 'test-agent',
|
|
264
|
-
missionId: 'mission-123',
|
|
265
|
-
})
|
|
266
|
-
const release = mgr.acquireExternalSessionExecutionHold('sess-mission')
|
|
267
|
-
|
|
268
|
-
const result = enqueue({
|
|
269
|
-
sessionId: 'sess-mission',
|
|
270
|
-
message: 'Continue the release mission',
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
const run = mgr.getRunById(result.runId)
|
|
274
|
-
const snapshot = mgr.getSessionQueueSnapshot('sess-mission')
|
|
275
|
-
assert.equal(run?.missionId, 'mission-123')
|
|
276
|
-
assert.equal(snapshot.items[0]?.missionId, 'mission-123')
|
|
277
|
-
release()
|
|
278
|
-
})
|
|
279
|
-
|
|
280
252
|
it('persists run records and replay events in storage', () => {
|
|
281
253
|
const result = enqueue({
|
|
282
254
|
sessionId: 'sess-persisted',
|
|
@@ -521,6 +493,37 @@ describe('session-run-manager', () => {
|
|
|
521
493
|
})
|
|
522
494
|
})
|
|
523
495
|
|
|
496
|
+
it('exposes the active user-visible turn separately from queued follow-ups', () => {
|
|
497
|
+
const running = enqueue({
|
|
498
|
+
sessionId: 'sess-active-turn',
|
|
499
|
+
message: 'running now',
|
|
500
|
+
source: 'chat',
|
|
501
|
+
})
|
|
502
|
+
const queued = enqueue({
|
|
503
|
+
sessionId: 'sess-active-turn',
|
|
504
|
+
message: 'queued next',
|
|
505
|
+
source: 'chat',
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
const snapshot = mgr.getSessionQueueSnapshot('sess-active-turn')
|
|
509
|
+
assert.equal(snapshot.activeRunId, running.runId)
|
|
510
|
+
assert.deepEqual(snapshot.activeTurn, {
|
|
511
|
+
runId: running.runId,
|
|
512
|
+
sessionId: 'sess-active-turn',
|
|
513
|
+
missionId: null,
|
|
514
|
+
text: 'running now',
|
|
515
|
+
queuedAt: snapshot.activeTurn?.queuedAt,
|
|
516
|
+
position: 0,
|
|
517
|
+
imagePath: undefined,
|
|
518
|
+
imageUrl: undefined,
|
|
519
|
+
attachedFiles: undefined,
|
|
520
|
+
replyToId: undefined,
|
|
521
|
+
source: 'chat',
|
|
522
|
+
})
|
|
523
|
+
assert.deepEqual(snapshot.items.map((item) => item.runId), [queued.runId])
|
|
524
|
+
assert.equal(snapshot.queueLength, 1)
|
|
525
|
+
})
|
|
526
|
+
|
|
524
527
|
it('hides internal queued runs from the user-visible queue snapshot', () => {
|
|
525
528
|
enqueue({ sessionId: 'sess-visible-queue', message: 'running' })
|
|
526
529
|
enqueue({
|
|
@@ -538,9 +541,36 @@ describe('session-run-manager', () => {
|
|
|
538
541
|
|
|
539
542
|
const snapshot = mgr.getSessionQueueSnapshot('sess-visible-queue')
|
|
540
543
|
assert.equal(snapshot.queueLength, 1)
|
|
544
|
+
assert.equal(snapshot.activeTurn?.runId, snapshot.activeRunId)
|
|
541
545
|
assert.deepEqual(snapshot.items.map((item) => item.runId), [visible.runId])
|
|
542
546
|
})
|
|
543
547
|
|
|
548
|
+
it('hides internal active runs from the active-turn snapshot field', () => {
|
|
549
|
+
const { entry, promise } = makeManualQueuedEntry({
|
|
550
|
+
sessionId: 'sess-hidden-active',
|
|
551
|
+
runId: 'run-hidden',
|
|
552
|
+
message: 'heartbeat hidden',
|
|
553
|
+
internal: true,
|
|
554
|
+
source: 'heartbeat',
|
|
555
|
+
})
|
|
556
|
+
entry.run.status = 'running'
|
|
557
|
+
entry.run.startedAt = Date.now()
|
|
558
|
+
const state = getRuntimeState()
|
|
559
|
+
state.runningByExecution.set(entry.executionKey, entry as unknown)
|
|
560
|
+
state.runs.set(entry.run.id, entry.run)
|
|
561
|
+
state.promises.set(entry.run.id, promise)
|
|
562
|
+
pendingPromises.push(promise.catch(() => {}))
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
const snapshot = mgr.getSessionQueueSnapshot('sess-hidden-active')
|
|
566
|
+
assert.equal(snapshot.activeRunId, 'run-hidden')
|
|
567
|
+
assert.equal(snapshot.activeTurn, null)
|
|
568
|
+
assert.equal(snapshot.queueLength, 0)
|
|
569
|
+
} finally {
|
|
570
|
+
entry.resolve(undefined)
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
|
|
544
574
|
it('reports heartbeat vs non-heartbeat queued runs', () => {
|
|
545
575
|
enqueue({ sessionId: 'sess-hb-state', message: 'occupier' })
|
|
546
576
|
enqueue({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import test from 'node:test'
|
|
3
|
-
import { resolveSandboxRuntimeStatus, resolveSandboxWorkdir } from '@/lib/server/sandbox/session-runtime'
|
|
3
|
+
import { resolveSandboxRuntimeStatus, resolveSandboxSessionContext, resolveSandboxWorkdir } from '@/lib/server/sandbox/session-runtime'
|
|
4
4
|
import type { Session } from '@/types'
|
|
5
5
|
|
|
6
6
|
test('resolveSandboxRuntimeStatus defaults enabled sandboxes to all sessions', () => {
|
|
@@ -49,6 +49,23 @@ test('resolveSandboxRuntimeStatus sandboxes child sessions in non-main mode', ()
|
|
|
49
49
|
assert.equal(status.scopeKey, 'agent:agent-1')
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
+
test('resolveSandboxSessionContext resolves browser-compatible workspace context without starting containers', () => {
|
|
53
|
+
const context = resolveSandboxSessionContext({
|
|
54
|
+
config: { enabled: true, scope: 'agent', workdir: '/workspace' },
|
|
55
|
+
session: {
|
|
56
|
+
id: 'child-session',
|
|
57
|
+
agentId: 'agent-1',
|
|
58
|
+
parentSessionId: 'main-session',
|
|
59
|
+
} as Session,
|
|
60
|
+
workspaceDir: '/tmp/project',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
assert.ok(context)
|
|
64
|
+
assert.equal(context?.scopeKey, 'agent:agent-1')
|
|
65
|
+
assert.equal(context?.workspaceDir, '/tmp/project')
|
|
66
|
+
assert.equal(context?.containerWorkdir, '/workspace')
|
|
67
|
+
})
|
|
68
|
+
|
|
52
69
|
test('resolveSandboxWorkdir maps nested host paths into the container workspace', () => {
|
|
53
70
|
const resolved = resolveSandboxWorkdir({
|
|
54
71
|
workspaceDir: '/tmp/project',
|