@swarmclawai/swarmclaw 0.7.1 → 0.7.3
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 +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- 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/files/open/route.ts +16 -14
- 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/skills/route.ts +11 -3
- 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 +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- 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 +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- 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 +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- 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/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -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 +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- 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 +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- 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 +948 -112
- 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 +188 -9
- 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/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -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/heartbeat-service.ts +14 -40
- 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 +28 -1103
- 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 +5 -6
- 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 +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- 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 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- 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 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- 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/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import type { Agent, AppSettings, Session, SessionResetMode, SessionResetType } from '@/types'
|
|
2
|
+
|
|
3
|
+
export interface ResolvedSessionResetPolicy {
|
|
4
|
+
type: SessionResetType
|
|
5
|
+
mode: SessionResetMode
|
|
6
|
+
idleTimeoutSec: number | null
|
|
7
|
+
maxAgeSec: number | null
|
|
8
|
+
dailyResetAt: string | null
|
|
9
|
+
timezone: string | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SessionFreshnessSnapshot {
|
|
13
|
+
fresh: boolean
|
|
14
|
+
reason?: string
|
|
15
|
+
policy: ResolvedSessionResetPolicy
|
|
16
|
+
idleExpiresAt: number | null
|
|
17
|
+
dailyBoundaryKey: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_POLICIES: Record<SessionResetType, ResolvedSessionResetPolicy> = {
|
|
21
|
+
direct: {
|
|
22
|
+
type: 'direct',
|
|
23
|
+
mode: 'idle',
|
|
24
|
+
idleTimeoutSec: 12 * 60 * 60,
|
|
25
|
+
maxAgeSec: 7 * 24 * 60 * 60,
|
|
26
|
+
dailyResetAt: null,
|
|
27
|
+
timezone: null,
|
|
28
|
+
},
|
|
29
|
+
group: {
|
|
30
|
+
type: 'group',
|
|
31
|
+
mode: 'idle',
|
|
32
|
+
idleTimeoutSec: 6 * 60 * 60,
|
|
33
|
+
maxAgeSec: 3 * 24 * 60 * 60,
|
|
34
|
+
dailyResetAt: null,
|
|
35
|
+
timezone: null,
|
|
36
|
+
},
|
|
37
|
+
thread: {
|
|
38
|
+
type: 'thread',
|
|
39
|
+
mode: 'idle',
|
|
40
|
+
idleTimeoutSec: 4 * 60 * 60,
|
|
41
|
+
maxAgeSec: 2 * 24 * 60 * 60,
|
|
42
|
+
dailyResetAt: null,
|
|
43
|
+
timezone: null,
|
|
44
|
+
},
|
|
45
|
+
main: {
|
|
46
|
+
type: 'main',
|
|
47
|
+
mode: 'daily',
|
|
48
|
+
idleTimeoutSec: 24 * 60 * 60,
|
|
49
|
+
maxAgeSec: 14 * 24 * 60 * 60,
|
|
50
|
+
dailyResetAt: '04:00',
|
|
51
|
+
timezone: null,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseIntBounded(value: unknown, min: number, max: number): number | null {
|
|
56
|
+
if (value === null || value === undefined || value === '') return null
|
|
57
|
+
const parsed = typeof value === 'number'
|
|
58
|
+
? value
|
|
59
|
+
: typeof value === 'string'
|
|
60
|
+
? Number.parseInt(value.trim(), 10)
|
|
61
|
+
: Number.NaN
|
|
62
|
+
if (!Number.isFinite(parsed)) return null
|
|
63
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeMode(raw: unknown, fallback: SessionResetMode): SessionResetMode {
|
|
67
|
+
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : ''
|
|
68
|
+
return value === 'daily' ? 'daily' : value === 'idle' ? 'idle' : fallback
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeTimeHHMM(raw: unknown): string | null {
|
|
72
|
+
if (typeof raw !== 'string') return null
|
|
73
|
+
const value = raw.trim()
|
|
74
|
+
const match = value.match(/^(\d{1,2}):(\d{2})$/)
|
|
75
|
+
if (!match) return null
|
|
76
|
+
const hours = Number.parseInt(match[1], 10)
|
|
77
|
+
const minutes = Number.parseInt(match[2], 10)
|
|
78
|
+
if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return null
|
|
79
|
+
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null
|
|
80
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeTimezone(raw: unknown): string | null {
|
|
84
|
+
if (typeof raw !== 'string') return null
|
|
85
|
+
const value = raw.trim()
|
|
86
|
+
return value || null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getClockParts(date: Date, timezone?: string | null): { dateKey: string; minutes: number } | null {
|
|
90
|
+
try {
|
|
91
|
+
const formatter = new Intl.DateTimeFormat('en-CA', {
|
|
92
|
+
year: 'numeric',
|
|
93
|
+
month: '2-digit',
|
|
94
|
+
day: '2-digit',
|
|
95
|
+
hour: '2-digit',
|
|
96
|
+
minute: '2-digit',
|
|
97
|
+
hour12: false,
|
|
98
|
+
timeZone: timezone || undefined,
|
|
99
|
+
})
|
|
100
|
+
const parts = formatter.formatToParts(date)
|
|
101
|
+
const year = parts.find((part) => part.type === 'year')?.value
|
|
102
|
+
const month = parts.find((part) => part.type === 'month')?.value
|
|
103
|
+
const day = parts.find((part) => part.type === 'day')?.value
|
|
104
|
+
const hour = Number.parseInt(parts.find((part) => part.type === 'hour')?.value || '', 10)
|
|
105
|
+
const minute = Number.parseInt(parts.find((part) => part.type === 'minute')?.value || '', 10)
|
|
106
|
+
if (!year || !month || !day || !Number.isFinite(hour) || !Number.isFinite(minute)) return null
|
|
107
|
+
return {
|
|
108
|
+
dateKey: `${year}-${month}-${day}`,
|
|
109
|
+
minutes: hour * 60 + minute,
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function boundaryKeyForNow(now: number, boundaryMinutes: number, timezone?: string | null): string | null {
|
|
117
|
+
const current = getClockParts(new Date(now), timezone)
|
|
118
|
+
if (!current) return null
|
|
119
|
+
if (current.minutes >= boundaryMinutes) return current.dateKey
|
|
120
|
+
const previous = getClockParts(new Date(now - 24 * 60 * 60 * 1000), timezone)
|
|
121
|
+
return previous?.dateKey || null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function rawField(
|
|
125
|
+
session: Partial<Session> | null | undefined,
|
|
126
|
+
overrides: Record<string, unknown> | undefined,
|
|
127
|
+
agent: Partial<Agent> | null | undefined,
|
|
128
|
+
settings: Partial<AppSettings> | null | undefined,
|
|
129
|
+
key: 'sessionResetMode' | 'sessionIdleTimeoutSec' | 'sessionMaxAgeSec' | 'sessionDailyResetAt' | 'sessionResetTimezone',
|
|
130
|
+
): unknown {
|
|
131
|
+
if (session && session[key] !== undefined) return session[key]
|
|
132
|
+
if (overrides && overrides[key] !== undefined) return overrides[key]
|
|
133
|
+
if (agent && agent[key] !== undefined) return agent[key]
|
|
134
|
+
if (settings && settings[key] !== undefined) return settings[key]
|
|
135
|
+
return undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function inferSessionResetType(
|
|
139
|
+
session: Partial<Session> | null | undefined,
|
|
140
|
+
opts?: { isGroup?: boolean | null; threadId?: string | null },
|
|
141
|
+
): SessionResetType {
|
|
142
|
+
if ((session?.sessionType as string | undefined) === 'orchestrated') return 'main'
|
|
143
|
+
const threadId = opts?.threadId ?? session?.connectorContext?.threadId ?? null
|
|
144
|
+
if (threadId) return 'thread'
|
|
145
|
+
const isGroup = opts?.isGroup ?? session?.connectorContext?.isGroup ?? false
|
|
146
|
+
return isGroup ? 'group' : 'direct'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveSessionResetPolicy(params: {
|
|
150
|
+
session?: Partial<Session> | null
|
|
151
|
+
agent?: Partial<Agent> | null
|
|
152
|
+
settings?: Partial<AppSettings> | null
|
|
153
|
+
resetType?: SessionResetType
|
|
154
|
+
overrides?: Record<string, unknown>
|
|
155
|
+
}): ResolvedSessionResetPolicy {
|
|
156
|
+
const type = params.resetType ?? inferSessionResetType(params.session)
|
|
157
|
+
const defaults = DEFAULT_POLICIES[type]
|
|
158
|
+
return {
|
|
159
|
+
type,
|
|
160
|
+
mode: normalizeMode(
|
|
161
|
+
rawField(params.session, params.overrides, params.agent, params.settings, 'sessionResetMode'),
|
|
162
|
+
defaults.mode,
|
|
163
|
+
),
|
|
164
|
+
idleTimeoutSec: parseIntBounded(
|
|
165
|
+
rawField(params.session, params.overrides, params.agent, params.settings, 'sessionIdleTimeoutSec'),
|
|
166
|
+
0,
|
|
167
|
+
180 * 24 * 60 * 60,
|
|
168
|
+
) ?? defaults.idleTimeoutSec,
|
|
169
|
+
maxAgeSec: parseIntBounded(
|
|
170
|
+
rawField(params.session, params.overrides, params.agent, params.settings, 'sessionMaxAgeSec'),
|
|
171
|
+
0,
|
|
172
|
+
365 * 24 * 60 * 60,
|
|
173
|
+
) ?? defaults.maxAgeSec,
|
|
174
|
+
dailyResetAt: normalizeTimeHHMM(
|
|
175
|
+
rawField(params.session, params.overrides, params.agent, params.settings, 'sessionDailyResetAt'),
|
|
176
|
+
) ?? defaults.dailyResetAt,
|
|
177
|
+
timezone: normalizeTimezone(
|
|
178
|
+
rawField(params.session, params.overrides, params.agent, params.settings, 'sessionResetTimezone'),
|
|
179
|
+
) ?? defaults.timezone,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function evaluateSessionFreshness(params: {
|
|
184
|
+
session?: Partial<Session> | null
|
|
185
|
+
policy: ResolvedSessionResetPolicy
|
|
186
|
+
now?: number
|
|
187
|
+
}): SessionFreshnessSnapshot {
|
|
188
|
+
const now = typeof params.now === 'number' ? params.now : Date.now()
|
|
189
|
+
const session = params.session
|
|
190
|
+
const policy = params.policy
|
|
191
|
+
const messageCount = Array.isArray(session?.messages) ? session.messages.length : 0
|
|
192
|
+
const createdAt = typeof session?.createdAt === 'number' ? session.createdAt : now
|
|
193
|
+
const lastActiveAt = typeof session?.lastActiveAt === 'number' ? session.lastActiveAt : createdAt
|
|
194
|
+
const idleExpiresAt = typeof policy.idleTimeoutSec === 'number' && policy.idleTimeoutSec > 0
|
|
195
|
+
? lastActiveAt + policy.idleTimeoutSec * 1000
|
|
196
|
+
: null
|
|
197
|
+
|
|
198
|
+
if (!session || messageCount === 0) {
|
|
199
|
+
return {
|
|
200
|
+
fresh: true,
|
|
201
|
+
policy,
|
|
202
|
+
idleExpiresAt,
|
|
203
|
+
dailyBoundaryKey: null,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (idleExpiresAt !== null && now > idleExpiresAt) {
|
|
208
|
+
return {
|
|
209
|
+
fresh: false,
|
|
210
|
+
reason: `idle_timeout:${policy.idleTimeoutSec}`,
|
|
211
|
+
policy,
|
|
212
|
+
idleExpiresAt,
|
|
213
|
+
dailyBoundaryKey: null,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (typeof policy.maxAgeSec === 'number' && policy.maxAgeSec > 0) {
|
|
218
|
+
const maxAgeMs = policy.maxAgeSec * 1000
|
|
219
|
+
if (now - createdAt > maxAgeMs) {
|
|
220
|
+
return {
|
|
221
|
+
fresh: false,
|
|
222
|
+
reason: `max_age:${policy.maxAgeSec}`,
|
|
223
|
+
policy,
|
|
224
|
+
idleExpiresAt,
|
|
225
|
+
dailyBoundaryKey: null,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (policy.mode === 'daily' && policy.dailyResetAt) {
|
|
231
|
+
const boundary = normalizeTimeHHMM(policy.dailyResetAt)
|
|
232
|
+
if (boundary) {
|
|
233
|
+
const [hours, minutes] = boundary.split(':').map((value) => Number.parseInt(value, 10))
|
|
234
|
+
const boundaryMinutes = hours * 60 + minutes
|
|
235
|
+
const nowBoundaryKey = boundaryKeyForNow(now, boundaryMinutes, policy.timezone)
|
|
236
|
+
const lastActiveParts = getClockParts(new Date(lastActiveAt), policy.timezone)
|
|
237
|
+
if (
|
|
238
|
+
nowBoundaryKey
|
|
239
|
+
&& lastActiveParts
|
|
240
|
+
&& (
|
|
241
|
+
lastActiveParts.dateKey < nowBoundaryKey
|
|
242
|
+
|| (lastActiveParts.dateKey === nowBoundaryKey && lastActiveParts.minutes < boundaryMinutes)
|
|
243
|
+
)
|
|
244
|
+
) {
|
|
245
|
+
return {
|
|
246
|
+
fresh: false,
|
|
247
|
+
reason: `daily_reset:${policy.dailyResetAt}`,
|
|
248
|
+
policy,
|
|
249
|
+
idleExpiresAt,
|
|
250
|
+
dailyBoundaryKey: nowBoundaryKey,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
fresh: true,
|
|
255
|
+
policy,
|
|
256
|
+
idleExpiresAt,
|
|
257
|
+
dailyBoundaryKey: nowBoundaryKey,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
fresh: true,
|
|
264
|
+
policy,
|
|
265
|
+
idleExpiresAt,
|
|
266
|
+
dailyBoundaryKey: null,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function resetSessionRuntime(
|
|
271
|
+
session: Session,
|
|
272
|
+
reason: string,
|
|
273
|
+
opts?: { now?: number },
|
|
274
|
+
): number {
|
|
275
|
+
const now = typeof opts?.now === 'number' ? opts.now : Date.now()
|
|
276
|
+
const cleared = Array.isArray(session.messages) ? session.messages.length : 0
|
|
277
|
+
|
|
278
|
+
session.messages = []
|
|
279
|
+
session.claudeSessionId = null
|
|
280
|
+
session.codexThreadId = null
|
|
281
|
+
session.opencodeSessionId = null
|
|
282
|
+
session.delegateResumeIds = {
|
|
283
|
+
claudeCode: null,
|
|
284
|
+
codex: null,
|
|
285
|
+
opencode: null,
|
|
286
|
+
gemini: null,
|
|
287
|
+
}
|
|
288
|
+
session.createdAt = now
|
|
289
|
+
session.lastActiveAt = now
|
|
290
|
+
session.lastAutoMemoryAt = null
|
|
291
|
+
session.lastHeartbeatText = null
|
|
292
|
+
session.lastHeartbeatSentAt = null
|
|
293
|
+
session.conversationTone = undefined
|
|
294
|
+
session.lastSessionResetAt = now
|
|
295
|
+
session.lastSessionResetReason = reason
|
|
296
|
+
|
|
297
|
+
if (session.connectorContext) {
|
|
298
|
+
session.connectorContext = {
|
|
299
|
+
...session.connectorContext,
|
|
300
|
+
lastResetAt: now,
|
|
301
|
+
lastResetReason: reason,
|
|
302
|
+
lastInboundMessageId: null,
|
|
303
|
+
lastInboundReplyToMessageId: null,
|
|
304
|
+
lastInboundThreadId: null,
|
|
305
|
+
lastOutboundMessageId: null,
|
|
306
|
+
lastOutboundAt: null,
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return cleared
|
|
311
|
+
}
|
|
@@ -4,7 +4,9 @@ import { active, loadSessions } from './storage'
|
|
|
4
4
|
import { executeSessionChatTurn, type ExecuteChatTurnResult } from './chat-execution'
|
|
5
5
|
import { loadRuntimeSettings } from './runtime-settings'
|
|
6
6
|
import { log } from './logger'
|
|
7
|
-
import {
|
|
7
|
+
import { isInternalHeartbeatRun } from './heartbeat-source'
|
|
8
|
+
import { cleanupSessionBrowser } from './session-tools/web'
|
|
9
|
+
import { cancelDelegationJobsForParentSession } from './delegation-jobs'
|
|
8
10
|
|
|
9
11
|
export type SessionRunStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
10
12
|
export type SessionQueueMode = 'followup' | 'steer' | 'collect'
|
|
@@ -122,6 +124,23 @@ function emitRunMeta(entry: QueueEntry, status: SessionRunStatus, extra?: Record
|
|
|
122
124
|
})
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
function markRunningEntryCancelled(entry: QueueEntry, reason: string) {
|
|
128
|
+
if (entry.run.status === 'cancelled') return
|
|
129
|
+
entry.run.status = 'cancelled'
|
|
130
|
+
entry.run.endedAt = now()
|
|
131
|
+
entry.run.error = reason
|
|
132
|
+
emitRunMeta(entry, 'cancelled', { reason })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function abortSessionRuntime(entry: QueueEntry, reason: string) {
|
|
136
|
+
markRunningEntryCancelled(entry, reason)
|
|
137
|
+
entry.signalController.abort()
|
|
138
|
+
try { active.get(entry.run.sessionId)?.kill?.() } catch { /* noop */ }
|
|
139
|
+
active.delete(entry.run.sessionId)
|
|
140
|
+
try { cleanupSessionBrowser(entry.run.sessionId) } catch { /* noop */ }
|
|
141
|
+
try { cancelDelegationJobsForParentSession(entry.run.sessionId, reason) } catch { /* noop */ }
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
function executionKeyForSession(sessionId: string): string {
|
|
126
145
|
return `session:${sessionId}`
|
|
127
146
|
}
|
|
@@ -170,7 +189,7 @@ export function cancelAllHeartbeatRuns(reason = 'Heartbeat disabled globally'):
|
|
|
170
189
|
if (!queue.length) continue
|
|
171
190
|
const keep: QueueEntry[] = []
|
|
172
191
|
for (const entry of queue) {
|
|
173
|
-
const isHeartbeat = entry.run.internal
|
|
192
|
+
const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
|
|
174
193
|
if (!isHeartbeat) {
|
|
175
194
|
keep.push(entry)
|
|
176
195
|
continue
|
|
@@ -187,47 +206,15 @@ export function cancelAllHeartbeatRuns(reason = 'Heartbeat disabled globally'):
|
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
for (const entry of state.runningByExecution.values()) {
|
|
190
|
-
const isHeartbeat = entry.run.internal
|
|
209
|
+
const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
|
|
191
210
|
if (!isHeartbeat) continue
|
|
192
211
|
abortedRunning += 1
|
|
193
|
-
entry
|
|
194
|
-
try { active.get(entry.run.sessionId)?.kill?.() } catch { /* noop */ }
|
|
212
|
+
abortSessionRuntime(entry, reason)
|
|
195
213
|
}
|
|
196
214
|
|
|
197
215
|
return { cancelledQueued, abortedRunning }
|
|
198
216
|
}
|
|
199
217
|
|
|
200
|
-
function scheduleMainLoopFollowup(sessionId: string, followup: MainLoopFollowupRequest) {
|
|
201
|
-
const delayMs = Math.max(0, Math.trunc(followup.delayMs || 0))
|
|
202
|
-
setTimeout(() => {
|
|
203
|
-
try {
|
|
204
|
-
const sessions = loadSessions()
|
|
205
|
-
const session = sessions[sessionId]
|
|
206
|
-
if (!session || !isMainMissionSession(session)) return
|
|
207
|
-
enqueueSessionRun({
|
|
208
|
-
sessionId,
|
|
209
|
-
message: followup.message,
|
|
210
|
-
internal: true,
|
|
211
|
-
source: 'main-loop-followup',
|
|
212
|
-
mode: 'collect',
|
|
213
|
-
dedupeKey: followup.dedupeKey,
|
|
214
|
-
})
|
|
215
|
-
} catch (err: any) {
|
|
216
|
-
log.warn('session-run', `Failed to enqueue main-loop followup for ${sessionId}`, err?.message || String(err))
|
|
217
|
-
}
|
|
218
|
-
}, delayMs)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function isMainMissionSession(session: Record<string, unknown>): boolean {
|
|
222
|
-
const id = typeof session.id === 'string' ? session.id.trim() : ''
|
|
223
|
-
const name = typeof session.name === 'string' ? session.name.trim() : ''
|
|
224
|
-
const sessionType = typeof session.sessionType === 'string' ? session.sessionType : ''
|
|
225
|
-
if (id.startsWith('main-') || name === '__main__') return true
|
|
226
|
-
// Only orchestrated thread sessions should receive autonomous main-loop followups.
|
|
227
|
-
if (sessionType === 'orchestrated') return true
|
|
228
|
-
return false
|
|
229
|
-
}
|
|
230
|
-
|
|
231
218
|
async function drainExecution(executionKey: string): Promise<void> {
|
|
232
219
|
if (state.runningByExecution.has(executionKey)) return
|
|
233
220
|
const q = queueForExecution(executionKey)
|
|
@@ -271,49 +258,25 @@ async function drainExecution(executionKey: string): Promise<void> {
|
|
|
271
258
|
})
|
|
272
259
|
|
|
273
260
|
const failed = !!result.error
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
message: next.message,
|
|
279
|
-
internal: next.run.internal,
|
|
280
|
-
source: next.run.source,
|
|
281
|
-
resultText: result.text,
|
|
282
|
-
error: result.error,
|
|
283
|
-
toolEvents: result.toolEvents,
|
|
284
|
-
inputTokens: result.inputTokens,
|
|
285
|
-
outputTokens: result.outputTokens,
|
|
286
|
-
estimatedCost: result.estimatedCost,
|
|
287
|
-
})
|
|
288
|
-
} catch (mainLoopErr: any) {
|
|
289
|
-
log.warn('session-run', `Main-loop update failed for ${next.run.id}`, mainLoopErr?.message || String(mainLoopErr))
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
next.run.status = failed ? 'failed' : 'completed'
|
|
293
|
-
next.run.endedAt = now()
|
|
294
|
-
next.run.error = result.error
|
|
261
|
+
const aborted = next.signalController.signal.aborted
|
|
262
|
+
next.run.status = aborted ? 'cancelled' : (failed ? 'failed' : 'completed')
|
|
263
|
+
next.run.endedAt = next.run.endedAt || now()
|
|
264
|
+
next.run.error = aborted ? (next.run.error || 'Cancelled') : result.error
|
|
295
265
|
next.run.resultPreview = result.text?.slice(0, 280)
|
|
296
266
|
emitRunMeta(next, next.run.status, {
|
|
297
267
|
persisted: result.persisted,
|
|
298
268
|
hasText: !!result.text,
|
|
299
|
-
error:
|
|
269
|
+
error: next.run.error || null,
|
|
300
270
|
})
|
|
301
271
|
log.info('session-run', `Run finished ${next.run.id}`, {
|
|
302
272
|
sessionId: next.run.sessionId,
|
|
303
273
|
status: next.run.status,
|
|
304
274
|
persisted: result.persisted,
|
|
305
275
|
hasText: !!result.text,
|
|
306
|
-
error:
|
|
276
|
+
error: next.run.error || null,
|
|
307
277
|
durationMs: (next.run.endedAt || now()) - (next.run.startedAt || now()),
|
|
308
278
|
})
|
|
309
279
|
next.resolve(result)
|
|
310
|
-
if (!failed && followup) {
|
|
311
|
-
scheduleMainLoopFollowup(next.run.sessionId, followup)
|
|
312
|
-
log.info('session-run', `Queued main-loop followup after ${next.run.id}`, {
|
|
313
|
-
sessionId: next.run.sessionId,
|
|
314
|
-
delayMs: followup.delayMs,
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
280
|
} catch (err: any) {
|
|
318
281
|
const aborted = next.signalController.signal.aborted
|
|
319
282
|
next.run.status = aborted ? 'cancelled' : 'failed'
|
|
@@ -326,19 +289,6 @@ async function drainExecution(executionKey: string): Promise<void> {
|
|
|
326
289
|
error: next.run.error,
|
|
327
290
|
durationMs: (next.run.endedAt || now()) - (next.run.startedAt || now()),
|
|
328
291
|
})
|
|
329
|
-
try {
|
|
330
|
-
handleMainLoopRunResult({
|
|
331
|
-
sessionId: next.run.sessionId,
|
|
332
|
-
message: next.message,
|
|
333
|
-
internal: next.run.internal,
|
|
334
|
-
source: next.run.source,
|
|
335
|
-
resultText: '',
|
|
336
|
-
error: next.run.error,
|
|
337
|
-
toolEvents: [],
|
|
338
|
-
})
|
|
339
|
-
} catch {
|
|
340
|
-
// Main-loop bookkeeping failures should not affect queue execution.
|
|
341
|
-
}
|
|
342
292
|
next.reject(err instanceof Error ? err : new Error(next.run.error))
|
|
343
293
|
} finally {
|
|
344
294
|
if (runtimeTimer) clearTimeout(runtimeTimer)
|
|
@@ -530,7 +480,9 @@ export function getSessionRunState(sessionId: string): {
|
|
|
530
480
|
const running = state.runningByExecution.get(executionKey)
|
|
531
481
|
const queued = queueForExecution(executionKey).filter((entry) => entry.run.sessionId === sessionId).length
|
|
532
482
|
return {
|
|
533
|
-
runningRunId: running?.run.sessionId === sessionId
|
|
483
|
+
runningRunId: (running?.run.sessionId === sessionId && running.run.status === 'running')
|
|
484
|
+
? running.run.id
|
|
485
|
+
: undefined,
|
|
534
486
|
queueLength: queued,
|
|
535
487
|
}
|
|
536
488
|
}
|
|
@@ -564,8 +516,7 @@ export function cancelSessionRuns(sessionId: string, reason = 'Cancelled'): { ca
|
|
|
564
516
|
let cancelledRunning = false
|
|
565
517
|
if (running && running.run.sessionId === sessionId) {
|
|
566
518
|
cancelledRunning = true
|
|
567
|
-
running
|
|
568
|
-
try { active.get(sessionId)?.kill?.() } catch { /* noop */ }
|
|
519
|
+
abortSessionRuntime(running, reason)
|
|
569
520
|
}
|
|
570
521
|
const cancelledQueued = cancelPendingForSession(sessionId, reason)
|
|
571
522
|
return { cancelledQueued, cancelledRunning }
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { describe, it } from 'node:test'
|
|
5
|
+
|
|
6
|
+
const thisFile = new URL(import.meta.url).pathname
|
|
7
|
+
const toolsDir = path.dirname(thisFile)
|
|
8
|
+
const serverDir = path.resolve(toolsDir, '..')
|
|
9
|
+
|
|
10
|
+
function readToolSource(fileName: string): string {
|
|
11
|
+
return fs.readFileSync(path.join(toolsDir, fileName), 'utf-8')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readServerSource(fileName: string): string {
|
|
15
|
+
return fs.readFileSync(path.join(serverDir, fileName), 'utf-8')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('browser workflow surface', () => {
|
|
19
|
+
it('advertises the higher-level browser actions in web.ts', () => {
|
|
20
|
+
const src = readToolSource('web.ts')
|
|
21
|
+
for (const action of ['read_page', 'extract_links', 'extract_form_fields', 'extract_table', 'fill_form', 'submit_form', 'scroll_until', 'download_file', 'complete_web_task']) {
|
|
22
|
+
assert.equal(src.includes(`'${action}'`), true, `web.ts should expose ${action}`)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('durable wait surface', () => {
|
|
28
|
+
it('advertises the durable wait actions in monitor.ts', () => {
|
|
29
|
+
const src = readToolSource('monitor.ts')
|
|
30
|
+
for (const action of ['wait_until', 'wait_for_http', 'wait_for_file', 'wait_for_task', 'wait_for_webhook', 'wait_for_page_change']) {
|
|
31
|
+
assert.equal(src.includes(`'${action}'`), true, `monitor.ts should expose ${action}`)
|
|
32
|
+
}
|
|
33
|
+
assert.equal(src.includes('createDurableWatch'), true)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('routes schedule_wake through durable watch storage', () => {
|
|
37
|
+
const src = readToolSource('schedule.ts')
|
|
38
|
+
assert.equal(src.includes('createWatchJob'), true)
|
|
39
|
+
assert.equal(src.includes("type: 'time'"), true)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('delegation job handles', () => {
|
|
44
|
+
it('exposes subagent control actions', () => {
|
|
45
|
+
const src = readToolSource('subagent.ts')
|
|
46
|
+
for (const action of ['status', 'list', 'wait', 'cancel']) {
|
|
47
|
+
assert.equal(src.includes(`action === '${action}'`), true, `subagent.ts should handle ${action}`)
|
|
48
|
+
}
|
|
49
|
+
assert.equal(src.includes('createDelegationJob'), true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('builds delegate context from the invoking session and uses job records', () => {
|
|
53
|
+
const src = readToolSource('delegate.ts')
|
|
54
|
+
assert.equal(src.includes('buildDelegateContextFromSessionish'), true)
|
|
55
|
+
assert.equal(src.includes('createDelegationJob'), true)
|
|
56
|
+
assert.equal(src.includes('waitForDelegateJob'), true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('scheduler and daemon recover the durable autonomy jobs', () => {
|
|
60
|
+
const schedulerSrc = readServerSource('scheduler.ts')
|
|
61
|
+
const daemonSrc = readServerSource('daemon-state.ts')
|
|
62
|
+
assert.equal(schedulerSrc.includes('processDueWatchJobs'), true)
|
|
63
|
+
assert.equal(daemonSrc.includes('recoverStaleDelegationJobs'), true)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('primitive plugin surfaces', () => {
|
|
68
|
+
it('advertises mailbox and human-loop actions', () => {
|
|
69
|
+
const mailboxSrc = readToolSource('mailbox.ts')
|
|
70
|
+
const humanSrc = readToolSource('human-loop.ts')
|
|
71
|
+
for (const action of ['list_messages', 'list_threads', 'search_messages', 'read_message', 'download_attachment', 'reply', 'wait_for_email']) {
|
|
72
|
+
assert.equal(mailboxSrc.includes(`'${action}'`), true, `mailbox.ts should expose ${action}`)
|
|
73
|
+
}
|
|
74
|
+
for (const action of ['request_input', 'request_approval', 'wait_for_reply', 'wait_for_approval', 'list_mailbox', 'ack_mailbox', 'status']) {
|
|
75
|
+
assert.equal(humanSrc.includes(`'${action}'`), true, `human-loop.ts should expose ${action}`)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('advertises document, extract, table, and crawl actions', () => {
|
|
80
|
+
const documentSrc = readToolSource('document.ts')
|
|
81
|
+
const extractSrc = readToolSource('extract.ts')
|
|
82
|
+
const tableSrc = readToolSource('table.ts')
|
|
83
|
+
const crawlSrc = readToolSource('crawl.ts')
|
|
84
|
+
|
|
85
|
+
for (const action of ['read', 'metadata', 'ocr', 'extract_tables', 'store', 'list', 'search', 'get', 'delete']) {
|
|
86
|
+
assert.equal(documentSrc.includes(`'${action}'`), true, `document.ts should expose ${action}`)
|
|
87
|
+
}
|
|
88
|
+
for (const action of ['extract_structured', 'summarize', 'status']) {
|
|
89
|
+
assert.equal(extractSrc.includes(`'${action}'`), true, `extract.ts should expose ${action}`)
|
|
90
|
+
}
|
|
91
|
+
for (const action of ['read', 'load_csv', 'load_xlsx', 'summarize', 'filter', 'sort', 'group', 'pivot', 'dedupe', 'join', 'write']) {
|
|
92
|
+
assert.equal(tableSrc.includes(`'${action}'`), true, `table.ts should expose ${action}`)
|
|
93
|
+
}
|
|
94
|
+
for (const action of ['crawl_site', 'follow_pagination', 'extract_sitemap', 'dedupe_pages', 'batch_extract']) {
|
|
95
|
+
assert.equal(crawlSrc.includes(`'${action}'`), true, `crawl.ts should expose ${action}`)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('registers the primitive plugins in builtin-plugins.ts', () => {
|
|
100
|
+
const src = readServerSource('builtin-plugins.ts')
|
|
101
|
+
for (const moduleName of ['mailbox', 'human-loop', 'document', 'extract', 'table', 'crawl']) {
|
|
102
|
+
assert.equal(src.includes(`session-tools/${moduleName}`), true, `builtin-plugins.ts should import ${moduleName}`)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
})
|