ds-bot 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/client.js +2164 -1102
- package/index.js +10 -1
- package/package.json +6 -4
- package/src/host/approvals.js +69 -0
- package/src/host/capabilities.js +521 -0
- package/src/host/channel.js +26 -4
- package/src/host/constants.js +12 -1
- package/src/host/delivery.js +34 -8
- package/src/host/diagnostics.js +7 -5
- package/src/host/groups.js +296 -59
- package/src/host/home-lock.js +16 -10
- package/src/host/images.js +37 -0
- package/src/host/memory.js +27 -16
- package/src/host/parts.js +94 -21
- package/src/host/prompt.js +7 -3
- package/src/host/resume.js +247 -0
- package/src/host/roster.js +110 -56
- package/src/host/secrets.js +9 -1
- package/src/host/store.js +129 -31
- package/src/host/time-context.js +46 -0
- package/src/host/tools.js +182 -29
- package/src/host/turns.js +263 -39
- package/src/host/update.js +5 -1
- package/src/shared/mentions.js +65 -0
- package/web-search.js +23 -7
package/src/host/channel.js
CHANGED
|
@@ -40,7 +40,7 @@ export function install(rt) {
|
|
|
40
40
|
...bot, sessionId: rt.chatOf(bot.id), parts: (segments ?? []).map(segment => segment.sessionId), ...(kickoff ? { waiting: true } : {}),
|
|
41
41
|
})),
|
|
42
42
|
// `parts`: the members' hidden group Sessions and their earlier segments, for usage.
|
|
43
|
-
rooms: Object.values(state.rooms).map(({ log, seen, botRounds, sessions, segments, ...room }) => {
|
|
43
|
+
rooms: Object.values(state.rooms).map(({ log, seen, botRounds, lateRounds, sessions, segments, ...room }) => {
|
|
44
44
|
const parts = [...Object.values(sessions ?? {}), ...Object.values(segments ?? {}).flat().map(segment => segment.sessionId)]
|
|
45
45
|
const last = (log ?? []).filter(line => line.kind === undefined).at(-1)
|
|
46
46
|
return { ...room, parts, ...(last ? { preview: last.who === 'User' ? last.text : `${last.who}:${last.text}` } : {}) }
|
|
@@ -51,6 +51,7 @@ export function install(rt) {
|
|
|
51
51
|
agents: Object.values(state.agents),
|
|
52
52
|
// Key names and who may use them; values never leave the host.
|
|
53
53
|
secrets: rt.secretsView(),
|
|
54
|
+
capabilities: rt.capabilityLibraryView(),
|
|
54
55
|
secretRequests: state.secretRequests ?? {},
|
|
55
56
|
colors: [...COLORS, ...EXTRA_COLORS],
|
|
56
57
|
models: rt.cachedCatalog(),
|
|
@@ -67,11 +68,19 @@ export function install(rt) {
|
|
|
67
68
|
})
|
|
68
69
|
// What changes without a new revision, sent with every state answer. `memoryNews`:
|
|
69
70
|
// per Bot, its latest memory change; `memoryReviews`: memory reviews waiting or
|
|
70
|
-
// running
|
|
71
|
-
|
|
71
|
+
// running; `work`: per Bot, the turn it runs and its tool; per group, the members whose
|
|
72
|
+
// turn there runs; and the approvals waiting.
|
|
73
|
+
const live = () => ({
|
|
74
|
+
activity: rt.activityView(),
|
|
75
|
+
work: { bots: rt.workView(), rooms: rt.roomWorkView(), approvals: rt.approvalsView() },
|
|
76
|
+
memoryNews: rt.memoryNews(),
|
|
77
|
+
memoryReviews: rt.memoryReviews(),
|
|
78
|
+
connectors: rt.connectorsView(),
|
|
79
|
+
capabilityStatuses: rt.capabilityStatusesView(),
|
|
80
|
+
})
|
|
72
81
|
// What a host that only reads the team may still answer.
|
|
73
82
|
// Updates change the dsh profile, not the team, so a reading host may run them too.
|
|
74
|
-
const READS = new Set(['state', 'models', 'efforts', 'earlier', 'room-progress', 'exchange', 'usage', 'schedules', 'memory-view', 'browser-wait', 'browser-result', 'diagnostics', 'diagnostics-log', 'diagnostics-clear', 'update-check', 'update-install', 'dsh-update-install', 'repo-stars'])
|
|
83
|
+
const READS = new Set(['state', 'models', 'efforts', 'earlier', 'room-progress', 'exchange', 'usage', 'schedules', 'memory-view', 'browser-wait', 'browser-result', 'diagnostics', 'diagnostics-log', 'diagnostics-clear', 'update-check', 'update-install', 'dsh-update-install', 'repo-stars', 'approval-answer'])
|
|
75
84
|
|
|
76
85
|
function setPrefs(body) {
|
|
77
86
|
if (body.theme !== undefined) {
|
|
@@ -196,6 +205,11 @@ export function install(rt) {
|
|
|
196
205
|
await rt.load()
|
|
197
206
|
await rt.deleteRoom(body.id)
|
|
198
207
|
return ok(view())
|
|
208
|
+
case 'approval-answer':
|
|
209
|
+
rt.answerApproval(body.id, body.choice)
|
|
210
|
+
return ok(true)
|
|
211
|
+
case 'stop-member-work':
|
|
212
|
+
return ok(await rt.stopMemberWork(String(body.roomId ?? ''), String(body.botId ?? '')))
|
|
199
213
|
case 'dismiss-question':
|
|
200
214
|
await rt.load()
|
|
201
215
|
if (state.questions[body.sessionId] !== undefined) {
|
|
@@ -215,6 +229,12 @@ export function install(rt) {
|
|
|
215
229
|
case 'connector-retry':
|
|
216
230
|
await rt.load()
|
|
217
231
|
return ok(await rt.connectorEndpoint(endpoint, body))
|
|
232
|
+
case 'capability-save':
|
|
233
|
+
case 'capability-delete':
|
|
234
|
+
case 'capability-approve':
|
|
235
|
+
case 'bot-capability':
|
|
236
|
+
await rt.load()
|
|
237
|
+
return ok(await rt.capabilityEndpoint(endpoint, body))
|
|
218
238
|
case 'schedules':
|
|
219
239
|
case 'schedule-create':
|
|
220
240
|
case 'schedule-delete':
|
|
@@ -237,6 +257,7 @@ export function install(rt) {
|
|
|
237
257
|
// Claim the question before queueing so a second answer is refused.
|
|
238
258
|
delete state.questions[sessionId]
|
|
239
259
|
try {
|
|
260
|
+
await rt.ensureCapabilities(sessionId)
|
|
240
261
|
const agent = await rt.resolveAgent(sessionId)
|
|
241
262
|
agent.followup(createUserMessage({ content: [{ type: 'text', text: reply.text }], source: reply.source }))
|
|
242
263
|
await ctx.sessions.flush(agent.session)
|
|
@@ -254,6 +275,7 @@ export function install(rt) {
|
|
|
254
275
|
// A Bot id or an earlier part of its own chat means the current part.
|
|
255
276
|
const past = rt.pastOwners.get(body.sessionId)
|
|
256
277
|
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId) : past && past.roomId === undefined ? rt.chatOf(past.botId) : body.sessionId
|
|
278
|
+
await rt.ensureCapabilities(sessionId)
|
|
257
279
|
await ctx.sessionController.prompt({
|
|
258
280
|
requestId: randomUUID(),
|
|
259
281
|
sessionId,
|
package/src/host/constants.js
CHANGED
|
@@ -26,6 +26,9 @@ export const ROOM_REPLY_TIMEOUT_MS = 240_000
|
|
|
26
26
|
export const SOURCE_KIND = 'bot'
|
|
27
27
|
// An answer from a question card, in the shape DSH's own late question replies use.
|
|
28
28
|
export const REPLY_KIND = 'user-question-reply'
|
|
29
|
+
// DSH's snapshot of a referenced conversation, logged right after the user's message
|
|
30
|
+
// that cites it.
|
|
31
|
+
export const REFERENCE_KIND = 'session-reference'
|
|
29
32
|
// `everyone`: every member answers a plain message in turn, admin first. `admin`: a
|
|
30
33
|
// plain message goes to the admin, who calls others with @mentions. New groups start
|
|
31
34
|
// in `admin` mode, because every extra speaker re-reads its whole context; a group
|
|
@@ -42,13 +45,21 @@ export const AGENT_SESSION_TITLE_MAX = 80
|
|
|
42
45
|
// Every tool this plugin registers for Bots. `read_browser` only exists in the
|
|
43
46
|
// Desktop profile, so a restriction filters this list down to registered names.
|
|
44
47
|
export const TEAM_TOOLS = [
|
|
45
|
-
'list_bots', 'message_bot', 'create_bot', 'update_bot', 'list_models',
|
|
48
|
+
'list_bots', 'message_bot', 'stop_bot', 'create_bot', 'update_bot', 'list_models',
|
|
46
49
|
'create_group', 'update_group', 'delete_group', 'post_to_group', 'ask_user',
|
|
47
50
|
'request_secret', 'list_secrets', 'group_relay', 'read_own_chat',
|
|
48
51
|
'read_group_chat', 'remember', 'forget', 'recall', 'read_browser',
|
|
52
|
+
'list_capabilities', 'save_capability', 'delete_capability',
|
|
53
|
+
'attach_capability', 'detach_capability',
|
|
49
54
|
]
|
|
50
55
|
// Rounds Bots may start in one group by posting, before the user speaks there again.
|
|
51
56
|
export const ROOM_BOT_ROUNDS = 3
|
|
57
|
+
// Answers a group gives to what members post after the round moved on (their finished
|
|
58
|
+
// work, mostly), before the user speaks there again.
|
|
59
|
+
export const ROOM_LATE_ROUNDS = 10
|
|
60
|
+
// A member still at work this long after its first tool call lets the round go on; it
|
|
61
|
+
// posts its reply when the work is done.
|
|
62
|
+
export const ROOM_WORK_GRACE_MS = 15_000
|
|
52
63
|
|
|
53
64
|
export const OWN_CHAT_LINE_MAX = 400
|
|
54
65
|
// The message an anchor points at is shown whole (up to this), so a long draft takes
|
package/src/host/delivery.js
CHANGED
|
@@ -12,8 +12,13 @@ export function install(rt) {
|
|
|
12
12
|
return resolved.agent
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const headerFor = (role, fromName, roomName, audience, note = 'new message') => {
|
|
15
|
+
const headerFor = (role, fromName, roomName, audience, note = 'new message', deferred = false, { resumed = false, retried = false } = {}) => {
|
|
16
|
+
if (deferred) return `[Message from ${fromName} · you read it during your last task; answer it now]`
|
|
17
|
+
if (retried && role !== 'room') return '[Your last reply wrote a tool call as text, so nothing ran]'
|
|
18
|
+
if (resumed && role === 'resume') return '[DeepSeek Harness restarted while you were working]'
|
|
19
|
+
if (resumed && role !== 'room') return `[Message from ${fromName} · DeepSeek Harness restarted while you were working on it]`
|
|
16
20
|
if (role === 'reply') return `[Reply from ${fromName}]`
|
|
21
|
+
if (role === 'stopped') return `[Stopped by ${fromName}]`
|
|
17
22
|
if (role === 'room') return `[Group chat "${roomName}" · ${note}]`
|
|
18
23
|
if (role === 'report') return `[Group chat "${roomName}" · replies to your post]`
|
|
19
24
|
if (role === 'kickoff') return '[Bot team setup]'
|
|
@@ -22,8 +27,18 @@ export function install(rt) {
|
|
|
22
27
|
return `[Message from ${fromName}]`
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
// Roles recorded as team exchanges.
|
|
31
|
+
const EXCHANGE_ROLES = new Set(['request', 'reply', 'stopped'])
|
|
32
|
+
|
|
25
33
|
// `sessionId` defaults to the recipient's own chat; group turns name its group Session.
|
|
26
|
-
|
|
34
|
+
// `deferred`: a request the Bot already read mid-turn, handed back to answer in a
|
|
35
|
+
// turn of its own; its exchange was recorded when it first arrived. `resumed` and
|
|
36
|
+
// `retried` carry on a turn that a restart cut off or that wrote its tool call as
|
|
37
|
+
// text (resume.js, turns.js), and `resent` is a message a shutdown cancelled; their
|
|
38
|
+
// exchange is recorded already too. `askedBy` is the member whose @mention called a
|
|
39
|
+
// group turn (groups.js). `fromRoomId`: the group whose Session the sender wrote
|
|
40
|
+
// from; the reply goes back to that Session (turns.js).
|
|
41
|
+
async function deliver({ fromId, toId, sessionId, text, role, hop = 0, replyTo, roomId, audience, note, exchangeId = randomUUID(), steer = false, deferred = false, resumed = false, retried = false, resent = false, askedBy, fromRoomId }) {
|
|
27
42
|
const from = rt.botOf(fromId)
|
|
28
43
|
const to = rt.botOf(toId)
|
|
29
44
|
if (to === undefined) throw new Error(`Unknown Bot ${toId}`)
|
|
@@ -41,16 +56,27 @@ export function install(rt) {
|
|
|
41
56
|
...(replyTo ? { replyTo } : {}),
|
|
42
57
|
...(roomId ? { roomId } : {}),
|
|
43
58
|
...(audience === 'user' ? { audience } : {}),
|
|
59
|
+
...(deferred ? { deferred: true } : {}),
|
|
60
|
+
...(resumed ? { resumed: true } : {}),
|
|
61
|
+
...(retried ? { retried: true } : {}),
|
|
62
|
+
...(askedBy ? { askedBy } : {}),
|
|
63
|
+
...(fromRoomId ? { senderRoomId: fromRoomId } : {}),
|
|
44
64
|
}
|
|
45
|
-
if (role
|
|
65
|
+
if (EXCHANGE_ROLES.has(role) && !deferred && !resumed && !retried && !resent) {
|
|
46
66
|
state.exchanges.push({ id: exchangeId, from: fromId, to: toId, text, role, time: Date.now(), ...(replyTo ? { replyTo } : {}), ...(audience === 'user' ? { audience } : {}) })
|
|
47
|
-
void rt.save()
|
|
67
|
+
void rt.save().catch(() => {})
|
|
48
68
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
const targetSessionId = sessionId ?? rt.chatOf(toId)
|
|
70
|
+
await rt.ensureCapabilities(targetSessionId)
|
|
71
|
+
const agent = await resolveAgent(targetSessionId)
|
|
72
|
+
// A steered message cuts into a running turn at its next step; a follow-up
|
|
73
|
+
// queues for the next turn (see turns.js for how each is routed).
|
|
74
|
+
const message = createUserMessage({
|
|
75
|
+
content: [{ type: 'text', text: `${headerFor(role, fromName, room?.name, audience, note, deferred, { resumed, retried })}\n${text}` }],
|
|
52
76
|
source,
|
|
53
|
-
})
|
|
77
|
+
})
|
|
78
|
+
if (steer) agent.steer(message)
|
|
79
|
+
else agent.followup(message)
|
|
54
80
|
await ctx.sessions.flush(agent.session)
|
|
55
81
|
return exchangeId
|
|
56
82
|
}
|
package/src/host/diagnostics.js
CHANGED
|
@@ -16,7 +16,7 @@ const LOG_MAX_BYTES = 1 << 20
|
|
|
16
16
|
const STACK_LINES = 8
|
|
17
17
|
const TEXT_MAX = 2000
|
|
18
18
|
export const REPORT_ENTRIES = 20
|
|
19
|
-
export const ISSUES_URL = 'https://github.com/
|
|
19
|
+
export const ISSUES_URL = 'https://github.com/Fei2411/DeepSeek-Bot/issues/new'
|
|
20
20
|
|
|
21
21
|
const quote = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
22
22
|
|
|
@@ -144,7 +144,7 @@ export function install(rt) {
|
|
|
144
144
|
}).catch((error) => {
|
|
145
145
|
if (fileFailed) return
|
|
146
146
|
fileFailed = true
|
|
147
|
-
console.error('[ds-bot] diagnostics log not written:', error?.code ?? error?.message ?? error)
|
|
147
|
+
console.error('[ds-bot] diagnostics log not written:', error?.code ?? clean(error?.message ?? String(error)))
|
|
148
148
|
})
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -164,13 +164,15 @@ export function install(rt) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
const warn = (template, ...args) => {
|
|
167
|
-
ctx.logger.warn(template, ...args)
|
|
168
|
-
console.error(`[ds-bot] ${template}`, ...args.map(arg => arg?.stack ?? arg))
|
|
169
167
|
const error = args.find(arg => arg instanceof Error)
|
|
168
|
+
const line = clean(format(template, ...args.map(arg => (arg instanceof Error ? errorText(arg) : arg))))
|
|
169
|
+
const frames = typeof error?.stack === 'string' ? error.stack.split('\n').filter(item => /^\s+at /.test(item)).slice(0, STACK_LINES).map(item => item.trim()) : []
|
|
170
|
+
ctx.logger.warn('%s', line)
|
|
171
|
+
console.error(`[ds-bot] ${line}${frames.length > 0 ? `\n${clean(frames.join('\n'))}` : ''}`)
|
|
170
172
|
note({
|
|
171
173
|
level: 'warn',
|
|
172
174
|
source: 'host',
|
|
173
|
-
text:
|
|
175
|
+
text: line.replace(/^ds-bot: /, ''),
|
|
174
176
|
code: typeof error?.code === 'string' ? error.code : undefined,
|
|
175
177
|
stack: error?.stack,
|
|
176
178
|
})
|