@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -0,0 +1,119 @@
|
|
|
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-runtime-settings-'))
|
|
12
|
+
try {
|
|
13
|
+
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
|
|
14
|
+
cwd: repoRoot,
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
DATA_DIR: tempDir,
|
|
18
|
+
WORKSPACE_DIR: path.join(tempDir, 'workspace'),
|
|
19
|
+
},
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
})
|
|
22
|
+
assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
|
|
23
|
+
const lines = (result.stdout || '')
|
|
24
|
+
.trim()
|
|
25
|
+
.split('\n')
|
|
26
|
+
.map((line) => line.trim())
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
|
|
29
|
+
return JSON.parse(jsonLine || '{}')
|
|
30
|
+
} finally {
|
|
31
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('runtime settings defaults', () => {
|
|
36
|
+
it('backfills explicit runtime defaults for clean installs', () => {
|
|
37
|
+
const output = runWithTempDataDir(`
|
|
38
|
+
const storageMod = await import('./src/lib/server/storage.ts')
|
|
39
|
+
const runtimeMod = await import('./src/lib/server/runtime-settings.ts')
|
|
40
|
+
const storage = storageMod.default || storageMod
|
|
41
|
+
const runtime = runtimeMod.default || runtimeMod
|
|
42
|
+
console.log(JSON.stringify({
|
|
43
|
+
settings: storage.loadSettings(),
|
|
44
|
+
runtime: runtime.loadRuntimeSettings(),
|
|
45
|
+
}))
|
|
46
|
+
`)
|
|
47
|
+
|
|
48
|
+
assert.equal(output.settings.loopMode, 'bounded')
|
|
49
|
+
assert.equal(output.settings.agentLoopRecursionLimit, 60)
|
|
50
|
+
assert.equal(output.settings.orchestratorLoopRecursionLimit, 80)
|
|
51
|
+
assert.equal(output.settings.legacyOrchestratorMaxTurns, 16)
|
|
52
|
+
assert.equal(output.settings.ongoingLoopMaxIterations, 250)
|
|
53
|
+
assert.equal(output.settings.ongoingLoopMaxRuntimeMinutes, 60)
|
|
54
|
+
assert.equal(output.settings.delegationMaxDepth, 3)
|
|
55
|
+
assert.equal(output.settings.shellCommandTimeoutSec, 30)
|
|
56
|
+
assert.equal(output.settings.claudeCodeTimeoutSec, 1800)
|
|
57
|
+
assert.equal(output.settings.cliProcessTimeoutSec, 1800)
|
|
58
|
+
assert.equal(output.settings.heartbeatIntervalSec, 1800)
|
|
59
|
+
assert.equal(output.settings.heartbeatAckMaxChars, 300)
|
|
60
|
+
assert.equal(output.settings.heartbeatShowOk, false)
|
|
61
|
+
assert.equal(output.settings.heartbeatShowAlerts, true)
|
|
62
|
+
assert.equal(output.settings.heartbeatTarget, null)
|
|
63
|
+
assert.equal(output.settings.heartbeatPrompt, null)
|
|
64
|
+
assert.equal(output.runtime.agentLoopRecursionLimit, 60)
|
|
65
|
+
assert.equal(output.runtime.orchestratorLoopRecursionLimit, 80)
|
|
66
|
+
assert.equal(output.runtime.legacyOrchestratorMaxTurns, 16)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('clamps invalid persisted runtime settings into the supported range', () => {
|
|
70
|
+
const output = runWithTempDataDir(`
|
|
71
|
+
const storageMod = await import('./src/lib/server/storage.ts')
|
|
72
|
+
const runtimeMod = await import('./src/lib/server/runtime-settings.ts')
|
|
73
|
+
const storage = storageMod.default || storageMod
|
|
74
|
+
const runtime = runtimeMod.default || runtimeMod
|
|
75
|
+
|
|
76
|
+
storage.saveSettings({
|
|
77
|
+
loopMode: 'invalid',
|
|
78
|
+
agentLoopRecursionLimit: 999,
|
|
79
|
+
orchestratorLoopRecursionLimit: -5,
|
|
80
|
+
legacyOrchestratorMaxTurns: 0,
|
|
81
|
+
ongoingLoopMaxIterations: 999999,
|
|
82
|
+
ongoingLoopMaxRuntimeMinutes: -1,
|
|
83
|
+
delegationMaxDepth: 99,
|
|
84
|
+
shellCommandTimeoutSec: 0,
|
|
85
|
+
claudeCodeTimeoutSec: 999999,
|
|
86
|
+
cliProcessTimeoutSec: 'abc',
|
|
87
|
+
heartbeatIntervalSec: 999999,
|
|
88
|
+
heartbeatAckMaxChars: -50,
|
|
89
|
+
heartbeatShowOk: 'yes',
|
|
90
|
+
heartbeatShowAlerts: 'off',
|
|
91
|
+
heartbeatTarget: ' ',
|
|
92
|
+
heartbeatPrompt: ' ',
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
console.log(JSON.stringify({
|
|
96
|
+
settings: storage.loadSettings(),
|
|
97
|
+
runtime: runtime.loadRuntimeSettings(),
|
|
98
|
+
}))
|
|
99
|
+
`)
|
|
100
|
+
|
|
101
|
+
assert.equal(output.settings.loopMode, 'bounded')
|
|
102
|
+
assert.equal(output.settings.agentLoopRecursionLimit, 200)
|
|
103
|
+
assert.equal(output.settings.orchestratorLoopRecursionLimit, 1)
|
|
104
|
+
assert.equal(output.settings.legacyOrchestratorMaxTurns, 1)
|
|
105
|
+
assert.equal(output.settings.ongoingLoopMaxIterations, 5000)
|
|
106
|
+
assert.equal(output.settings.ongoingLoopMaxRuntimeMinutes, 0)
|
|
107
|
+
assert.equal(output.settings.delegationMaxDepth, 12)
|
|
108
|
+
assert.equal(output.settings.shellCommandTimeoutSec, 1)
|
|
109
|
+
assert.equal(output.settings.claudeCodeTimeoutSec, 7200)
|
|
110
|
+
assert.equal(output.settings.cliProcessTimeoutSec, 1800)
|
|
111
|
+
assert.equal(output.settings.heartbeatIntervalSec, 86400)
|
|
112
|
+
assert.equal(output.settings.heartbeatAckMaxChars, 0)
|
|
113
|
+
assert.equal(output.settings.heartbeatShowOk, true)
|
|
114
|
+
assert.equal(output.settings.heartbeatShowAlerts, false)
|
|
115
|
+
assert.equal(output.settings.heartbeatTarget, null)
|
|
116
|
+
assert.equal(output.settings.heartbeatPrompt, null)
|
|
117
|
+
assert.equal(output.runtime.ongoingLoopMaxRuntimeMs, null)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import type { LoopMode } from '@/types'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
DEFAULT_CLAUDE_CODE_TIMEOUT_SEC,
|
|
5
|
-
DEFAULT_CLI_PROCESS_TIMEOUT_SEC,
|
|
6
|
-
DEFAULT_DELEGATION_MAX_DEPTH,
|
|
7
|
-
DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS,
|
|
8
|
-
DEFAULT_LOOP_MODE,
|
|
9
|
-
DEFAULT_ONGOING_LOOP_MAX_ITERATIONS,
|
|
10
|
-
DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES,
|
|
11
|
-
DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT,
|
|
12
|
-
DEFAULT_SHELL_COMMAND_TIMEOUT_SEC,
|
|
3
|
+
normalizeRuntimeSettingFields,
|
|
13
4
|
} from '@/lib/runtime-loop'
|
|
14
5
|
import { loadSettings } from './storage'
|
|
15
6
|
|
|
@@ -26,92 +17,21 @@ export interface RuntimeSettings {
|
|
|
26
17
|
cliProcessTimeoutMs: number
|
|
27
18
|
}
|
|
28
19
|
|
|
29
|
-
function parseIntSetting(value: unknown, fallback: number, min: number, max: number): number {
|
|
30
|
-
const parsed = typeof value === 'number'
|
|
31
|
-
? value
|
|
32
|
-
: typeof value === 'string'
|
|
33
|
-
? Number.parseInt(value, 10)
|
|
34
|
-
: Number.NaN
|
|
35
|
-
if (!Number.isFinite(parsed)) return fallback
|
|
36
|
-
const int = Math.trunc(parsed)
|
|
37
|
-
return Math.max(min, Math.min(max, int))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function parseLoopMode(value: unknown): LoopMode {
|
|
41
|
-
return value === 'ongoing' ? 'ongoing' : DEFAULT_LOOP_MODE
|
|
42
|
-
}
|
|
43
|
-
|
|
44
20
|
export function loadRuntimeSettings(): RuntimeSettings {
|
|
45
21
|
const settings = loadSettings()
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const agentLoopRecursionLimit = parseIntSetting(
|
|
49
|
-
settings.agentLoopRecursionLimit,
|
|
50
|
-
DEFAULT_AGENT_LOOP_RECURSION_LIMIT,
|
|
51
|
-
1,
|
|
52
|
-
200,
|
|
53
|
-
)
|
|
54
|
-
const orchestratorLoopRecursionLimit = parseIntSetting(
|
|
55
|
-
settings.orchestratorLoopRecursionLimit,
|
|
56
|
-
DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT,
|
|
57
|
-
1,
|
|
58
|
-
300,
|
|
59
|
-
)
|
|
60
|
-
const legacyOrchestratorMaxTurns = parseIntSetting(
|
|
61
|
-
settings.legacyOrchestratorMaxTurns,
|
|
62
|
-
DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS,
|
|
63
|
-
1,
|
|
64
|
-
300,
|
|
65
|
-
)
|
|
66
|
-
const delegationMaxDepth = parseIntSetting(
|
|
67
|
-
settings.delegationMaxDepth,
|
|
68
|
-
DEFAULT_DELEGATION_MAX_DEPTH,
|
|
69
|
-
1,
|
|
70
|
-
12,
|
|
71
|
-
)
|
|
72
|
-
const ongoingLoopMaxIterations = parseIntSetting(
|
|
73
|
-
settings.ongoingLoopMaxIterations,
|
|
74
|
-
DEFAULT_ONGOING_LOOP_MAX_ITERATIONS,
|
|
75
|
-
10,
|
|
76
|
-
5000,
|
|
77
|
-
)
|
|
78
|
-
const ongoingLoopMaxRuntimeMinutes = parseIntSetting(
|
|
79
|
-
settings.ongoingLoopMaxRuntimeMinutes,
|
|
80
|
-
DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES,
|
|
81
|
-
0,
|
|
82
|
-
1440,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
const shellCommandTimeoutSec = parseIntSetting(
|
|
86
|
-
settings.shellCommandTimeoutSec,
|
|
87
|
-
DEFAULT_SHELL_COMMAND_TIMEOUT_SEC,
|
|
88
|
-
1,
|
|
89
|
-
600,
|
|
90
|
-
)
|
|
91
|
-
const claudeCodeTimeoutSec = parseIntSetting(
|
|
92
|
-
settings.claudeCodeTimeoutSec,
|
|
93
|
-
DEFAULT_CLAUDE_CODE_TIMEOUT_SEC,
|
|
94
|
-
5,
|
|
95
|
-
7200,
|
|
96
|
-
)
|
|
97
|
-
const cliProcessTimeoutSec = parseIntSetting(
|
|
98
|
-
settings.cliProcessTimeoutSec,
|
|
99
|
-
DEFAULT_CLI_PROCESS_TIMEOUT_SEC,
|
|
100
|
-
10,
|
|
101
|
-
7200,
|
|
102
|
-
)
|
|
22
|
+
const normalized = normalizeRuntimeSettingFields(settings)
|
|
103
23
|
|
|
104
24
|
return {
|
|
105
|
-
loopMode,
|
|
106
|
-
agentLoopRecursionLimit,
|
|
107
|
-
orchestratorLoopRecursionLimit,
|
|
108
|
-
legacyOrchestratorMaxTurns,
|
|
109
|
-
delegationMaxDepth,
|
|
110
|
-
ongoingLoopMaxIterations,
|
|
111
|
-
ongoingLoopMaxRuntimeMs: ongoingLoopMaxRuntimeMinutes > 0 ? ongoingLoopMaxRuntimeMinutes * 60_000 : null,
|
|
112
|
-
shellCommandTimeoutMs: shellCommandTimeoutSec * 1000,
|
|
113
|
-
claudeCodeTimeoutMs: claudeCodeTimeoutSec * 1000,
|
|
114
|
-
cliProcessTimeoutMs: cliProcessTimeoutSec * 1000,
|
|
25
|
+
loopMode: normalized.loopMode as LoopMode,
|
|
26
|
+
agentLoopRecursionLimit: normalized.agentLoopRecursionLimit,
|
|
27
|
+
orchestratorLoopRecursionLimit: normalized.orchestratorLoopRecursionLimit,
|
|
28
|
+
legacyOrchestratorMaxTurns: normalized.legacyOrchestratorMaxTurns,
|
|
29
|
+
delegationMaxDepth: normalized.delegationMaxDepth,
|
|
30
|
+
ongoingLoopMaxIterations: normalized.ongoingLoopMaxIterations,
|
|
31
|
+
ongoingLoopMaxRuntimeMs: normalized.ongoingLoopMaxRuntimeMinutes > 0 ? normalized.ongoingLoopMaxRuntimeMinutes * 60_000 : null,
|
|
32
|
+
shellCommandTimeoutMs: normalized.shellCommandTimeoutSec * 1000,
|
|
33
|
+
claudeCodeTimeoutMs: normalized.claudeCodeTimeoutSec * 1000,
|
|
34
|
+
cliProcessTimeoutMs: normalized.cliProcessTimeoutSec * 1000,
|
|
115
35
|
}
|
|
116
36
|
}
|
|
117
37
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { WORKSPACE_DIR } from './data-dir'
|
|
4
|
+
|
|
5
|
+
type SchedulePayload = Record<string, unknown>
|
|
6
|
+
|
|
7
|
+
export interface NormalizeScheduleOptions {
|
|
8
|
+
cwd?: string | null
|
|
9
|
+
now?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type NormalizeScheduleResult =
|
|
13
|
+
| { ok: true; value: SchedulePayload }
|
|
14
|
+
| { ok: false; error: string }
|
|
15
|
+
|
|
16
|
+
const SCRIPT_FILE_EXT = /\.(py|js|mjs|cjs|ts|tsx|sh|bash|zsh|rb|php|pl)$/i
|
|
17
|
+
const DIRECT_SCRIPT_RUNNERS = new Set(['python', 'python3', 'python3.11', 'node', 'bash', 'sh', 'zsh', 'ruby', 'tsx', 'ts-node'])
|
|
18
|
+
const VALID_STATUSES = new Set(['active', 'paused', 'completed', 'failed'])
|
|
19
|
+
|
|
20
|
+
function trimString(value: unknown): string {
|
|
21
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeScheduleType(value: unknown): 'cron' | 'interval' | 'once' {
|
|
25
|
+
if (value === 'cron' || value === 'interval' || value === 'once') return value
|
|
26
|
+
return 'interval'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizePositiveInt(value: unknown): number | null {
|
|
30
|
+
const parsed = typeof value === 'number'
|
|
31
|
+
? value
|
|
32
|
+
: typeof value === 'string'
|
|
33
|
+
? Number.parseInt(value, 10)
|
|
34
|
+
: Number.NaN
|
|
35
|
+
if (!Number.isFinite(parsed)) return null
|
|
36
|
+
const intValue = Math.trunc(parsed)
|
|
37
|
+
return intValue > 0 ? intValue : null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isWithinDirectory(parent: string, child: string): boolean {
|
|
41
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child))
|
|
42
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveRelativePath(baseDir: string, candidate: string): string | null {
|
|
46
|
+
const trimmed = trimString(candidate)
|
|
47
|
+
if (!trimmed) return null
|
|
48
|
+
if (path.isAbsolute(trimmed)) {
|
|
49
|
+
const resolvedAbsolute = path.resolve(trimmed)
|
|
50
|
+
return isWithinDirectory(baseDir, resolvedAbsolute) ? resolvedAbsolute : null
|
|
51
|
+
}
|
|
52
|
+
const resolved = path.resolve(baseDir, trimmed)
|
|
53
|
+
return isWithinDirectory(baseDir, resolved) ? resolved : null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tokenizeCommand(command: string): string[] {
|
|
57
|
+
return String(command || '').match(/(?:[^\s"'`]+|"[^"]*"|'[^']*')+/g) || []
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function unquoteToken(token: string): string {
|
|
61
|
+
if ((token.startsWith('"') && token.endsWith('"')) || (token.startsWith('\'') && token.endsWith('\''))) {
|
|
62
|
+
return token.slice(1, -1)
|
|
63
|
+
}
|
|
64
|
+
return token
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function looksLikeScriptPath(token: string): boolean {
|
|
68
|
+
return SCRIPT_FILE_EXT.test(token) || token.includes('/') || token.includes(path.sep)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractScriptPathFromCommand(command: string): string | null {
|
|
72
|
+
const tokens = tokenizeCommand(command).map(unquoteToken).filter(Boolean)
|
|
73
|
+
if (!tokens.length) return null
|
|
74
|
+
|
|
75
|
+
const commandName = path.basename(tokens[0] || '').toLowerCase()
|
|
76
|
+
let startIndex = 1
|
|
77
|
+
if (commandName === 'npx' && tokens[1]) {
|
|
78
|
+
const nestedRunner = path.basename(tokens[1]).toLowerCase()
|
|
79
|
+
if (nestedRunner === 'tsx' || nestedRunner === 'ts-node') startIndex = 2
|
|
80
|
+
} else if (commandName === 'deno' && tokens[1] === 'run') {
|
|
81
|
+
startIndex = 2
|
|
82
|
+
} else if (!DIRECT_SCRIPT_RUNNERS.has(commandName)) {
|
|
83
|
+
startIndex = 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let index = startIndex; index < tokens.length; index += 1) {
|
|
87
|
+
const candidate = tokens[index]
|
|
88
|
+
if (!candidate || candidate.startsWith('-')) continue
|
|
89
|
+
if (!looksLikeScriptPath(candidate)) continue
|
|
90
|
+
return candidate
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function deriveTaskPrompt(payload: SchedulePayload): string {
|
|
97
|
+
const explicitTaskPrompt = trimString(payload.taskPrompt)
|
|
98
|
+
if (explicitTaskPrompt) return explicitTaskPrompt
|
|
99
|
+
|
|
100
|
+
const command = trimString(payload.command)
|
|
101
|
+
if (command) {
|
|
102
|
+
return `Execute the command \`${command}\` from this schedule's working directory and report the result, including any errors.`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const filePath = trimString(payload.path)
|
|
106
|
+
if (!filePath) return ''
|
|
107
|
+
|
|
108
|
+
const action = trimString(payload.action).toLowerCase()
|
|
109
|
+
if (action === 'run_script') {
|
|
110
|
+
return `Run the script at \`${filePath}\` from this schedule's working directory and report the result, including any errors.`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return `Use the file at \`${filePath}\` to complete this scheduled task and report the result.`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function validateScheduleArtifacts(payload: SchedulePayload, baseDir: string): string | null {
|
|
117
|
+
const action = trimString(payload.action).toLowerCase()
|
|
118
|
+
const filePath = trimString(payload.path)
|
|
119
|
+
const command = trimString(payload.command)
|
|
120
|
+
|
|
121
|
+
if (action === 'run_script' && !filePath) {
|
|
122
|
+
return 'run_script schedules require a path.'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (filePath) {
|
|
126
|
+
const resolved = resolveRelativePath(baseDir, filePath)
|
|
127
|
+
if (!resolved) return `schedule path must stay inside ${baseDir}: ${filePath}`
|
|
128
|
+
if (!fs.existsSync(resolved)) return `schedule path not found: ${filePath}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!command) return null
|
|
132
|
+
const commandScriptPath = extractScriptPathFromCommand(command)
|
|
133
|
+
if (!commandScriptPath) return null
|
|
134
|
+
const resolved = resolveRelativePath(baseDir, commandScriptPath)
|
|
135
|
+
if (!resolved) return `schedule command references a path outside ${baseDir}: ${commandScriptPath}`
|
|
136
|
+
if (!fs.existsSync(resolved)) return `schedule command references a missing file: ${commandScriptPath}`
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function normalizeSchedulePayload(payload: SchedulePayload, opts: NormalizeScheduleOptions = {}): NormalizeScheduleResult {
|
|
141
|
+
const now = typeof opts.now === 'number' ? opts.now : Date.now()
|
|
142
|
+
const baseDir = path.resolve(trimString(opts.cwd) || WORKSPACE_DIR)
|
|
143
|
+
const normalized: SchedulePayload = {
|
|
144
|
+
...payload,
|
|
145
|
+
scheduleType: normalizeScheduleType(payload.scheduleType),
|
|
146
|
+
}
|
|
147
|
+
const action = trimString(normalized.action)
|
|
148
|
+
const command = trimString(normalized.command)
|
|
149
|
+
const filePath = trimString(normalized.path)
|
|
150
|
+
if (action) normalized.action = action
|
|
151
|
+
if (command) normalized.command = command
|
|
152
|
+
if (filePath) normalized.path = filePath
|
|
153
|
+
|
|
154
|
+
const status = trimString(normalized.status).toLowerCase()
|
|
155
|
+
normalized.status = VALID_STATUSES.has(status) ? status : 'active'
|
|
156
|
+
|
|
157
|
+
const agentId = trimString(normalized.agentId)
|
|
158
|
+
if (!agentId) {
|
|
159
|
+
return { ok: false, error: 'Error: schedules require a target agentId.' }
|
|
160
|
+
}
|
|
161
|
+
normalized.agentId = agentId
|
|
162
|
+
|
|
163
|
+
const taskPrompt = deriveTaskPrompt(normalized)
|
|
164
|
+
if (!taskPrompt) {
|
|
165
|
+
return { ok: false, error: 'Error: schedules require a taskPrompt, command, or action/path payload.' }
|
|
166
|
+
}
|
|
167
|
+
normalized.taskPrompt = taskPrompt
|
|
168
|
+
|
|
169
|
+
const validationError = validateScheduleArtifacts(normalized, baseDir)
|
|
170
|
+
if (validationError) return { ok: false, error: `Error: ${validationError}` }
|
|
171
|
+
|
|
172
|
+
if (normalized.nextRunAt == null) {
|
|
173
|
+
if (normalized.scheduleType === 'once') {
|
|
174
|
+
const runAt = normalizePositiveInt(normalized.runAt)
|
|
175
|
+
if (runAt != null) normalized.nextRunAt = runAt
|
|
176
|
+
} else if (normalized.scheduleType === 'interval') {
|
|
177
|
+
const intervalMs = normalizePositiveInt(normalized.intervalMs)
|
|
178
|
+
if (intervalMs != null) normalized.nextRunAt = now + intervalMs
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { ok: true, value: normalized }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function extractScheduleCommandScriptPath(command: string): string | null {
|
|
186
|
+
return extractScriptPathFromCommand(command)
|
|
187
|
+
}
|
|
@@ -6,6 +6,7 @@ import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
|
6
6
|
import { getScheduleSignatureKey } from '@/lib/schedule-dedupe'
|
|
7
7
|
import { enqueueSystemEvent } from './system-events'
|
|
8
8
|
import { requestHeartbeatNow } from './heartbeat-wake'
|
|
9
|
+
import { processDueWatchJobs } from './watch-jobs'
|
|
9
10
|
|
|
10
11
|
const TICK_INTERVAL = 60_000 // 60 seconds
|
|
11
12
|
let intervalId: ReturnType<typeof setInterval> | null = null
|
|
@@ -73,6 +74,7 @@ function computeNextRuns() {
|
|
|
73
74
|
|
|
74
75
|
async function tick() {
|
|
75
76
|
const now = Date.now()
|
|
77
|
+
await processDueWatchJobs(now)
|
|
76
78
|
const schedules = loadSchedules()
|
|
77
79
|
const agents = loadAgents()
|
|
78
80
|
const tasks = loadTasks()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import type { Session } from '@/types'
|
|
4
|
+
import { buildSessionArchiveMarkdown, buildSessionArchivePayload } from './session-archive-memory'
|
|
5
|
+
|
|
6
|
+
test('buildSessionArchivePayload summarizes session transcript and metadata', () => {
|
|
7
|
+
const session = {
|
|
8
|
+
id: 'session-1',
|
|
9
|
+
name: 'Support Thread',
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
user: 'Alice',
|
|
12
|
+
provider: 'openai',
|
|
13
|
+
model: 'gpt-4.1',
|
|
14
|
+
claudeSessionId: null,
|
|
15
|
+
codexThreadId: null,
|
|
16
|
+
opencodeSessionId: null,
|
|
17
|
+
createdAt: Date.parse('2026-03-05T00:00:00.000Z'),
|
|
18
|
+
lastActiveAt: Date.parse('2026-03-05T10:00:00.000Z'),
|
|
19
|
+
sessionType: 'human',
|
|
20
|
+
messages: [
|
|
21
|
+
{ role: 'user', text: 'Can you help me debug this issue?', time: 1 },
|
|
22
|
+
{ role: 'assistant', text: 'Yes, show me the stack trace.', time: 2, toolEvents: [{ name: 'files', input: '{}' }] },
|
|
23
|
+
],
|
|
24
|
+
identityState: { personaLabel: 'Debugger' },
|
|
25
|
+
} as Session
|
|
26
|
+
|
|
27
|
+
const payload = buildSessionArchivePayload(session, { name: 'Swarmy' })
|
|
28
|
+
|
|
29
|
+
assert.ok(payload)
|
|
30
|
+
assert.equal(payload?.title, 'Session archive: Support Thread')
|
|
31
|
+
assert.match(payload?.content || '', /Transcript excerpt:/)
|
|
32
|
+
assert.match(payload?.content || '', /Swarmy/)
|
|
33
|
+
assert.equal(payload?.metadata.tier, 'archive')
|
|
34
|
+
assert.equal(payload?.references[0]?.type, 'session')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('buildSessionArchiveMarkdown creates a portable markdown snapshot', () => {
|
|
38
|
+
const session = {
|
|
39
|
+
id: 'session-3',
|
|
40
|
+
name: 'Architecture Review',
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
user: 'Alice',
|
|
43
|
+
provider: 'openai',
|
|
44
|
+
model: 'gpt-4.1',
|
|
45
|
+
claudeSessionId: null,
|
|
46
|
+
codexThreadId: null,
|
|
47
|
+
opencodeSessionId: null,
|
|
48
|
+
createdAt: Date.parse('2026-03-05T00:00:00.000Z'),
|
|
49
|
+
lastActiveAt: Date.parse('2026-03-05T10:00:00.000Z'),
|
|
50
|
+
sessionType: 'human',
|
|
51
|
+
messages: [
|
|
52
|
+
{ role: 'user', text: 'Summarize the new connector policy.', time: 1 },
|
|
53
|
+
{ role: 'assistant', text: 'It now uses scoped sessions and freshness resets.', time: 2 },
|
|
54
|
+
],
|
|
55
|
+
identityState: { personaLabel: 'Reviewer' },
|
|
56
|
+
} as Session
|
|
57
|
+
|
|
58
|
+
const payload = buildSessionArchivePayload(session, { name: 'Swarmy' })
|
|
59
|
+
assert.ok(payload)
|
|
60
|
+
|
|
61
|
+
const markdown = buildSessionArchiveMarkdown(session, payload!, { name: 'Swarmy' })
|
|
62
|
+
assert.match(markdown, /^# Session archive: Architecture Review/m)
|
|
63
|
+
assert.match(markdown, /## Archive Snapshot/)
|
|
64
|
+
assert.match(markdown, /## Transcript Excerpt/)
|
|
65
|
+
assert.match(markdown, /\*\*Swarmy\*\*/)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('buildSessionArchivePayload skips trivial sessions', () => {
|
|
69
|
+
const session = {
|
|
70
|
+
id: 'session-2',
|
|
71
|
+
name: 'Too Short',
|
|
72
|
+
cwd: process.cwd(),
|
|
73
|
+
user: 'Bob',
|
|
74
|
+
provider: 'openai',
|
|
75
|
+
model: 'gpt-4.1',
|
|
76
|
+
claudeSessionId: null,
|
|
77
|
+
codexThreadId: null,
|
|
78
|
+
opencodeSessionId: null,
|
|
79
|
+
createdAt: 1,
|
|
80
|
+
lastActiveAt: 1,
|
|
81
|
+
messages: [{ role: 'user', text: 'hi', time: 1 }],
|
|
82
|
+
} as Session
|
|
83
|
+
|
|
84
|
+
assert.equal(buildSessionArchivePayload(session), null)
|
|
85
|
+
})
|