@simonyea/holysheep-cli 2.1.40 → 2.1.42
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/dist/configure-worker.js +4510 -0
- package/dist/index.js +9610 -0
- package/dist/process-proxy-inject.js +117 -0
- package/package.json +19 -6
- package/.gitea/workflows/sanity.yml +0 -125
- package/scripts/check-tarball-size.js +0 -44
- package/src/commands/balance.js +0 -57
- package/src/commands/claude-proxy.js +0 -248
- package/src/commands/claude.js +0 -135
- package/src/commands/doctor.js +0 -282
- package/src/commands/login.js +0 -211
- package/src/commands/openclaw.js +0 -258
- package/src/commands/reset.js +0 -53
- package/src/commands/setup.js +0 -493
- package/src/commands/upgrade.js +0 -168
- package/src/commands/webui.js +0 -622
- package/src/index.js +0 -226
- package/src/tools/aider.js +0 -78
- package/src/tools/antigravity.js +0 -42
- package/src/tools/claude-code.js +0 -228
- package/src/tools/claude-process-proxy.js +0 -1030
- package/src/tools/codex.js +0 -254
- package/src/tools/continue.js +0 -146
- package/src/tools/cursor.js +0 -71
- package/src/tools/droid.js +0 -281
- package/src/tools/env-config.js +0 -185
- package/src/tools/gemini-cli.js +0 -82
- package/src/tools/hermes.js +0 -354
- package/src/tools/index.js +0 -13
- package/src/tools/openclaw-bridge.js +0 -987
- package/src/tools/openclaw.js +0 -925
- package/src/tools/opencode.js +0 -227
- package/src/tools/process-proxy-inject.js +0 -142
- package/src/utils/config.js +0 -54
- package/src/utils/shell.js +0 -342
- package/src/utils/which.js +0 -176
- package/src/webui/aionui-runtime-fetcher.js +0 -429
- package/src/webui/aionui-runtime.js +0 -139
- package/src/webui/aionui-wrapper.js +0 -734
- package/src/webui/configure-worker.js +0 -67
- package/src/webui/server.js +0 -1572
- package/src/webui/workspace-runtime.js +0 -288
- package/src/webui/workspace-store.js +0 -325
- /package/{src/webui → dist}/index.html +0 -0
- /package/{src/tools → dist}/pty-hermes-wrapper.py +0 -0
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fetch = require('node-fetch')
|
|
4
|
-
const {
|
|
5
|
-
addMessage,
|
|
6
|
-
createConversation,
|
|
7
|
-
ensureConversation,
|
|
8
|
-
getConversation,
|
|
9
|
-
getHolySheepApiConfig,
|
|
10
|
-
listTasks,
|
|
11
|
-
updateConversation,
|
|
12
|
-
updateTaskRun,
|
|
13
|
-
} = require('./workspace-store')
|
|
14
|
-
const { getApiKey, BASE_URL_OPENAI } = require('../utils/config')
|
|
15
|
-
|
|
16
|
-
const schedulerHandles = new Map()
|
|
17
|
-
|
|
18
|
-
function normalizeRuntimeConfig(config = {}) {
|
|
19
|
-
const saved = getHolySheepApiConfig()
|
|
20
|
-
const apiKey = String(config.apiKey || saved.apiKey || getApiKey() || '').trim()
|
|
21
|
-
const baseUrl = String(config.baseUrl || saved.baseUrl || BASE_URL_OPENAI).replace(/\/+$/, '')
|
|
22
|
-
const model = String(config.model || saved.model || '').trim()
|
|
23
|
-
return { apiKey, baseUrl, model }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function assertRuntimeConfig(config) {
|
|
27
|
-
if (!config.apiKey || !config.apiKey.startsWith('cr_')) {
|
|
28
|
-
throw new Error('HolySheep API Key is required')
|
|
29
|
-
}
|
|
30
|
-
if (!config.baseUrl) {
|
|
31
|
-
throw new Error('HolySheep API Base URL is required')
|
|
32
|
-
}
|
|
33
|
-
if (!config.model) {
|
|
34
|
-
throw new Error('HolySheep API model is required')
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function buildSystemPrompt(toolId) {
|
|
39
|
-
const toolName = String(toolId || 'codex')
|
|
40
|
-
return [
|
|
41
|
-
`You are operating inside HolySheep Workspace.`,
|
|
42
|
-
`The selected coding tool is "${toolName}".`,
|
|
43
|
-
`Respond with concise, implementation-focused guidance suitable for a coding assistant workspace.`,
|
|
44
|
-
`When you mention commands, prefer copy-paste-ready shell commands.`,
|
|
45
|
-
].join(' ')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function requestCompletion(messages, config) {
|
|
49
|
-
const controller = new AbortController()
|
|
50
|
-
const timeout = setTimeout(() => controller.abort(), 20_000)
|
|
51
|
-
let response
|
|
52
|
-
try {
|
|
53
|
-
response = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: {
|
|
56
|
-
Authorization: `Bearer ${config.apiKey}`,
|
|
57
|
-
'Content-Type': 'application/json',
|
|
58
|
-
},
|
|
59
|
-
body: JSON.stringify({
|
|
60
|
-
model: config.model,
|
|
61
|
-
temperature: 0.2,
|
|
62
|
-
messages,
|
|
63
|
-
}),
|
|
64
|
-
signal: controller.signal,
|
|
65
|
-
})
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (error.name === 'AbortError') {
|
|
68
|
-
throw new Error('HolySheep API request timed out')
|
|
69
|
-
}
|
|
70
|
-
throw error
|
|
71
|
-
} finally {
|
|
72
|
-
clearTimeout(timeout)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const payload = await response.json().catch(() => null)
|
|
76
|
-
if (!response.ok) {
|
|
77
|
-
const reason = payload?.error?.message || payload?.message || `HTTP ${response.status}`
|
|
78
|
-
throw new Error(reason)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const content = payload?.choices?.[0]?.message?.content
|
|
82
|
-
if (typeof content === 'string' && content.trim()) return content.trim()
|
|
83
|
-
if (Array.isArray(content)) {
|
|
84
|
-
const text = content
|
|
85
|
-
.map((part) => {
|
|
86
|
-
if (typeof part === 'string') return part
|
|
87
|
-
if (typeof part?.text === 'string') return part.text
|
|
88
|
-
if (typeof part?.content === 'string') return part.content
|
|
89
|
-
return ''
|
|
90
|
-
})
|
|
91
|
-
.filter(Boolean)
|
|
92
|
-
.join('\n')
|
|
93
|
-
.trim()
|
|
94
|
-
if (text) return text
|
|
95
|
-
}
|
|
96
|
-
const fallbacks = [
|
|
97
|
-
payload?.choices?.[0]?.text,
|
|
98
|
-
payload?.output_text,
|
|
99
|
-
payload?.response,
|
|
100
|
-
payload?.message,
|
|
101
|
-
]
|
|
102
|
-
for (const item of fallbacks) {
|
|
103
|
-
if (typeof item === 'string' && item.trim()) return item.trim()
|
|
104
|
-
}
|
|
105
|
-
if (Array.isArray(payload?.output)) {
|
|
106
|
-
const text = payload.output
|
|
107
|
-
.map((part) => {
|
|
108
|
-
if (typeof part?.content === 'string') return part.content
|
|
109
|
-
if (typeof part?.text === 'string') return part.text
|
|
110
|
-
if (Array.isArray(part?.content)) {
|
|
111
|
-
return part.content.map((child) => child?.text || child?.content || '').join('\n')
|
|
112
|
-
}
|
|
113
|
-
return ''
|
|
114
|
-
})
|
|
115
|
-
.filter(Boolean)
|
|
116
|
-
.join('\n')
|
|
117
|
-
.trim()
|
|
118
|
-
if (text) return text
|
|
119
|
-
}
|
|
120
|
-
return 'No response content returned.'
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function sendConversationMessage(conversationId, input, options = {}) {
|
|
124
|
-
const conversation = ensureConversation(conversationId)
|
|
125
|
-
const config = normalizeRuntimeConfig(options)
|
|
126
|
-
assertRuntimeConfig(config)
|
|
127
|
-
|
|
128
|
-
const userMessage = addMessage(conversationId, {
|
|
129
|
-
role: 'user',
|
|
130
|
-
content: String(input || ''),
|
|
131
|
-
meta: {
|
|
132
|
-
source: 'workspace',
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const messages = [
|
|
138
|
-
{ role: 'system', content: buildSystemPrompt(conversation.toolId) },
|
|
139
|
-
...conversation.messages
|
|
140
|
-
.concat(userMessage)
|
|
141
|
-
.map((message) => ({
|
|
142
|
-
role: message.role,
|
|
143
|
-
content: message.content,
|
|
144
|
-
})),
|
|
145
|
-
]
|
|
146
|
-
const reply = await requestCompletion(messages, config)
|
|
147
|
-
const assistantMessage = addMessage(conversationId, {
|
|
148
|
-
role: 'assistant',
|
|
149
|
-
content: reply,
|
|
150
|
-
meta: {
|
|
151
|
-
model: config.model,
|
|
152
|
-
source: 'holysheep-api',
|
|
153
|
-
},
|
|
154
|
-
})
|
|
155
|
-
updateConversation(conversationId, {
|
|
156
|
-
summary: reply.slice(0, 240),
|
|
157
|
-
})
|
|
158
|
-
return { userMessage, assistantMessage }
|
|
159
|
-
} catch (error) {
|
|
160
|
-
const assistantMessage = addMessage(conversationId, {
|
|
161
|
-
role: 'assistant',
|
|
162
|
-
content: `Error: ${error.message}`,
|
|
163
|
-
status: 'error',
|
|
164
|
-
meta: {
|
|
165
|
-
model: config.model,
|
|
166
|
-
source: 'holysheep-api',
|
|
167
|
-
},
|
|
168
|
-
})
|
|
169
|
-
updateConversation(conversationId, {
|
|
170
|
-
summary: assistantMessage.content.slice(0, 240),
|
|
171
|
-
})
|
|
172
|
-
throw error
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function parseScheduleToMs(schedule) {
|
|
177
|
-
const value = String(schedule || '').trim().toLowerCase()
|
|
178
|
-
const match = value.match(/^(\d+)\s*([smhd])$/)
|
|
179
|
-
if (!match) throw new Error('Schedule must use format like 30s, 5m, 1h, 1d')
|
|
180
|
-
const amount = Number(match[1])
|
|
181
|
-
const unit = match[2]
|
|
182
|
-
if (!Number.isFinite(amount) || amount <= 0) throw new Error('Invalid schedule amount')
|
|
183
|
-
const factors = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }
|
|
184
|
-
return amount * factors[unit]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function runTask(taskId, options = {}) {
|
|
188
|
-
const task = listTasks().find((item) => item.id === taskId)
|
|
189
|
-
if (!task) throw new Error('Task not found')
|
|
190
|
-
|
|
191
|
-
const config = normalizeRuntimeConfig({
|
|
192
|
-
...options,
|
|
193
|
-
model: task.modelOverride || options.model,
|
|
194
|
-
})
|
|
195
|
-
assertRuntimeConfig(config)
|
|
196
|
-
updateTaskRun(taskId, {
|
|
197
|
-
lastStatus: 'running',
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
let conversationId = task.conversationId
|
|
201
|
-
if (!conversationId) {
|
|
202
|
-
const conversation = createConversation({
|
|
203
|
-
title: task.title,
|
|
204
|
-
toolId: 'codex',
|
|
205
|
-
})
|
|
206
|
-
conversationId = conversation.id
|
|
207
|
-
updateTaskRun(taskId, { conversationId })
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const { assistantMessage } = await sendConversationMessage(conversationId, task.prompt, config)
|
|
212
|
-
updateTaskRun(taskId, {
|
|
213
|
-
lastRunAt: new Date().toISOString(),
|
|
214
|
-
lastStatus: 'ok',
|
|
215
|
-
lastResult: assistantMessage.content,
|
|
216
|
-
conversationId,
|
|
217
|
-
})
|
|
218
|
-
return assistantMessage
|
|
219
|
-
} catch (error) {
|
|
220
|
-
updateTaskRun(taskId, {
|
|
221
|
-
lastRunAt: new Date().toISOString(),
|
|
222
|
-
lastStatus: 'error',
|
|
223
|
-
lastResult: error.message,
|
|
224
|
-
conversationId,
|
|
225
|
-
})
|
|
226
|
-
throw error
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function clearTaskHandle(taskId) {
|
|
231
|
-
const existing = schedulerHandles.get(taskId)
|
|
232
|
-
if (!existing) return
|
|
233
|
-
clearInterval(existing)
|
|
234
|
-
schedulerHandles.delete(taskId)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function scheduleTask(task) {
|
|
238
|
-
clearTaskHandle(task.id)
|
|
239
|
-
if (!task.active) return
|
|
240
|
-
|
|
241
|
-
let intervalMs
|
|
242
|
-
try {
|
|
243
|
-
intervalMs = parseScheduleToMs(task.schedule)
|
|
244
|
-
} catch {
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const handle = setInterval(() => {
|
|
249
|
-
void runTask(task.id).catch(() => {})
|
|
250
|
-
}, intervalMs)
|
|
251
|
-
schedulerHandles.set(task.id, handle)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function rescheduleAllTasks() {
|
|
255
|
-
const tasks = listTasks()
|
|
256
|
-
const seen = new Set(tasks.map((task) => task.id))
|
|
257
|
-
for (const taskId of schedulerHandles.keys()) {
|
|
258
|
-
if (!seen.has(taskId)) {
|
|
259
|
-
clearTaskHandle(taskId)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
for (const task of tasks) {
|
|
263
|
-
scheduleTask(task)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function stopScheduler() {
|
|
268
|
-
for (const taskId of schedulerHandles.keys()) {
|
|
269
|
-
clearTaskHandle(taskId)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function startScheduler() {
|
|
274
|
-
rescheduleAllTasks()
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
module.exports = {
|
|
278
|
-
normalizeRuntimeConfig,
|
|
279
|
-
assertRuntimeConfig,
|
|
280
|
-
sendConversationMessage,
|
|
281
|
-
parseScheduleToMs,
|
|
282
|
-
runTask,
|
|
283
|
-
startScheduler,
|
|
284
|
-
stopScheduler,
|
|
285
|
-
rescheduleAllTasks,
|
|
286
|
-
createConversation,
|
|
287
|
-
getConversation,
|
|
288
|
-
}
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const path = require('path')
|
|
5
|
-
const crypto = require('crypto')
|
|
6
|
-
const { CONFIG_DIR } = require('../utils/config')
|
|
7
|
-
|
|
8
|
-
const STATE_FILE = path.join(CONFIG_DIR, 'workspace-state.json')
|
|
9
|
-
const STATE_VERSION = 1
|
|
10
|
-
|
|
11
|
-
function now() {
|
|
12
|
-
return new Date().toISOString()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function createDefaultState() {
|
|
16
|
-
return {
|
|
17
|
-
version: STATE_VERSION,
|
|
18
|
-
updatedAt: now(),
|
|
19
|
-
holySheepApi: {
|
|
20
|
-
apiKey: '',
|
|
21
|
-
baseUrl: '',
|
|
22
|
-
model: '',
|
|
23
|
-
},
|
|
24
|
-
conversations: [],
|
|
25
|
-
scheduledTasks: [],
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function ensureDir() {
|
|
30
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function clone(value) {
|
|
34
|
-
return JSON.parse(JSON.stringify(value))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizeConversation(item = {}) {
|
|
38
|
-
const createdAt = item.createdAt || now()
|
|
39
|
-
const messages = Array.isArray(item.messages)
|
|
40
|
-
? item.messages.map((message) => ({
|
|
41
|
-
id: message.id || crypto.randomUUID(),
|
|
42
|
-
role: message.role === 'assistant' ? 'assistant' : 'user',
|
|
43
|
-
content: String(message.content || ''),
|
|
44
|
-
status: message.status || 'done',
|
|
45
|
-
createdAt: message.createdAt || createdAt,
|
|
46
|
-
meta: typeof message.meta === 'object' && message.meta ? message.meta : {},
|
|
47
|
-
}))
|
|
48
|
-
: []
|
|
49
|
-
|
|
50
|
-
const lastMessage = messages[messages.length - 1] || null
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
id: item.id || crypto.randomUUID().slice(0, 8),
|
|
54
|
-
title: String(item.title || 'New Conversation'),
|
|
55
|
-
toolId: String(item.toolId || 'codex'),
|
|
56
|
-
createdAt,
|
|
57
|
-
updatedAt: item.updatedAt || lastMessage?.createdAt || createdAt,
|
|
58
|
-
pinned: Boolean(item.pinned),
|
|
59
|
-
summary: String(item.summary || lastMessage?.content || '').slice(0, 240),
|
|
60
|
-
messages,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function normalizeTask(item = {}) {
|
|
65
|
-
return {
|
|
66
|
-
id: item.id || crypto.randomUUID().slice(0, 8),
|
|
67
|
-
title: String(item.title || 'Untitled Task'),
|
|
68
|
-
prompt: String(item.prompt || ''),
|
|
69
|
-
schedule: String(item.schedule || '1h'),
|
|
70
|
-
active: item.active !== false,
|
|
71
|
-
createdAt: item.createdAt || now(),
|
|
72
|
-
updatedAt: item.updatedAt || now(),
|
|
73
|
-
lastRunAt: item.lastRunAt || null,
|
|
74
|
-
lastStatus: item.lastStatus || 'idle',
|
|
75
|
-
lastResult: String(item.lastResult || ''),
|
|
76
|
-
conversationId: item.conversationId || null,
|
|
77
|
-
modelOverride: item.modelOverride ? String(item.modelOverride) : '',
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function normalizeState(raw = {}) {
|
|
82
|
-
const state = createDefaultState()
|
|
83
|
-
state.holySheepApi = {
|
|
84
|
-
...state.holySheepApi,
|
|
85
|
-
...(typeof raw.holySheepApi === 'object' && raw.holySheepApi ? raw.holySheepApi : {}),
|
|
86
|
-
}
|
|
87
|
-
state.conversations = Array.isArray(raw.conversations) ? raw.conversations.map(normalizeConversation) : []
|
|
88
|
-
state.scheduledTasks = Array.isArray(raw.scheduledTasks) ? raw.scheduledTasks.map(normalizeTask) : []
|
|
89
|
-
state.updatedAt = raw.updatedAt || now()
|
|
90
|
-
return state
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function loadState() {
|
|
94
|
-
ensureDir()
|
|
95
|
-
try {
|
|
96
|
-
if (!fs.existsSync(STATE_FILE)) {
|
|
97
|
-
return createDefaultState()
|
|
98
|
-
}
|
|
99
|
-
return normalizeState(JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')))
|
|
100
|
-
} catch {
|
|
101
|
-
return createDefaultState()
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function saveState(next) {
|
|
106
|
-
ensureDir()
|
|
107
|
-
const normalized = normalizeState(next)
|
|
108
|
-
normalized.updatedAt = now()
|
|
109
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(normalized, null, 2), 'utf8')
|
|
110
|
-
return normalized
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function updateState(mutator) {
|
|
114
|
-
const current = loadState()
|
|
115
|
-
const draft = clone(current)
|
|
116
|
-
const result = mutator(draft) || draft
|
|
117
|
-
return saveState(result)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function sortConversations(items) {
|
|
121
|
-
return [...items].sort((a, b) => {
|
|
122
|
-
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1
|
|
123
|
-
return Date.parse(b.updatedAt) - Date.parse(a.updatedAt)
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function listConversations() {
|
|
128
|
-
return sortConversations(loadState().conversations).map((item) => ({
|
|
129
|
-
id: item.id,
|
|
130
|
-
title: item.title,
|
|
131
|
-
toolId: item.toolId,
|
|
132
|
-
pinned: item.pinned,
|
|
133
|
-
summary: item.summary,
|
|
134
|
-
createdAt: item.createdAt,
|
|
135
|
-
updatedAt: item.updatedAt,
|
|
136
|
-
messageCount: item.messages.length,
|
|
137
|
-
}))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function getConversation(id) {
|
|
141
|
-
return loadState().conversations.find((item) => item.id === id) || null
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function ensureConversation(id) {
|
|
145
|
-
const conversation = getConversation(id)
|
|
146
|
-
if (!conversation) throw new Error('Conversation not found')
|
|
147
|
-
return conversation
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function createConversation(payload = {}) {
|
|
151
|
-
const conversation = normalizeConversation({
|
|
152
|
-
title: payload.title || 'New Conversation',
|
|
153
|
-
toolId: payload.toolId || 'codex',
|
|
154
|
-
pinned: Boolean(payload.pinned),
|
|
155
|
-
})
|
|
156
|
-
updateState((state) => {
|
|
157
|
-
state.conversations.unshift(conversation)
|
|
158
|
-
})
|
|
159
|
-
return conversation
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function updateConversation(id, patch = {}) {
|
|
163
|
-
let updated = null
|
|
164
|
-
updateState((state) => {
|
|
165
|
-
const index = state.conversations.findIndex((item) => item.id === id)
|
|
166
|
-
if (index === -1) throw new Error('Conversation not found')
|
|
167
|
-
const current = state.conversations[index]
|
|
168
|
-
updated = normalizeConversation({
|
|
169
|
-
...current,
|
|
170
|
-
...patch,
|
|
171
|
-
messages: patch.messages || current.messages,
|
|
172
|
-
updatedAt: now(),
|
|
173
|
-
})
|
|
174
|
-
state.conversations[index] = updated
|
|
175
|
-
})
|
|
176
|
-
return updated
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function addMessage(conversationId, payload = {}) {
|
|
180
|
-
let updated = null
|
|
181
|
-
updateState((state) => {
|
|
182
|
-
const conversation = state.conversations.find((item) => item.id === conversationId)
|
|
183
|
-
if (!conversation) throw new Error('Conversation not found')
|
|
184
|
-
const message = {
|
|
185
|
-
id: payload.id || crypto.randomUUID(),
|
|
186
|
-
role: payload.role === 'assistant' ? 'assistant' : 'user',
|
|
187
|
-
content: String(payload.content || ''),
|
|
188
|
-
status: payload.status || 'done',
|
|
189
|
-
createdAt: payload.createdAt || now(),
|
|
190
|
-
meta: typeof payload.meta === 'object' && payload.meta ? payload.meta : {},
|
|
191
|
-
}
|
|
192
|
-
conversation.messages.push(message)
|
|
193
|
-
conversation.updatedAt = message.createdAt
|
|
194
|
-
if (!conversation.summary || message.role === 'assistant') {
|
|
195
|
-
conversation.summary = message.content.slice(0, 240)
|
|
196
|
-
}
|
|
197
|
-
updated = clone(message)
|
|
198
|
-
})
|
|
199
|
-
return updated
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function searchWorkspace(query) {
|
|
203
|
-
const needle = String(query || '').trim().toLowerCase()
|
|
204
|
-
if (!needle) return { conversations: [], tasks: [] }
|
|
205
|
-
|
|
206
|
-
const state = loadState()
|
|
207
|
-
const conversations = state.conversations
|
|
208
|
-
.filter((item) => {
|
|
209
|
-
if (item.title.toLowerCase().includes(needle)) return true
|
|
210
|
-
if (String(item.summary || '').toLowerCase().includes(needle)) return true
|
|
211
|
-
return item.messages.some((message) => String(message.content || '').toLowerCase().includes(needle))
|
|
212
|
-
})
|
|
213
|
-
.map((item) => ({
|
|
214
|
-
id: item.id,
|
|
215
|
-
title: item.title,
|
|
216
|
-
summary: item.summary,
|
|
217
|
-
updatedAt: item.updatedAt,
|
|
218
|
-
toolId: item.toolId,
|
|
219
|
-
}))
|
|
220
|
-
|
|
221
|
-
const tasks = state.scheduledTasks
|
|
222
|
-
.filter((item) => {
|
|
223
|
-
return item.title.toLowerCase().includes(needle) || item.prompt.toLowerCase().includes(needle)
|
|
224
|
-
})
|
|
225
|
-
.map((item) => ({
|
|
226
|
-
id: item.id,
|
|
227
|
-
title: item.title,
|
|
228
|
-
prompt: item.prompt.slice(0, 180),
|
|
229
|
-
schedule: item.schedule,
|
|
230
|
-
active: item.active,
|
|
231
|
-
updatedAt: item.updatedAt,
|
|
232
|
-
}))
|
|
233
|
-
|
|
234
|
-
return { conversations, tasks }
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function listTasks() {
|
|
238
|
-
return [...loadState().scheduledTasks].sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function getTask(id) {
|
|
242
|
-
return loadState().scheduledTasks.find((item) => item.id === id) || null
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function saveTask(payload = {}) {
|
|
246
|
-
let result = null
|
|
247
|
-
updateState((state) => {
|
|
248
|
-
if (payload.id) {
|
|
249
|
-
const index = state.scheduledTasks.findIndex((item) => item.id === payload.id)
|
|
250
|
-
if (index === -1) throw new Error('Task not found')
|
|
251
|
-
result = normalizeTask({
|
|
252
|
-
...state.scheduledTasks[index],
|
|
253
|
-
...payload,
|
|
254
|
-
updatedAt: now(),
|
|
255
|
-
})
|
|
256
|
-
state.scheduledTasks[index] = result
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
result = normalizeTask({
|
|
260
|
-
...payload,
|
|
261
|
-
createdAt: now(),
|
|
262
|
-
updatedAt: now(),
|
|
263
|
-
})
|
|
264
|
-
state.scheduledTasks.unshift(result)
|
|
265
|
-
})
|
|
266
|
-
return result
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function deleteTask(id) {
|
|
270
|
-
updateState((state) => {
|
|
271
|
-
state.scheduledTasks = state.scheduledTasks.filter((item) => item.id !== id)
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function updateTaskRun(id, patch = {}) {
|
|
276
|
-
let result = null
|
|
277
|
-
updateState((state) => {
|
|
278
|
-
const index = state.scheduledTasks.findIndex((item) => item.id === id)
|
|
279
|
-
if (index === -1) throw new Error('Task not found')
|
|
280
|
-
result = normalizeTask({
|
|
281
|
-
...state.scheduledTasks[index],
|
|
282
|
-
...patch,
|
|
283
|
-
updatedAt: now(),
|
|
284
|
-
})
|
|
285
|
-
state.scheduledTasks[index] = result
|
|
286
|
-
})
|
|
287
|
-
return result
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function getHolySheepApiConfig() {
|
|
291
|
-
return loadState().holySheepApi
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function saveHolySheepApiConfig(config = {}) {
|
|
295
|
-
let result = null
|
|
296
|
-
updateState((state) => {
|
|
297
|
-
state.holySheepApi = {
|
|
298
|
-
apiKey: String(config.apiKey || state.holySheepApi.apiKey || ''),
|
|
299
|
-
baseUrl: String(config.baseUrl || state.holySheepApi.baseUrl || ''),
|
|
300
|
-
model: String(config.model || state.holySheepApi.model || ''),
|
|
301
|
-
}
|
|
302
|
-
result = clone(state.holySheepApi)
|
|
303
|
-
})
|
|
304
|
-
return result
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
module.exports = {
|
|
308
|
-
STATE_FILE,
|
|
309
|
-
loadState,
|
|
310
|
-
saveState,
|
|
311
|
-
listConversations,
|
|
312
|
-
getConversation,
|
|
313
|
-
createConversation,
|
|
314
|
-
updateConversation,
|
|
315
|
-
addMessage,
|
|
316
|
-
searchWorkspace,
|
|
317
|
-
listTasks,
|
|
318
|
-
getTask,
|
|
319
|
-
saveTask,
|
|
320
|
-
deleteTask,
|
|
321
|
-
updateTaskRun,
|
|
322
|
-
getHolySheepApiConfig,
|
|
323
|
-
saveHolySheepApiConfig,
|
|
324
|
-
ensureConversation,
|
|
325
|
-
}
|
|
File without changes
|
|
File without changes
|