@swarmclawai/swarmclaw 0.4.0 → 0.5.0
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 +21 -4
- package/bin/server-cmd.js +28 -19
- package/next.config.ts +13 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +39 -22
- package/src/app/api/agents/[id]/thread/route.ts +2 -2
- package/src/app/api/agents/route.ts +3 -2
- package/src/app/api/agents/trash/route.ts +44 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +17 -7
- package/src/app/api/connectors/[id]/webhook/route.ts +103 -0
- package/src/app/api/connectors/route.ts +6 -3
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -2
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +2 -2
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/agent-files/route.ts +57 -0
- package/src/app/api/openclaw/approvals/route.ts +46 -0
- package/src/app/api/openclaw/config-sync/route.ts +33 -0
- package/src/app/api/openclaw/cron/route.ts +52 -0
- package/src/app/api/openclaw/directory/route.ts +27 -0
- package/src/app/api/openclaw/discover/route.ts +62 -0
- package/src/app/api/openclaw/dotenv-keys/route.ts +18 -0
- package/src/app/api/openclaw/exec-config/route.ts +41 -0
- package/src/app/api/openclaw/gateway/route.ts +72 -0
- package/src/app/api/openclaw/history/route.ts +109 -0
- package/src/app/api/openclaw/media/route.ts +53 -0
- package/src/app/api/openclaw/models/route.ts +12 -0
- package/src/app/api/openclaw/permissions/route.ts +39 -0
- package/src/app/api/openclaw/sandbox-env/route.ts +69 -0
- package/src/app/api/openclaw/skills/install/route.ts +32 -0
- package/src/app/api/openclaw/skills/remove/route.ts +24 -0
- package/src/app/api/openclaw/skills/route.ts +82 -0
- package/src/app/api/openclaw/sync/route.ts +31 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -15
- package/src/app/api/providers/route.ts +2 -2
- package/src/app/api/schedules/[id]/route.ts +16 -18
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +2 -2
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +2 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/edit-resend/route.ts +22 -0
- package/src/app/api/sessions/[id]/fork/route.ts +44 -0
- package/src/app/api/sessions/[id]/messages/route.ts +20 -2
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +14 -4
- package/src/app/api/sessions/route.ts +8 -4
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +2 -2
- package/src/app/api/tasks/[id]/approve/route.ts +2 -1
- package/src/app/api/tasks/[id]/route.ts +6 -5
- package/src/app/api/tasks/route.ts +2 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/webhooks/[id]/route.ts +29 -31
- package/src/app/api/webhooks/route.ts +2 -2
- package/src/app/globals.css +14 -0
- package/src/app/layout.tsx +5 -20
- package/src/app/page.tsx +3 -24
- package/src/cli/index.js +60 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +45 -0
- package/src/components/agents/agent-card.tsx +19 -5
- package/src/components/agents/agent-chat-list.tsx +31 -24
- package/src/components/agents/agent-files-editor.tsx +185 -0
- package/src/components/agents/agent-list.tsx +84 -3
- package/src/components/agents/agent-sheet.tsx +147 -14
- package/src/components/agents/cron-job-form.tsx +137 -0
- package/src/components/agents/exec-config-panel.tsx +147 -0
- package/src/components/agents/inspector-panel.tsx +310 -0
- package/src/components/agents/openclaw-skills-panel.tsx +230 -0
- package/src/components/agents/permission-preset-selector.tsx +79 -0
- package/src/components/agents/personality-builder.tsx +111 -0
- package/src/components/agents/sandbox-env-panel.tsx +72 -0
- package/src/components/agents/skill-install-dialog.tsx +102 -0
- package/src/components/agents/trash-list.tsx +109 -0
- package/src/components/chat/chat-area.tsx +41 -6
- package/src/components/chat/chat-header.tsx +305 -29
- package/src/components/chat/chat-preview-panel.tsx +113 -0
- package/src/components/chat/exec-approval-card.tsx +89 -0
- package/src/components/chat/message-bubble.tsx +218 -36
- package/src/components/chat/message-list.tsx +135 -31
- package/src/components/chat/streaming-bubble.tsx +59 -10
- package/src/components/chat/suggestions-bar.tsx +74 -0
- package/src/components/chat/thinking-indicator.tsx +20 -6
- package/src/components/chat/tool-call-bubble.tsx +98 -19
- package/src/components/chat/tool-request-banner.tsx +20 -2
- package/src/components/chat/trace-block.tsx +103 -0
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +6 -2
- package/src/components/connectors/connector-sheet.tsx +31 -7
- package/src/components/layout/app-layout.tsx +47 -25
- package/src/components/projects/project-list.tsx +123 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/schedules/schedule-list.tsx +3 -1
- package/src/components/sessions/new-session-sheet.tsx +6 -6
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/settings/gateway-connection-panel.tsx +278 -0
- package/src/components/shared/avatar.tsx +13 -2
- package/src/components/shared/connector-platform-icon.tsx +4 -0
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-orchestrator.tsx +1 -2
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +74 -0
- package/src/components/skills/skill-list.tsx +2 -1
- package/src/components/tasks/task-board.tsx +1 -1
- package/src/components/tasks/task-list.tsx +5 -2
- package/src/components/tasks/task-sheet.tsx +12 -12
- package/src/hooks/use-continuous-speech.ts +181 -0
- package/src/hooks/use-openclaw-gateway.ts +63 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/lib/id.ts +6 -0
- package/src/lib/notification-sounds.ts +58 -0
- package/src/lib/personality-parser.ts +97 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +14 -1
- package/src/lib/providers/index.ts +6 -0
- package/src/lib/providers/ollama.ts +9 -1
- package/src/lib/providers/openai.ts +9 -1
- package/src/lib/providers/openclaw.ts +28 -2
- package/src/lib/runtime-loop.ts +2 -2
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +82 -6
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +217 -0
- package/src/lib/server/connectors/bluebubbles.ts +360 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +51 -8
- package/src/lib/server/connectors/manager.ts +424 -13
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +65 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/daemon-state.ts +11 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/main-agent-loop.ts +8 -9
- package/src/lib/server/main-session.ts +21 -0
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-config-sync.ts +107 -0
- package/src/lib/server/openclaw-exec-config.ts +52 -0
- package/src/lib/server/openclaw-gateway.ts +291 -0
- package/src/lib/server/openclaw-history-merge.ts +36 -0
- package/src/lib/server/openclaw-models.ts +56 -0
- package/src/lib/server/openclaw-permission-presets.ts +64 -0
- package/src/lib/server/openclaw-sync.ts +497 -0
- package/src/lib/server/orchestrator-lg.ts +30 -9
- package/src/lib/server/orchestrator.ts +4 -4
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +24 -11
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +2 -2
- package/src/lib/server/session-tools/connector.ts +53 -6
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +22 -6
- package/src/lib/server/session-tools/file.ts +192 -19
- package/src/lib/server/session-tools/index.ts +4 -2
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +33 -0
- package/src/lib/server/session-tools/search-providers.ts +277 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +2 -2
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/web.ts +53 -72
- package/src/lib/server/storage.ts +74 -11
- package/src/lib/server/stream-agent-chat.ts +53 -4
- package/src/lib/server/suggestions.ts +20 -0
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/ws-hub.ts +14 -0
- package/src/lib/tool-definitions.ts +5 -3
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/view-routes.ts +28 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +80 -1
- package/src/stores/use-approval-store.ts +78 -0
- package/src/stores/use-chat-store.ts +162 -6
- package/src/types/index.ts +154 -3
- package/tsconfig.json +13 -4
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ExecApprovalConfig, PermissionPreset } from '@/types'
|
|
2
|
+
import { ensureGatewayConnected } from './openclaw-gateway'
|
|
3
|
+
import { setExecConfig, getExecConfig } from './openclaw-exec-config'
|
|
4
|
+
|
|
5
|
+
export interface PresetConfig {
|
|
6
|
+
security: ExecApprovalConfig['security']
|
|
7
|
+
askMode: ExecApprovalConfig['askMode']
|
|
8
|
+
toolGroups: string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PRESET_CONFIGS: Record<PermissionPreset, PresetConfig> = {
|
|
12
|
+
conservative: {
|
|
13
|
+
security: 'deny',
|
|
14
|
+
askMode: 'off',
|
|
15
|
+
toolGroups: [],
|
|
16
|
+
},
|
|
17
|
+
collaborative: {
|
|
18
|
+
security: 'allowlist',
|
|
19
|
+
askMode: 'on-miss',
|
|
20
|
+
toolGroups: ['group:web', 'group:fs'],
|
|
21
|
+
},
|
|
22
|
+
autonomous: {
|
|
23
|
+
security: 'full',
|
|
24
|
+
askMode: 'off',
|
|
25
|
+
toolGroups: ['group:runtime', 'group:web', 'group:fs'],
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Derive which preset matches the current config, or 'custom' if none match */
|
|
30
|
+
export function resolvePresetFromConfig(config: ExecApprovalConfig): PermissionPreset | 'custom' {
|
|
31
|
+
for (const [preset, pc] of Object.entries(PRESET_CONFIGS) as [PermissionPreset, PresetConfig][]) {
|
|
32
|
+
if (config.security === pc.security && config.askMode === pc.askMode) {
|
|
33
|
+
return preset
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 'custom'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Apply a permission preset to an agent via gateway RPC */
|
|
40
|
+
export async function applyPreset(agentId: string, preset: PermissionPreset): Promise<void> {
|
|
41
|
+
const pc = PRESET_CONFIGS[preset]
|
|
42
|
+
if (!pc) throw new Error(`Unknown preset: ${preset}`)
|
|
43
|
+
|
|
44
|
+
const gw = await ensureGatewayConnected()
|
|
45
|
+
if (!gw) throw new Error('Gateway not connected')
|
|
46
|
+
|
|
47
|
+
// Update exec approval config
|
|
48
|
+
const snap = await getExecConfig(agentId)
|
|
49
|
+
await setExecConfig(agentId, {
|
|
50
|
+
security: pc.security,
|
|
51
|
+
askMode: pc.askMode,
|
|
52
|
+
patterns: pc.security === 'allowlist' ? snap.file.patterns : [],
|
|
53
|
+
}, snap.hash)
|
|
54
|
+
|
|
55
|
+
// Sync tool groups if gateway supports it
|
|
56
|
+
try {
|
|
57
|
+
await gw.rpc('config.set', {
|
|
58
|
+
key: `agents.${agentId}.toolGroups`,
|
|
59
|
+
value: pc.toolGroups,
|
|
60
|
+
})
|
|
61
|
+
} catch {
|
|
62
|
+
// Not all gateways support tool group config — ignore
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { DATA_DIR } from './data-dir'
|
|
5
|
+
import { loadSettings, loadAgents, saveAgents, loadSchedules, saveSchedules, loadCredentials, decryptKey, encryptKey } from './storage'
|
|
6
|
+
import { getMemoryDb } from './memory-db'
|
|
7
|
+
import type { AppSettings, MemoryEntry, Schedule } from '@/types'
|
|
8
|
+
|
|
9
|
+
export interface OpenClawSyncConfig {
|
|
10
|
+
workspacePath: string
|
|
11
|
+
autoSyncMemory: boolean
|
|
12
|
+
autoSyncSchedules: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Resolve the OpenClaw workspace directory. Checks settings override, then ~/.openclaw, then ~/.clawdbot */
|
|
16
|
+
export function resolveOpenClawWorkspace(): string {
|
|
17
|
+
const settings = loadSettings() as AppSettings
|
|
18
|
+
if (settings.openclawWorkspacePath) {
|
|
19
|
+
const resolved = settings.openclawWorkspacePath.replace(/^~/, process.env.HOME || '')
|
|
20
|
+
if (fs.existsSync(resolved)) return resolved
|
|
21
|
+
}
|
|
22
|
+
const home = process.env.HOME || process.env.USERPROFILE || ''
|
|
23
|
+
const override = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim()
|
|
24
|
+
if (override) {
|
|
25
|
+
const resolved = path.resolve(override.replace(/^~/, home))
|
|
26
|
+
if (fs.existsSync(resolved)) return resolved
|
|
27
|
+
}
|
|
28
|
+
const newDir = path.join(home, '.openclaw')
|
|
29
|
+
if (fs.existsSync(newDir)) return newDir
|
|
30
|
+
const legacyDir = path.join(home, '.clawdbot')
|
|
31
|
+
if (fs.existsSync(legacyDir)) return legacyDir
|
|
32
|
+
// Default to creating ~/.openclaw
|
|
33
|
+
return newDir
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function loadSyncConfig(): OpenClawSyncConfig {
|
|
37
|
+
const settings = loadSettings() as AppSettings
|
|
38
|
+
return {
|
|
39
|
+
workspacePath: resolveOpenClawWorkspace(),
|
|
40
|
+
autoSyncMemory: settings.openclawAutoSyncMemory ?? false,
|
|
41
|
+
autoSyncSchedules: settings.openclawAutoSyncSchedules ?? false,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureDir(dirPath: string): void {
|
|
46
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function contentHash(text: string): string {
|
|
50
|
+
return crypto.createHash('sha256').update(text).digest('hex').slice(0, 16)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Memory Sync (Feature 2) ---
|
|
54
|
+
|
|
55
|
+
export function pushMemoryToOpenClaw(agentId?: string): { written: number } {
|
|
56
|
+
const config = loadSyncConfig()
|
|
57
|
+
const memoryDir = path.join(config.workspacePath, 'memory')
|
|
58
|
+
ensureDir(memoryDir)
|
|
59
|
+
|
|
60
|
+
const db = getMemoryDb()
|
|
61
|
+
const entries = db.list(agentId, 500) as MemoryEntry[]
|
|
62
|
+
if (!entries.length) return { written: 0 }
|
|
63
|
+
|
|
64
|
+
// Group by date
|
|
65
|
+
const byDate = new Map<string, MemoryEntry[]>()
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const date = new Date(entry.createdAt).toISOString().slice(0, 10)
|
|
68
|
+
const group = byDate.get(date) || []
|
|
69
|
+
group.push(entry)
|
|
70
|
+
byDate.set(date, group)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let written = 0
|
|
74
|
+
for (const [date, group] of byDate) {
|
|
75
|
+
const lines: string[] = [`# Memory — ${date}`, '']
|
|
76
|
+
for (const entry of group) {
|
|
77
|
+
lines.push(`## ${entry.title}`)
|
|
78
|
+
lines.push(`- Category: ${entry.category}`)
|
|
79
|
+
if (entry.agentId) lines.push(`- Agent: ${entry.agentId}`)
|
|
80
|
+
lines.push('')
|
|
81
|
+
lines.push(entry.content)
|
|
82
|
+
lines.push('')
|
|
83
|
+
}
|
|
84
|
+
fs.writeFileSync(path.join(memoryDir, `${date}.md`), lines.join('\n'))
|
|
85
|
+
written++
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Write curated MEMORY.md
|
|
89
|
+
const curated = entries
|
|
90
|
+
.filter((e) => e.category !== 'execution' && e.category !== 'working' && e.category !== 'scratch')
|
|
91
|
+
.slice(0, 50)
|
|
92
|
+
if (curated.length > 0) {
|
|
93
|
+
const memoryMdLines: string[] = ['# Memory', '']
|
|
94
|
+
for (const entry of curated) {
|
|
95
|
+
memoryMdLines.push(`- **${entry.title}** (${entry.category}): ${entry.content.slice(0, 200)}`)
|
|
96
|
+
}
|
|
97
|
+
fs.writeFileSync(path.join(config.workspacePath, 'MEMORY.md'), memoryMdLines.join('\n'))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { written }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function pullMemoryFromOpenClaw(): { imported: number } {
|
|
104
|
+
const config = loadSyncConfig()
|
|
105
|
+
const memoryDir = path.join(config.workspacePath, 'memory')
|
|
106
|
+
const db = getMemoryDb()
|
|
107
|
+
let imported = 0
|
|
108
|
+
|
|
109
|
+
// Build set of existing content hashes for dedup
|
|
110
|
+
const existing = db.list(undefined, 500) as MemoryEntry[]
|
|
111
|
+
const existingHashes = new Set(existing.map((e) => contentHash(`${e.title}|${e.content}`)))
|
|
112
|
+
|
|
113
|
+
const files: string[] = []
|
|
114
|
+
if (fs.existsSync(memoryDir)) {
|
|
115
|
+
files.push(...fs.readdirSync(memoryDir).filter((f) => f.endsWith('.md')))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Also check MEMORY.md at workspace root
|
|
119
|
+
const memoryMdPath = path.join(config.workspacePath, 'MEMORY.md')
|
|
120
|
+
if (fs.existsSync(memoryMdPath)) {
|
|
121
|
+
const content = fs.readFileSync(memoryMdPath, 'utf8')
|
|
122
|
+
const lines = content.split('\n')
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const match = line.match(/^- \*\*(.+?)\*\* \((.+?)\): (.+)/)
|
|
125
|
+
if (match) {
|
|
126
|
+
const [, title, category, text] = match
|
|
127
|
+
const hash = contentHash(`${title}|${text}`)
|
|
128
|
+
if (!existingHashes.has(hash)) {
|
|
129
|
+
db.add({
|
|
130
|
+
agentId: null,
|
|
131
|
+
sessionId: null,
|
|
132
|
+
category: category || 'note',
|
|
133
|
+
title: title || 'Imported',
|
|
134
|
+
content: text || '',
|
|
135
|
+
metadata: { source: 'openclaw-sync' },
|
|
136
|
+
})
|
|
137
|
+
existingHashes.add(hash)
|
|
138
|
+
imported++
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const content = fs.readFileSync(path.join(memoryDir, file), 'utf8')
|
|
146
|
+
// Parse markdown sections
|
|
147
|
+
const sections = content.split(/^## /m).slice(1)
|
|
148
|
+
for (const section of sections) {
|
|
149
|
+
const lines = section.split('\n')
|
|
150
|
+
const title = (lines[0] || 'Untitled').trim()
|
|
151
|
+
const bodyLines = lines.slice(1).filter((l) => !l.startsWith('- Category:') && !l.startsWith('- Agent:'))
|
|
152
|
+
const body = bodyLines.join('\n').trim()
|
|
153
|
+
if (!body) continue
|
|
154
|
+
|
|
155
|
+
const hash = contentHash(`${title}|${body}`)
|
|
156
|
+
if (existingHashes.has(hash)) continue
|
|
157
|
+
|
|
158
|
+
const categoryMatch = section.match(/- Category: (.+)/)
|
|
159
|
+
const category = categoryMatch?.[1]?.trim() || 'note'
|
|
160
|
+
|
|
161
|
+
db.add({
|
|
162
|
+
agentId: null,
|
|
163
|
+
sessionId: null,
|
|
164
|
+
category,
|
|
165
|
+
title,
|
|
166
|
+
content: body,
|
|
167
|
+
metadata: { source: 'openclaw-sync' },
|
|
168
|
+
})
|
|
169
|
+
existingHashes.add(hash)
|
|
170
|
+
imported++
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { imported }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Workspace File Mapping (Feature 3) ---
|
|
178
|
+
|
|
179
|
+
export function pushAgentToOpenClaw(agentId: string): { written: string[] } {
|
|
180
|
+
const config = loadSyncConfig()
|
|
181
|
+
const agents = loadAgents()
|
|
182
|
+
const agent = agents[agentId]
|
|
183
|
+
if (!agent) throw new Error(`Agent not found: ${agentId}`)
|
|
184
|
+
|
|
185
|
+
const agentDir = path.join(config.workspacePath, 'agents', agent.name.toLowerCase().replace(/\s+/g, '-'))
|
|
186
|
+
ensureDir(agentDir)
|
|
187
|
+
|
|
188
|
+
const written: string[] = []
|
|
189
|
+
|
|
190
|
+
if (agent.soul) {
|
|
191
|
+
const soulPath = path.join(agentDir, 'SOUL.md')
|
|
192
|
+
fs.writeFileSync(soulPath, agent.soul)
|
|
193
|
+
written.push('SOUL.md')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const identityLines: string[] = [`# ${agent.name}`, '']
|
|
197
|
+
if (agent.description) identityLines.push(agent.description)
|
|
198
|
+
identityLines.push('')
|
|
199
|
+
identityLines.push(`- Provider: ${agent.provider}`)
|
|
200
|
+
identityLines.push(`- Model: ${agent.model}`)
|
|
201
|
+
if (agent.capabilities?.length) {
|
|
202
|
+
identityLines.push(`- Capabilities: ${agent.capabilities.join(', ')}`)
|
|
203
|
+
}
|
|
204
|
+
const identityPath = path.join(agentDir, 'IDENTITY.md')
|
|
205
|
+
fs.writeFileSync(identityPath, identityLines.join('\n'))
|
|
206
|
+
written.push('IDENTITY.md')
|
|
207
|
+
|
|
208
|
+
return { written }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function pullAgentFromOpenClaw(agentId: string): { updated: string[] } {
|
|
212
|
+
const config = loadSyncConfig()
|
|
213
|
+
const agents = loadAgents()
|
|
214
|
+
const agent = agents[agentId]
|
|
215
|
+
if (!agent) throw new Error(`Agent not found: ${agentId}`)
|
|
216
|
+
|
|
217
|
+
const agentDir = path.join(config.workspacePath, 'agents', agent.name.toLowerCase().replace(/\s+/g, '-'))
|
|
218
|
+
const updated: string[] = []
|
|
219
|
+
|
|
220
|
+
const soulPath = path.join(agentDir, 'SOUL.md')
|
|
221
|
+
if (fs.existsSync(soulPath)) {
|
|
222
|
+
agent.soul = fs.readFileSync(soulPath, 'utf8')
|
|
223
|
+
updated.push('soul')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const identityPath = path.join(agentDir, 'IDENTITY.md')
|
|
227
|
+
if (fs.existsSync(identityPath)) {
|
|
228
|
+
const content = fs.readFileSync(identityPath, 'utf8')
|
|
229
|
+
// Extract description: everything after the first heading and before the metadata lines
|
|
230
|
+
const lines = content.split('\n')
|
|
231
|
+
const descLines = lines.filter((l) => !l.startsWith('#') && !l.startsWith('- Provider:') && !l.startsWith('- Model:') && !l.startsWith('- Capabilities:'))
|
|
232
|
+
const desc = descLines.join('\n').trim()
|
|
233
|
+
if (desc) {
|
|
234
|
+
agent.description = desc
|
|
235
|
+
updated.push('description')
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (updated.length > 0) {
|
|
240
|
+
agent.updatedAt = Date.now()
|
|
241
|
+
agents[agentId] = agent
|
|
242
|
+
saveAgents(agents)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { updated }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// --- Schedule Sync (Feature 6) ---
|
|
249
|
+
|
|
250
|
+
export function pushSchedulesToOpenClaw(): { written: number } {
|
|
251
|
+
const config = loadSyncConfig()
|
|
252
|
+
const cronDir = path.join(config.workspacePath, 'cron')
|
|
253
|
+
ensureDir(cronDir)
|
|
254
|
+
|
|
255
|
+
const schedules = loadSchedules() as Record<string, Schedule>
|
|
256
|
+
const cronSchedules = Object.values(schedules).filter(
|
|
257
|
+
(s) => s.scheduleType === 'cron' && s.status === 'active',
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const jobs = cronSchedules.map((s) => ({
|
|
261
|
+
name: s.name,
|
|
262
|
+
cron: s.cron,
|
|
263
|
+
agentId: s.agentId,
|
|
264
|
+
taskPrompt: s.taskPrompt,
|
|
265
|
+
status: s.status,
|
|
266
|
+
}))
|
|
267
|
+
|
|
268
|
+
fs.writeFileSync(path.join(cronDir, 'jobs.json'), JSON.stringify(jobs, null, 2))
|
|
269
|
+
return { written: jobs.length }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function pullSchedulesFromOpenClaw(): { imported: number } {
|
|
273
|
+
const config = loadSyncConfig()
|
|
274
|
+
const jobsPath = path.join(config.workspacePath, 'cron', 'jobs.json')
|
|
275
|
+
if (!fs.existsSync(jobsPath)) return { imported: 0 }
|
|
276
|
+
|
|
277
|
+
const raw = JSON.parse(fs.readFileSync(jobsPath, 'utf8'))
|
|
278
|
+
if (!Array.isArray(raw)) return { imported: 0 }
|
|
279
|
+
|
|
280
|
+
const schedules = loadSchedules() as Record<string, Schedule>
|
|
281
|
+
const existingNames = new Set(Object.values(schedules).map((s) => `${s.name}|${s.cron}`))
|
|
282
|
+
let imported = 0
|
|
283
|
+
|
|
284
|
+
for (const job of raw) {
|
|
285
|
+
if (!job.name || !job.cron) continue
|
|
286
|
+
const key = `${job.name}|${job.cron}`
|
|
287
|
+
if (existingNames.has(key)) continue
|
|
288
|
+
|
|
289
|
+
const id = crypto.randomUUID()
|
|
290
|
+
schedules[id] = {
|
|
291
|
+
id,
|
|
292
|
+
name: job.name,
|
|
293
|
+
agentId: job.agentId || '',
|
|
294
|
+
taskPrompt: job.taskPrompt || '',
|
|
295
|
+
scheduleType: 'cron',
|
|
296
|
+
cron: job.cron,
|
|
297
|
+
status: 'active',
|
|
298
|
+
createdAt: Date.now(),
|
|
299
|
+
}
|
|
300
|
+
existingNames.add(key)
|
|
301
|
+
imported++
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (imported > 0) saveSchedules(schedules)
|
|
305
|
+
return { imported }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// --- Secret/Credential Sync (Feature 7) ---
|
|
309
|
+
|
|
310
|
+
export async function pullCredentialsFromOpenClaw(): Promise<{ imported: number }> {
|
|
311
|
+
const config = loadSyncConfig()
|
|
312
|
+
const modelsPath = path.join(config.workspacePath, 'agents', 'main', 'agent', 'models.json')
|
|
313
|
+
if (!fs.existsSync(modelsPath)) return { imported: 0 }
|
|
314
|
+
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
316
|
+
let raw: any
|
|
317
|
+
try {
|
|
318
|
+
raw = JSON.parse(fs.readFileSync(modelsPath, 'utf8'))
|
|
319
|
+
} catch {
|
|
320
|
+
return { imported: 0 }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const { loadCredentials: loadCreds, saveCredentials } = await import('./storage')
|
|
324
|
+
const creds = loadCreds()
|
|
325
|
+
const existingProviders = new Set(Object.values(creds).map((c: Record<string, unknown>) => c.provider))
|
|
326
|
+
let imported = 0
|
|
327
|
+
|
|
328
|
+
// Extract API keys from models.json entries
|
|
329
|
+
const entries = Array.isArray(raw) ? raw : raw?.models ? raw.models : []
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
if (!entry.apiKey || !entry.provider) continue
|
|
332
|
+
if (existingProviders.has(`openclaw-${entry.provider}`)) continue
|
|
333
|
+
|
|
334
|
+
const id = crypto.randomUUID()
|
|
335
|
+
creds[id] = {
|
|
336
|
+
id,
|
|
337
|
+
provider: `openclaw-${entry.provider}`,
|
|
338
|
+
name: `OpenClaw ${entry.provider}`,
|
|
339
|
+
encryptedKey: encryptKey(entry.apiKey),
|
|
340
|
+
createdAt: Date.now(),
|
|
341
|
+
}
|
|
342
|
+
existingProviders.add(`openclaw-${entry.provider}`)
|
|
343
|
+
imported++
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (imported > 0) saveCredentials(creds)
|
|
347
|
+
return { imported }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function pushCredentialsToOpenClaw(): { written: boolean } {
|
|
351
|
+
const config = loadSyncConfig()
|
|
352
|
+
const authProfilesPath = path.join(config.workspacePath, 'auth-profiles.json')
|
|
353
|
+
const creds = loadCredentials()
|
|
354
|
+
|
|
355
|
+
const profiles: Record<string, string> = {}
|
|
356
|
+
for (const cred of Object.values(creds) as Array<Record<string, string>>) {
|
|
357
|
+
if (!cred.encryptedKey || !cred.provider) continue
|
|
358
|
+
try {
|
|
359
|
+
profiles[cred.provider] = decryptKey(cred.encryptedKey)
|
|
360
|
+
} catch { /* skip undecryptable */ }
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (Object.keys(profiles).length === 0) return { written: false }
|
|
364
|
+
|
|
365
|
+
ensureDir(path.dirname(authProfilesPath))
|
|
366
|
+
fs.writeFileSync(authProfilesPath, JSON.stringify(profiles, null, 2), { mode: 0o600 })
|
|
367
|
+
try { fs.chmodSync(authProfilesPath, 0o600) } catch { /* best effort */ }
|
|
368
|
+
return { written: true }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// --- Plugin Sync (Feature 11) ---
|
|
372
|
+
|
|
373
|
+
export function syncPluginsFromOpenClaw(): { imported: number } {
|
|
374
|
+
const config = loadSyncConfig()
|
|
375
|
+
const openclawPluginDir = path.join(config.workspacePath, 'plugins')
|
|
376
|
+
if (!fs.existsSync(openclawPluginDir)) return { imported: 0 }
|
|
377
|
+
|
|
378
|
+
const localPluginDir = path.join(DATA_DIR, 'plugins')
|
|
379
|
+
ensureDir(localPluginDir)
|
|
380
|
+
|
|
381
|
+
const files = fs.readdirSync(openclawPluginDir).filter((f) => f.endsWith('.js'))
|
|
382
|
+
const existingHashes = new Set<string>()
|
|
383
|
+
// Hash existing local plugins
|
|
384
|
+
if (fs.existsSync(localPluginDir)) {
|
|
385
|
+
for (const f of fs.readdirSync(localPluginDir).filter((f) => f.endsWith('.js'))) {
|
|
386
|
+
const content = fs.readFileSync(path.join(localPluginDir, f), 'utf8')
|
|
387
|
+
existingHashes.add(contentHash(content))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let imported = 0
|
|
392
|
+
for (const file of files) {
|
|
393
|
+
const content = fs.readFileSync(path.join(openclawPluginDir, file), 'utf8')
|
|
394
|
+
const hash = contentHash(content)
|
|
395
|
+
if (existingHashes.has(hash)) continue
|
|
396
|
+
|
|
397
|
+
const destName = `openclaw-${file}`
|
|
398
|
+
fs.writeFileSync(path.join(localPluginDir, destName), content)
|
|
399
|
+
existingHashes.add(hash)
|
|
400
|
+
imported++
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return { imported }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// --- Device Token Cross-Sync (Feature 14) ---
|
|
407
|
+
|
|
408
|
+
const SHARED_TOKEN_PATH = path.join(DATA_DIR, 'openclaw', 'shared-device-token.json')
|
|
409
|
+
|
|
410
|
+
export function getSharedDeviceToken(): string | null {
|
|
411
|
+
try {
|
|
412
|
+
if (!fs.existsSync(SHARED_TOKEN_PATH)) return null
|
|
413
|
+
const raw = JSON.parse(fs.readFileSync(SHARED_TOKEN_PATH, 'utf8'))
|
|
414
|
+
return typeof raw?.token === 'string' && raw.token.trim() ? raw.token.trim() : null
|
|
415
|
+
} catch {
|
|
416
|
+
return null
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function setSharedDeviceToken(token: string): void {
|
|
421
|
+
const dir = path.dirname(SHARED_TOKEN_PATH)
|
|
422
|
+
ensureDir(dir)
|
|
423
|
+
fs.writeFileSync(SHARED_TOKEN_PATH, JSON.stringify({ token, updatedAt: Date.now() }, null, 2), { mode: 0o600 })
|
|
424
|
+
try { fs.chmodSync(SHARED_TOKEN_PATH, 0o600) } catch { /* best effort */ }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// --- Unified Sync Entry Point ---
|
|
428
|
+
|
|
429
|
+
export type SyncType = 'memory' | 'workspace' | 'schedules' | 'credentials' | 'plugins'
|
|
430
|
+
|
|
431
|
+
export interface SyncResult {
|
|
432
|
+
type: SyncType
|
|
433
|
+
action: 'push' | 'pull'
|
|
434
|
+
result: Record<string, unknown>
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export async function runSync(params: {
|
|
438
|
+
action: 'push' | 'pull' | 'both'
|
|
439
|
+
types: SyncType[]
|
|
440
|
+
}): Promise<SyncResult[]> {
|
|
441
|
+
const results: SyncResult[] = []
|
|
442
|
+
|
|
443
|
+
for (const type of params.types) {
|
|
444
|
+
if (params.action === 'push' || params.action === 'both') {
|
|
445
|
+
switch (type) {
|
|
446
|
+
case 'memory':
|
|
447
|
+
results.push({ type, action: 'push', result: pushMemoryToOpenClaw() })
|
|
448
|
+
break
|
|
449
|
+
case 'workspace': {
|
|
450
|
+
const agents = loadAgents()
|
|
451
|
+
for (const id of Object.keys(agents)) {
|
|
452
|
+
try {
|
|
453
|
+
results.push({ type, action: 'push', result: { agentId: id, ...pushAgentToOpenClaw(id) } })
|
|
454
|
+
} catch { /* skip */ }
|
|
455
|
+
}
|
|
456
|
+
break
|
|
457
|
+
}
|
|
458
|
+
case 'schedules':
|
|
459
|
+
results.push({ type, action: 'push', result: pushSchedulesToOpenClaw() })
|
|
460
|
+
break
|
|
461
|
+
case 'credentials':
|
|
462
|
+
results.push({ type, action: 'push', result: pushCredentialsToOpenClaw() })
|
|
463
|
+
break
|
|
464
|
+
case 'plugins':
|
|
465
|
+
// Plugins only pull from OpenClaw
|
|
466
|
+
break
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (params.action === 'pull' || params.action === 'both') {
|
|
470
|
+
switch (type) {
|
|
471
|
+
case 'memory':
|
|
472
|
+
results.push({ type, action: 'pull', result: pullMemoryFromOpenClaw() })
|
|
473
|
+
break
|
|
474
|
+
case 'workspace': {
|
|
475
|
+
const agents = loadAgents()
|
|
476
|
+
for (const id of Object.keys(agents)) {
|
|
477
|
+
try {
|
|
478
|
+
results.push({ type, action: 'pull', result: { agentId: id, ...pullAgentFromOpenClaw(id) } })
|
|
479
|
+
} catch { /* skip */ }
|
|
480
|
+
}
|
|
481
|
+
break
|
|
482
|
+
}
|
|
483
|
+
case 'schedules':
|
|
484
|
+
results.push({ type, action: 'pull', result: pullSchedulesFromOpenClaw() })
|
|
485
|
+
break
|
|
486
|
+
case 'credentials':
|
|
487
|
+
results.push({ type, action: 'pull', result: await pullCredentialsFromOpenClaw() })
|
|
488
|
+
break
|
|
489
|
+
case 'plugins':
|
|
490
|
+
results.push({ type, action: 'pull', result: syncPluginsFromOpenClaw() })
|
|
491
|
+
break
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return results
|
|
497
|
+
}
|
|
@@ -11,11 +11,10 @@ import { buildChatModel } from './build-llm'
|
|
|
11
11
|
import { getCheckpointSaver } from './langgraph-checkpoint'
|
|
12
12
|
import { notify } from './ws-hub'
|
|
13
13
|
import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
14
|
-
import
|
|
14
|
+
import { genId } from '@/lib/id'
|
|
15
|
+
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
15
16
|
import type { Agent, TaskComment, MessageToolEvent } from '@/types'
|
|
16
17
|
|
|
17
|
-
const NON_LANGGRAPH_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli'])
|
|
18
|
-
|
|
19
18
|
function resolveCredential(credentialId: string | null | undefined): string | null {
|
|
20
19
|
if (!credentialId) return null
|
|
21
20
|
const creds = loadCredentials()
|
|
@@ -92,12 +91,11 @@ function saveMessage(sessionId: string, role: 'user' | 'assistant', text: string
|
|
|
92
91
|
async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId: string): Promise<string> {
|
|
93
92
|
// Dynamic import to avoid circular deps
|
|
94
93
|
const { callProvider } = await import('./orchestrator')
|
|
95
|
-
const crypto = await import('crypto')
|
|
96
94
|
const { loadSessions: ls, saveSessions: ss } = await import('./storage')
|
|
97
95
|
|
|
98
96
|
const sessions = ls()
|
|
99
97
|
const parentSession = sessions[parentSessionId]
|
|
100
|
-
const childId =
|
|
98
|
+
const childId = genId()
|
|
101
99
|
sessions[childId] = {
|
|
102
100
|
id: childId,
|
|
103
101
|
name: `[Agent] ${agent.name}: ${task.slice(0, 40)}`,
|
|
@@ -273,7 +271,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
273
271
|
if (!t) return `Task "${taskId}" not found.`
|
|
274
272
|
if (!t.comments) t.comments = []
|
|
275
273
|
const c: TaskComment = {
|
|
276
|
-
id:
|
|
274
|
+
id: genId(),
|
|
277
275
|
author: orchestrator.name,
|
|
278
276
|
agentId: orchestrator.id,
|
|
279
277
|
text: comment,
|
|
@@ -298,7 +296,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
298
296
|
const createTaskTool = tool(
|
|
299
297
|
async ({ title, description: desc }) => {
|
|
300
298
|
const tasks = loadTasks()
|
|
301
|
-
const id =
|
|
299
|
+
const id = genId()
|
|
302
300
|
tasks[id] = {
|
|
303
301
|
id,
|
|
304
302
|
title,
|
|
@@ -532,6 +530,29 @@ export async function executeLangGraphOrchestrator(
|
|
|
532
530
|
const toolCalls = lastMsg?.tool_calls || lastMsg?.additional_kwargs?.tool_calls || []
|
|
533
531
|
const pendingCall = toolCalls[0]
|
|
534
532
|
if (pendingCall) {
|
|
533
|
+
// Try OpenClaw approval bridge first when agent uses openclaw provider
|
|
534
|
+
try {
|
|
535
|
+
if (orchestrator.provider === 'openclaw') {
|
|
536
|
+
const { forwardApprovalToOpenClaw } = await import('./openclaw-approvals')
|
|
537
|
+
const toolName = pendingCall.name || pendingCall.function?.name || 'unknown'
|
|
538
|
+
const toolArgs = pendingCall.args || (pendingCall.function?.arguments ? JSON.parse(pendingCall.function.arguments) : {})
|
|
539
|
+
const decision = await forwardApprovalToOpenClaw({ toolName, args: toolArgs })
|
|
540
|
+
if (decision) {
|
|
541
|
+
if (decision.approved) {
|
|
542
|
+
// OpenClaw approved — resume the graph instead of pausing
|
|
543
|
+
console.log(`[orchestrator-lg] OpenClaw approved tool "${toolName}" — resuming graph`)
|
|
544
|
+
// Don't set pendingApproval, let the loop continue
|
|
545
|
+
} else {
|
|
546
|
+
console.log(`[orchestrator-lg] OpenClaw rejected tool "${toolName}": ${decision.reason || 'no reason'}`)
|
|
547
|
+
// Fall through to SwarmClaw's pendingApproval UI
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// If decision is null (socket unavailable), fall through to SwarmClaw UI
|
|
551
|
+
}
|
|
552
|
+
} catch {
|
|
553
|
+
// OpenClaw approval bridge not available — fall through
|
|
554
|
+
}
|
|
555
|
+
|
|
535
556
|
const tasks = loadTasks()
|
|
536
557
|
const t = tasks[taskId]
|
|
537
558
|
if (t) {
|
|
@@ -668,7 +689,7 @@ export async function resumeLangGraphOrchestrator(
|
|
|
668
689
|
if (!t) return `Task "${taskId}" not found.`
|
|
669
690
|
if (!t.comments) t.comments = []
|
|
670
691
|
t.comments.push({
|
|
671
|
-
id:
|
|
692
|
+
id: genId(),
|
|
672
693
|
author: orchestrator.name,
|
|
673
694
|
agentId: orchestrator.id,
|
|
674
695
|
text: comment,
|
|
@@ -688,7 +709,7 @@ export async function resumeLangGraphOrchestrator(
|
|
|
688
709
|
const createTaskTool = tool(
|
|
689
710
|
async ({ title, description: desc }) => {
|
|
690
711
|
const tasks = loadTasks()
|
|
691
|
-
const id =
|
|
712
|
+
const id = genId()
|
|
692
713
|
tasks[id] = {
|
|
693
714
|
id, title, description: desc, status: 'backlog',
|
|
694
715
|
agentId: orchestrator.id, sessionId: null, result: null, error: null,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
2
|
import {
|
|
3
3
|
loadSessions, saveSessions, loadAgents,
|
|
4
4
|
loadCredentials, decryptKey, loadSettings, loadSkills,
|
|
@@ -20,7 +20,7 @@ export function createOrchestratorSession(
|
|
|
20
20
|
cwd?: string,
|
|
21
21
|
): string {
|
|
22
22
|
const sessions = loadSessions()
|
|
23
|
-
const sessionId =
|
|
23
|
+
const sessionId = genId()
|
|
24
24
|
sessions[sessionId] = {
|
|
25
25
|
id: sessionId,
|
|
26
26
|
name: `[Orch] ${orchestrator.name}: ${task.slice(0, 40)}`,
|
|
@@ -264,7 +264,7 @@ async function executeSubTask(
|
|
|
264
264
|
// Look up parent session cwd to inherit
|
|
265
265
|
const sessions = loadSessions()
|
|
266
266
|
const parentSession = sessions[parentSessionId]
|
|
267
|
-
const childId =
|
|
267
|
+
const childId = genId()
|
|
268
268
|
const childSession = {
|
|
269
269
|
id: childId,
|
|
270
270
|
name: `[Agent] ${agent.name}: ${task.slice(0, 40)}`,
|
|
@@ -325,7 +325,7 @@ export async function callProvider(
|
|
|
325
325
|
|
|
326
326
|
// Build a mock session for the provider
|
|
327
327
|
const mockSession = {
|
|
328
|
-
id: 'orch-' +
|
|
328
|
+
id: 'orch-' + genId(2),
|
|
329
329
|
provider: agent.provider,
|
|
330
330
|
model: agent.model,
|
|
331
331
|
credentialId: agent.credentialId,
|