@lemoncat7/dsh-partner 1.4.2 → 1.5.1
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 +38 -0
- package/docs/architecture.md +99 -0
- package/lib/agent-runtime.d.ts +31 -8
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +230 -48
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api/features/workspace-api.d.ts +18 -0
- package/lib/api/features/workspace-api.d.ts.map +1 -0
- package/lib/api/features/workspace-api.js +188 -0
- package/lib/api/features/workspace-api.js.map +1 -0
- package/lib/api/http.d.ts +12 -0
- package/lib/api/http.d.ts.map +1 -0
- package/lib/api/http.js +53 -0
- package/lib/api/http.js.map +1 -0
- package/lib/api.d.ts +10 -0
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +48 -63
- package/lib/api.js.map +1 -1
- package/lib/channels/manager.d.ts +4 -1
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +20 -3
- package/lib/channels/manager.js.map +1 -1
- package/lib/client-api.d.ts +157 -1
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client-controller.d.ts +15 -0
- package/lib/client-controller.d.ts.map +1 -0
- package/lib/client-controller.js +69 -0
- package/lib/client-controller.js.map +1 -0
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +2827 -885
- package/lib/client.js.map +4 -4
- package/lib/collaboration/composition.d.ts +27 -0
- package/lib/collaboration/composition.d.ts.map +1 -0
- package/lib/collaboration/composition.js +345 -0
- package/lib/collaboration/composition.js.map +1 -0
- package/lib/collaboration/domain.d.ts +38 -0
- package/lib/collaboration/domain.d.ts.map +1 -0
- package/lib/collaboration/domain.js +3 -0
- package/lib/collaboration/domain.js.map +1 -0
- package/lib/collaboration/retry-policy.d.ts +4 -0
- package/lib/collaboration/retry-policy.d.ts.map +1 -0
- package/lib/collaboration/retry-policy.js +18 -0
- package/lib/collaboration/retry-policy.js.map +1 -0
- package/lib/collaboration/service.d.ts +76 -0
- package/lib/collaboration/service.d.ts.map +1 -0
- package/lib/collaboration/service.js +370 -0
- package/lib/collaboration/service.js.map +1 -0
- package/lib/companions/service.d.ts +13 -0
- package/lib/companions/service.d.ts.map +1 -0
- package/lib/companions/service.js +51 -0
- package/lib/companions/service.js.map +1 -0
- package/lib/concern-domain.d.ts +8 -1
- package/lib/concern-domain.d.ts.map +1 -1
- package/lib/concern-domain.js +25 -1
- package/lib/concern-domain.js.map +1 -1
- package/lib/concern-store.d.ts.map +1 -1
- package/lib/concern-store.js +2 -1
- package/lib/concern-store.js.map +1 -1
- package/lib/concern-tool.d.ts +6 -0
- package/lib/concern-tool.d.ts.map +1 -1
- package/lib/concern-tool.js +35 -9
- package/lib/concern-tool.js.map +1 -1
- package/lib/core/collections.d.ts +2 -0
- package/lib/core/collections.d.ts.map +1 -0
- package/lib/core/collections.js +6 -0
- package/lib/core/collections.js.map +1 -0
- package/lib/core/semaphore.d.ts +10 -0
- package/lib/core/semaphore.d.ts.map +1 -0
- package/lib/core/semaphore.js +32 -0
- package/lib/core/semaphore.js.map +1 -0
- package/lib/core/validation.d.ts +7 -0
- package/lib/core/validation.d.ts.map +1 -0
- package/lib/core/validation.js +40 -0
- package/lib/core/validation.js.map +1 -0
- package/lib/domain.d.ts +28 -2
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js +3 -3
- package/lib/domain.js.map +1 -1
- package/lib/execution/agent-support.d.ts +12 -0
- package/lib/execution/agent-support.d.ts.map +1 -0
- package/lib/execution/agent-support.js +48 -0
- package/lib/execution/agent-support.js.map +1 -0
- package/lib/execution/domain.d.ts +17 -0
- package/lib/execution/domain.d.ts.map +1 -0
- package/lib/execution/domain.js +2 -0
- package/lib/execution/domain.js.map +1 -0
- package/lib/execution/service.d.ts +52 -0
- package/lib/execution/service.d.ts.map +1 -0
- package/lib/execution/service.js +175 -0
- package/lib/execution/service.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +42 -3
- package/lib/index.js.map +1 -1
- package/lib/memory-reflection.js +2 -2
- package/lib/memory-reflection.js.map +1 -1
- package/lib/scheduler/domain.d.ts +25 -0
- package/lib/scheduler/domain.d.ts.map +1 -0
- package/lib/scheduler/domain.js +2 -0
- package/lib/scheduler/domain.js.map +1 -0
- package/lib/scheduler/service.d.ts +26 -0
- package/lib/scheduler/service.d.ts.map +1 -0
- package/lib/scheduler/service.js +177 -0
- package/lib/scheduler/service.js.map +1 -0
- package/lib/skills/builtin.d.ts +7 -0
- package/lib/skills/builtin.d.ts.map +1 -0
- package/lib/skills/builtin.js +16 -0
- package/lib/skills/builtin.js.map +1 -0
- package/lib/skills/domain.d.ts +52 -0
- package/lib/skills/domain.d.ts.map +1 -0
- package/lib/skills/domain.js +2 -0
- package/lib/skills/domain.js.map +1 -0
- package/lib/skills/loader.d.ts +18 -0
- package/lib/skills/loader.d.ts.map +1 -0
- package/lib/skills/loader.js +134 -0
- package/lib/skills/loader.js.map +1 -0
- package/lib/skills/markets/adapters.d.ts +8 -0
- package/lib/skills/markets/adapters.d.ts.map +1 -0
- package/lib/skills/markets/adapters.js +72 -0
- package/lib/skills/markets/adapters.js.map +1 -0
- package/lib/skills/markets/builtin.d.ts +10 -0
- package/lib/skills/markets/builtin.d.ts.map +1 -0
- package/lib/skills/markets/builtin.js +19 -0
- package/lib/skills/markets/builtin.js.map +1 -0
- package/lib/skills/network.d.ts +14 -0
- package/lib/skills/network.d.ts.map +1 -0
- package/lib/skills/network.js +174 -0
- package/lib/skills/network.js.map +1 -0
- package/lib/skills/repository.d.ts +17 -0
- package/lib/skills/repository.d.ts.map +1 -0
- package/lib/skills/repository.js +76 -0
- package/lib/skills/repository.js.map +1 -0
- package/lib/skills/service.d.ts +48 -0
- package/lib/skills/service.d.ts.map +1 -0
- package/lib/skills/service.js +248 -0
- package/lib/skills/service.js.map +1 -0
- package/lib/skills/zip.d.ts +3 -0
- package/lib/skills/zip.d.ts.map +1 -0
- package/lib/skills/zip.js +59 -0
- package/lib/skills/zip.js.map +1 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +69 -3
- package/lib/store.js.map +1 -1
- package/lib/tasks/domain.d.ts +39 -0
- package/lib/tasks/domain.d.ts.map +1 -0
- package/lib/tasks/domain.js +3 -0
- package/lib/tasks/domain.js.map +1 -0
- package/lib/tasks/result.d.ts +14 -0
- package/lib/tasks/result.d.ts.map +1 -0
- package/lib/tasks/result.js +109 -0
- package/lib/tasks/result.js.map +1 -0
- package/lib/tasks/service.d.ts +39 -0
- package/lib/tasks/service.d.ts.map +1 -0
- package/lib/tasks/service.js +334 -0
- package/lib/tasks/service.js.map +1 -0
- package/lib/ui/companion-access-panel.d.ts +4 -0
- package/lib/ui/companion-access-panel.d.ts.map +1 -0
- package/lib/ui/companion-access-panel.js +46 -0
- package/lib/ui/companion-access-panel.js.map +1 -0
- package/lib/ui/companion-create.d.ts +11 -0
- package/lib/ui/companion-create.d.ts.map +1 -0
- package/lib/ui/companion-create.js +30 -0
- package/lib/ui/companion-create.js.map +1 -0
- package/lib/ui/partner-components.d.ts +36 -0
- package/lib/ui/partner-components.d.ts.map +1 -0
- package/lib/ui/partner-components.js +32 -0
- package/lib/ui/partner-components.js.map +1 -0
- package/lib/ui/schedule-panel.d.ts +5 -0
- package/lib/ui/schedule-panel.d.ts.map +1 -0
- package/lib/ui/schedule-panel.js +86 -0
- package/lib/ui/schedule-panel.js.map +1 -0
- package/lib/ui/skills-panel.d.ts +5 -0
- package/lib/ui/skills-panel.d.ts.map +1 -0
- package/lib/ui/skills-panel.js +236 -0
- package/lib/ui/skills-panel.js.map +1 -0
- package/lib/ui/task-board-panel.d.ts +2 -0
- package/lib/ui/task-board-panel.d.ts.map +1 -0
- package/lib/ui/task-board-panel.js +282 -0
- package/lib/ui/task-board-panel.js.map +1 -0
- package/lib/ui/workspace-components.d.ts +40 -0
- package/lib/ui/workspace-components.d.ts.map +1 -0
- package/lib/ui/workspace-components.js +61 -0
- package/lib/ui/workspace-components.js.map +1 -0
- package/package.json +2 -1
package/lib/agent-runtime.js
CHANGED
|
@@ -2,13 +2,15 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, realpath, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { BlockAssembler, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
5
|
-
import { createScope, scopeOf } from '@deepseek-ai/dsh-scope';
|
|
5
|
+
import { createScope, scopeOf, scopeParentOf } from '@deepseek-ai/dsh-scope';
|
|
6
6
|
import { concernObservationPrompt } from './autonomy.js';
|
|
7
7
|
import { boundedConcernCheckMinutes } from './concern-domain.js';
|
|
8
8
|
import { PARTNER_MEDIA_MAX_BYTES, safeMediaName } from './channel-message.js';
|
|
9
9
|
import { listConcernFileSources } from './concern-sources.js';
|
|
10
10
|
import { CONCERN_CREATED_NOTICE, renderConcernCreatedNotice } from './concern-notification.js';
|
|
11
11
|
import { HEARTBEAT_LOCAL_COMMAND, heartbeatLocalCommandTool } from './heartbeat-command.js';
|
|
12
|
+
import { assistantTextAfter, renderPartnerPersona, renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
13
|
+
export { renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
12
14
|
const HEARTBEAT_TIMEOUT_MS = 115_000;
|
|
13
15
|
const HEARTBEAT_DISCOVERY_BUDGET_MS = 72_000;
|
|
14
16
|
const HEARTBEAT_TOOL_TIMEOUT_MS = 18_000;
|
|
@@ -19,6 +21,7 @@ const HEARTBEAT_WEB_FETCH_RESULT_LIMIT = 40_000;
|
|
|
19
21
|
const HEARTBEAT_WEB_SOURCE_RESULT_LIMIT = 125_000;
|
|
20
22
|
const HEARTBEAT_AUDIT_INPUT_LIMIT = 320;
|
|
21
23
|
const HEARTBEAT_AUDIT_OUTPUT_LIMIT = 520;
|
|
24
|
+
const PARTNER_TASK_TIMEOUT_MS = 30 * 60_000;
|
|
22
25
|
const HEARTBEAT_READ_ONLY_TOOLS = new Set([
|
|
23
26
|
'knowledge_base_search',
|
|
24
27
|
'knowledge_search',
|
|
@@ -40,19 +43,34 @@ export class PartnerAgentRuntime {
|
|
|
40
43
|
memory;
|
|
41
44
|
reflection;
|
|
42
45
|
concerns;
|
|
46
|
+
composer;
|
|
43
47
|
handles = new Map();
|
|
44
48
|
queues = new Map();
|
|
45
49
|
heartbeatQueues = new Map();
|
|
50
|
+
taskQueues = new Map();
|
|
46
51
|
steeringQueues = new Map();
|
|
47
52
|
workspaces = new Map();
|
|
48
53
|
profileVersions = new Map();
|
|
49
|
-
|
|
54
|
+
restoredCompositions = new Map();
|
|
55
|
+
restoredCompositionJobs = new Map();
|
|
56
|
+
disposeAgentListener;
|
|
57
|
+
constructor(ctx, store, defaultCwd, memory, reflection, concerns, composer) {
|
|
50
58
|
this.ctx = ctx;
|
|
51
59
|
this.store = store;
|
|
52
60
|
this.defaultCwd = defaultCwd;
|
|
53
61
|
this.memory = memory;
|
|
54
62
|
this.reflection = reflection;
|
|
55
63
|
this.concerns = concerns;
|
|
64
|
+
this.composer = composer;
|
|
65
|
+
this.disposeAgentListener = typeof this.ctx.on === 'function'
|
|
66
|
+
? this.ctx.on('agent/disposed', ({ agent }) => {
|
|
67
|
+
const restored = this.restoredCompositions.get(agent.session.id);
|
|
68
|
+
if (restored?.agent !== agent)
|
|
69
|
+
return;
|
|
70
|
+
restored.dispose();
|
|
71
|
+
this.restoredCompositions.delete(agent.session.id);
|
|
72
|
+
})
|
|
73
|
+
: () => { };
|
|
56
74
|
}
|
|
57
75
|
async reply(companion, channelId, userId, message) {
|
|
58
76
|
const key = `${channelId}:${userId}`;
|
|
@@ -122,6 +140,114 @@ export class PartnerAgentRuntime {
|
|
|
122
140
|
await this.ensureAgent(companion, route);
|
|
123
141
|
return route;
|
|
124
142
|
}
|
|
143
|
+
async ensureLocalSessionRecord(companionId) {
|
|
144
|
+
const companion = this.store.snapshot().companions.find(item => item.id === companionId);
|
|
145
|
+
if (companion === undefined)
|
|
146
|
+
throw new Error('伙伴身份不存在');
|
|
147
|
+
const existing = this.store.snapshot().sessions.find(item => item.companionId === companionId && item.kind === 'local');
|
|
148
|
+
if (existing !== undefined && canReuseSession(existing, companion.id, this.ctx.workspaceRegistry.archivedSessionIds)) {
|
|
149
|
+
return existing;
|
|
150
|
+
}
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
const route = {
|
|
153
|
+
id: `route-${randomUUID()}`,
|
|
154
|
+
kind: 'local',
|
|
155
|
+
channelId: '@local',
|
|
156
|
+
userId: `owner:${companion.id}`,
|
|
157
|
+
companionId: companion.id,
|
|
158
|
+
sessionId: `session-${randomUUID()}`,
|
|
159
|
+
cwd: partnerCwd(this.defaultCwd, companion.id),
|
|
160
|
+
lastMessageAt: now,
|
|
161
|
+
};
|
|
162
|
+
await this.store.update(state => {
|
|
163
|
+
state.sessions = state.sessions.filter(item => !(item.companionId === companion.id && item.kind === 'local'));
|
|
164
|
+
state.sessions.push(route);
|
|
165
|
+
});
|
|
166
|
+
return route;
|
|
167
|
+
}
|
|
168
|
+
async createLocalSession(companionId) {
|
|
169
|
+
const companion = this.store.snapshot().companions.find(item => item.id === companionId);
|
|
170
|
+
if (companion === undefined)
|
|
171
|
+
throw new Error('伙伴身份不存在');
|
|
172
|
+
const route = await this.ensureLocalSessionRecord(companionId);
|
|
173
|
+
await this.ensureAgent(companion, route);
|
|
174
|
+
return route;
|
|
175
|
+
}
|
|
176
|
+
async notifyTaskProgress(task, previousStatus) {
|
|
177
|
+
if (!task.creatorCompanionId || (task.status !== 'review' && task.status !== 'done' && task.status !== 'blocked'))
|
|
178
|
+
return;
|
|
179
|
+
const companion = this.store.snapshot().companions.find(item => item.id === task.creatorCompanionId);
|
|
180
|
+
if (!companion)
|
|
181
|
+
return;
|
|
182
|
+
const state = this.store.snapshot();
|
|
183
|
+
const routes = state.sessions.filter(item => item.companionId === companion.id);
|
|
184
|
+
const downstream = state.tasks.filter(item => item.dependencyTaskIds.includes(task.id) && item.status !== 'done');
|
|
185
|
+
const route = selectTaskNotificationRoute(routes, task.creatorSessionId, item => this.isArchived(item))
|
|
186
|
+
?? await this.createLocalSession(companion.id);
|
|
187
|
+
const agent = await this.ensureAgent(companion, route);
|
|
188
|
+
const isReviewer = task.status === 'review' && task.reviewerCompanionId === companion.id;
|
|
189
|
+
const instruction = isReviewer
|
|
190
|
+
? '这是你创建并分配的看板任务,执行伙伴已提交结果,现在默认由你验收。请根据任务要求真实核验已有产出;通过后必须调用 partner_task_board accept,不通过则调用 reject 并写明原因。不要重新执行该任务。'
|
|
191
|
+
: task.status === 'review'
|
|
192
|
+
? '这是你创建并分配的看板任务进度事件。执行结果已提交并等待指定伙伴验收;不要重新执行,请向用户简要说明当前进度。'
|
|
193
|
+
: task.status === 'done'
|
|
194
|
+
? '这是你创建并分配的看板任务终态事件。不要重新执行已完成的工作。请检查依赖它的后续任务是否解锁,需要时继续在看板上分配。对用户的终态结果由系统直接投递,不要只改写成“已完成”或进度摘要。'
|
|
195
|
+
: '这是你创建并分配的看板任务受阻事件。请保留已有产出、检查是否能调整分工或解锁依赖;需要用户介入时,明确说明阻塞原因与所需动作。';
|
|
196
|
+
agent.followup(createUserMessage({
|
|
197
|
+
content: [{ type: 'text', text: [
|
|
198
|
+
instruction,
|
|
199
|
+
`任务:${task.title}`,
|
|
200
|
+
`状态:${previousStatus} → ${task.status}`,
|
|
201
|
+
task.resultSummary ? `结果:\n${task.resultSummary.slice(0, 3000)}` : '',
|
|
202
|
+
task.status === 'review' && task.reviewHandoff ? `验收交接:\n${task.reviewHandoff.slice(0, 2000)}` : '',
|
|
203
|
+
task.reviewSummary ? `验收:\n${task.reviewSummary.slice(0, 2000)}` : '',
|
|
204
|
+
downstream.length > 0 ? `后续任务:\n${downstream.map(item => `- ${item.title}(${item.status})`).join('\n')}` : '没有依赖此任务的后续任务。',
|
|
205
|
+
].filter(Boolean).join('\n\n') }],
|
|
206
|
+
source: { kind: 'plugin', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: taskProgressNoticeSummary(task.status) },
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
async executeTask(input) {
|
|
210
|
+
const route = await this.createLocalSession(input.companion.id);
|
|
211
|
+
const key = route.sessionId;
|
|
212
|
+
const previous = this.taskQueues.get(key) ?? Promise.resolve();
|
|
213
|
+
let output = '';
|
|
214
|
+
const current = previous.catch(() => { }).then(async () => {
|
|
215
|
+
const agent = await this.ensureAgent(input.companion, route);
|
|
216
|
+
if (agent.status !== 'idle')
|
|
217
|
+
await agent.whenIdle();
|
|
218
|
+
const startSeq = agent.session.seq;
|
|
219
|
+
agent.followup(createUserMessage({
|
|
220
|
+
content: [{ type: 'text', text: input.prompt }],
|
|
221
|
+
source: { kind: 'plugin', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: input.sourceId.startsWith('review:') ? '伙伴核验看板任务' : '伙伴执行看板任务' },
|
|
222
|
+
}));
|
|
223
|
+
let timedOut = false;
|
|
224
|
+
const timer = setTimeout(() => {
|
|
225
|
+
timedOut = true;
|
|
226
|
+
agent.cancel({ kind: 'hook', reason: 'partner task execution timed out' });
|
|
227
|
+
}, PARTNER_TASK_TIMEOUT_MS);
|
|
228
|
+
timer.unref?.();
|
|
229
|
+
try {
|
|
230
|
+
await agent.whenIdle();
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
}
|
|
235
|
+
if (timedOut)
|
|
236
|
+
throw new Error('伙伴看板任务执行超时');
|
|
237
|
+
output = assistantTextAfter(agent, startSeq).trim();
|
|
238
|
+
if (!output)
|
|
239
|
+
throw new Error('伙伴会话没有产生任务结果');
|
|
240
|
+
});
|
|
241
|
+
this.taskQueues.set(key, current);
|
|
242
|
+
try {
|
|
243
|
+
await current;
|
|
244
|
+
return { run: { id: `session-task-${randomUUID()}` }, output };
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
if (this.taskQueues.get(key) === current)
|
|
248
|
+
this.taskQueues.delete(key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
125
251
|
isArchived(route) {
|
|
126
252
|
return this.ctx.workspaceRegistry.archivedSessionIds.includes(route.sessionId);
|
|
127
253
|
}
|
|
@@ -131,10 +257,14 @@ export class PartnerAgentRuntime {
|
|
|
131
257
|
throw new Error('伙伴会话不存在');
|
|
132
258
|
if (!this.isArchived(previous))
|
|
133
259
|
return previous;
|
|
260
|
+
const companion = this.store.snapshot().companions.find(item => item.id === previous.companionId);
|
|
261
|
+
if (companion === undefined)
|
|
262
|
+
throw new Error('伙伴身份不存在');
|
|
134
263
|
const next = renewedSession(previous);
|
|
135
264
|
await this.store.update(state => {
|
|
136
265
|
state.sessions = state.sessions.map(item => item.id === routeId ? next : item);
|
|
137
266
|
});
|
|
267
|
+
await this.ensureAgent(companion, next);
|
|
138
268
|
return next;
|
|
139
269
|
}
|
|
140
270
|
async heartbeat(companion, route, concerns) {
|
|
@@ -351,6 +481,7 @@ export class PartnerAgentRuntime {
|
|
|
351
481
|
const handle = this.handles.get(item.sessionId);
|
|
352
482
|
this.handles.delete(item.sessionId);
|
|
353
483
|
this.profileVersions.delete(item.sessionId);
|
|
484
|
+
await this.releaseRestoredComposition(item.sessionId);
|
|
354
485
|
await handle?.dispose().catch(() => { });
|
|
355
486
|
}));
|
|
356
487
|
}
|
|
@@ -360,17 +491,31 @@ export class PartnerAgentRuntime {
|
|
|
360
491
|
const handle = this.handles.get(item.sessionId);
|
|
361
492
|
this.handles.delete(item.sessionId);
|
|
362
493
|
this.profileVersions.delete(item.sessionId);
|
|
494
|
+
await this.releaseRestoredComposition(item.sessionId);
|
|
363
495
|
await handle?.dispose().catch(() => { });
|
|
364
496
|
}));
|
|
365
497
|
await this.store.update(state => { state.sessions = state.sessions.filter(item => item.channelId !== channelId); });
|
|
366
498
|
}
|
|
367
499
|
async close() {
|
|
500
|
+
this.disposeAgentListener();
|
|
501
|
+
await Promise.allSettled(this.restoredCompositionJobs.values());
|
|
502
|
+
for (const restored of this.restoredCompositions.values())
|
|
503
|
+
restored.dispose();
|
|
504
|
+
this.restoredCompositions.clear();
|
|
505
|
+
this.restoredCompositionJobs.clear();
|
|
368
506
|
await Promise.all([...this.handles.values()].map(handle => handle.dispose().catch(() => { })));
|
|
369
507
|
this.handles.clear();
|
|
370
508
|
this.profileVersions.clear();
|
|
371
509
|
this.heartbeatQueues.clear();
|
|
510
|
+
this.taskQueues.clear();
|
|
372
511
|
this.steeringQueues.clear();
|
|
373
512
|
}
|
|
513
|
+
async releaseRestoredComposition(sessionId) {
|
|
514
|
+
await this.restoredCompositionJobs.get(sessionId)?.catch(() => { });
|
|
515
|
+
const restored = this.restoredCompositions.get(sessionId);
|
|
516
|
+
restored?.dispose();
|
|
517
|
+
this.restoredCompositions.delete(sessionId);
|
|
518
|
+
}
|
|
374
519
|
async drive(companion, channelId, userId, message) {
|
|
375
520
|
const session = await this.ensureSession(companion, channelId, userId);
|
|
376
521
|
const agent = await this.ensureAgent(companion, session);
|
|
@@ -398,7 +543,7 @@ export class PartnerAgentRuntime {
|
|
|
398
543
|
source: { kind: 'user' },
|
|
399
544
|
}));
|
|
400
545
|
await agent.whenIdle();
|
|
401
|
-
const response =
|
|
546
|
+
const response = assistantTextAfter(agent, startSeq);
|
|
402
547
|
await this.store.update(state => {
|
|
403
548
|
const target = state.sessions.find(item => item.id === session.id);
|
|
404
549
|
if (target)
|
|
@@ -443,6 +588,7 @@ export class PartnerAgentRuntime {
|
|
|
443
588
|
const now = Date.now();
|
|
444
589
|
const session = {
|
|
445
590
|
id: `route-${randomUUID()}`,
|
|
591
|
+
kind: 'channel',
|
|
446
592
|
channelId,
|
|
447
593
|
userId,
|
|
448
594
|
companionId: companion.id,
|
|
@@ -469,6 +615,7 @@ export class PartnerAgentRuntime {
|
|
|
469
615
|
}
|
|
470
616
|
const live = this.ctx.agents.get(route.sessionId);
|
|
471
617
|
if (live !== undefined) {
|
|
618
|
+
await this.ensureRestoredComposition(live, companion, route, profile);
|
|
472
619
|
await this.attachSession(companion, route);
|
|
473
620
|
if (profile)
|
|
474
621
|
this.injectProfileUpdate(live, route.sessionId, profile);
|
|
@@ -483,7 +630,7 @@ export class PartnerAgentRuntime {
|
|
|
483
630
|
agentCtx.systemPrompt.section({
|
|
484
631
|
name: 'partner-identity',
|
|
485
632
|
order: -10,
|
|
486
|
-
text:
|
|
633
|
+
text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation'),
|
|
487
634
|
});
|
|
488
635
|
agentCtx.systemPrompt.section({
|
|
489
636
|
name: 'partner-tool-routing',
|
|
@@ -496,6 +643,7 @@ export class PartnerAgentRuntime {
|
|
|
496
643
|
order: -8,
|
|
497
644
|
text: renderProfile(profile),
|
|
498
645
|
});
|
|
646
|
+
await this.composer?.compose(agentCtx, companion);
|
|
499
647
|
};
|
|
500
648
|
let handle;
|
|
501
649
|
try {
|
|
@@ -522,6 +670,68 @@ export class PartnerAgentRuntime {
|
|
|
522
670
|
await this.attachSession(companion, route);
|
|
523
671
|
return handle.agent;
|
|
524
672
|
}
|
|
673
|
+
async ensureRestoredComposition(agent, companion, route, profile) {
|
|
674
|
+
const installed = this.restoredCompositions.get(route.sessionId);
|
|
675
|
+
if (installed?.agent === agent)
|
|
676
|
+
return;
|
|
677
|
+
const pending = this.restoredCompositionJobs.get(route.sessionId);
|
|
678
|
+
if (pending !== undefined)
|
|
679
|
+
return pending;
|
|
680
|
+
const job = this.composeRestoredAgent(agent, companion, route, profile);
|
|
681
|
+
this.restoredCompositionJobs.set(route.sessionId, job);
|
|
682
|
+
try {
|
|
683
|
+
await job;
|
|
684
|
+
}
|
|
685
|
+
finally {
|
|
686
|
+
if (this.restoredCompositionJobs.get(route.sessionId) === job)
|
|
687
|
+
this.restoredCompositionJobs.delete(route.sessionId);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async composeRestoredAgent(agent, companion, route, profile) {
|
|
691
|
+
await agent.whenIdle();
|
|
692
|
+
let dispose;
|
|
693
|
+
await agent.runMaintenance(async (signal) => {
|
|
694
|
+
if (signal.aborted)
|
|
695
|
+
throw signal.reason;
|
|
696
|
+
const agentCtx = agent.ctx;
|
|
697
|
+
const key = scopeOf(agentCtx);
|
|
698
|
+
if (key === undefined)
|
|
699
|
+
throw new Error('伙伴会话缺少 Agent 作用域');
|
|
700
|
+
if (scopeParentOf(key) === undefined) {
|
|
701
|
+
const presets = agentCtx.get('agentPresets');
|
|
702
|
+
if (presets === undefined)
|
|
703
|
+
throw new Error('伙伴会话缺少 Agent Presets 服务');
|
|
704
|
+
await presets.mount(agentCtx, companion.presetId);
|
|
705
|
+
}
|
|
706
|
+
const disposers = [];
|
|
707
|
+
try {
|
|
708
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-identity', order: -10, text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation') }));
|
|
709
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-tool-routing', order: -9, text: renderToolProtocol() }));
|
|
710
|
+
if (profile && profile.entries.length > 0)
|
|
711
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-user-profile', order: -8, text: renderProfile(profile) }));
|
|
712
|
+
if (this.composer)
|
|
713
|
+
disposers.push(await this.composer.compose(agentCtx, companion));
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
for (let index = disposers.length - 1; index >= 0; index -= 1)
|
|
717
|
+
disposers[index]?.();
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
let disposed = false;
|
|
721
|
+
dispose = () => {
|
|
722
|
+
if (disposed)
|
|
723
|
+
return;
|
|
724
|
+
disposed = true;
|
|
725
|
+
for (let index = disposers.length - 1; index >= 0; index -= 1)
|
|
726
|
+
disposers[index]?.();
|
|
727
|
+
};
|
|
728
|
+
});
|
|
729
|
+
if (dispose === undefined)
|
|
730
|
+
throw new Error('伙伴会话恢复组合未完成');
|
|
731
|
+
const previous = this.restoredCompositions.get(route.sessionId);
|
|
732
|
+
previous?.dispose();
|
|
733
|
+
this.restoredCompositions.set(route.sessionId, { agent, dispose });
|
|
734
|
+
}
|
|
525
735
|
injectProfileUpdate(agent, sessionId, profile) {
|
|
526
736
|
const previous = this.profileVersions.get(sessionId);
|
|
527
737
|
if (previous === profile.version)
|
|
@@ -544,6 +754,21 @@ export class PartnerAgentRuntime {
|
|
|
544
754
|
await (await workspace).attachSession(route.sessionId);
|
|
545
755
|
}
|
|
546
756
|
}
|
|
757
|
+
const TASK_PROGRESS_NOTICE_SUMMARIES = ['看板任务待验收', '看板任务已完成', '看板任务受阻'];
|
|
758
|
+
function taskProgressNoticeSummary(status) {
|
|
759
|
+
if (status === 'review')
|
|
760
|
+
return '看板任务待验收';
|
|
761
|
+
if (status === 'done')
|
|
762
|
+
return '看板任务已完成';
|
|
763
|
+
return '看板任务受阻';
|
|
764
|
+
}
|
|
765
|
+
export function selectTaskNotificationRoute(routes, creatorSessionId, archived) {
|
|
766
|
+
const available = routes.filter(route => !archived(route));
|
|
767
|
+
return available.find(route => route.sessionId === creatorSessionId)
|
|
768
|
+
?? available.filter(route => route.kind === 'channel').sort((left, right) => right.lastMessageAt - left.lastMessageAt)[0]
|
|
769
|
+
?? available.find(route => route.kind === 'local')
|
|
770
|
+
?? available.sort((left, right) => right.lastMessageAt - left.lastMessageAt)[0];
|
|
771
|
+
}
|
|
547
772
|
function delay(milliseconds) {
|
|
548
773
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
549
774
|
}
|
|
@@ -660,13 +885,6 @@ function renderDeferredMentions(entries) {
|
|
|
660
885
|
...entries.map(item => `- ${item.event}${item.evidence ? `(依据:${item.evidence})` : ''}`),
|
|
661
886
|
].join('\n');
|
|
662
887
|
}
|
|
663
|
-
export function resolveAgentOptions(defaultModel, companion) {
|
|
664
|
-
return {
|
|
665
|
-
...defaultModel.currentSelection(),
|
|
666
|
-
...(companion.provider ? { provider: companion.provider } : {}),
|
|
667
|
-
...(companion.model ? { model: companion.model } : {}),
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
888
|
export function canReuseSession(route, companionId, archivedSessionIds) {
|
|
671
889
|
return route.companionId === companionId && !archivedSessionIds.includes(route.sessionId);
|
|
672
890
|
}
|
|
@@ -750,7 +968,7 @@ async function runHeartbeatInference(ctx, agent, companion, concerns, signal) {
|
|
|
750
968
|
for await (const chunk of prepared.stream({
|
|
751
969
|
...prepared.config,
|
|
752
970
|
messages,
|
|
753
|
-
system:
|
|
971
|
+
system: renderPartnerPersona(companion, 'heartbeat'),
|
|
754
972
|
tools: discoveryOpen ? tools : [],
|
|
755
973
|
signal,
|
|
756
974
|
}))
|
|
@@ -1001,42 +1219,6 @@ function formatDuration(milliseconds) {
|
|
|
1001
1219
|
return `${(milliseconds / 1_000).toFixed(milliseconds < 10_000 ? 1 : 0)} 秒`;
|
|
1002
1220
|
}
|
|
1003
1221
|
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
1004
|
-
function renderPersona(companion, surface = 'conversation') {
|
|
1005
|
-
const capabilities = companion.capabilities.length > 0 ? companion.capabilities.join('、') : '由当前 Agent Preset 提供的基础能力';
|
|
1006
|
-
return [
|
|
1007
|
-
`你当前以长期工作伙伴「${companion.name}」的身份工作。`,
|
|
1008
|
-
`角色:${companion.role}`,
|
|
1009
|
-
companion.description ? `定位:${companion.description}` : '',
|
|
1010
|
-
companion.instructions ? `长期行为准则:\n${companion.instructions}` : '',
|
|
1011
|
-
`用户为此伙伴启用的能力范围:${capabilities}。能力标识不等于授权;只能调用当前会话实际提供且已经通过权限校验的工具。`,
|
|
1012
|
-
surface === 'heartbeat'
|
|
1013
|
-
? '这是一轮独立的伙伴心跳,不是用户聊天的延续。只根据本轮真实可读信息行动,最终结果由渠道适配器决定是否发送。'
|
|
1014
|
-
: '这个会话由 DSH 网页与微信私聊渠道共同使用。保持同一上下文,不要假定每条消息都来自微信;渠道回传由适配器负责。回答兼顾网页与移动端阅读,执行外部操作前继续遵守工具自身的授权与审批边界。',
|
|
1015
|
-
].filter(Boolean).join('\n\n');
|
|
1016
|
-
}
|
|
1017
|
-
export function renderToolProtocol() {
|
|
1018
|
-
return [
|
|
1019
|
-
'DSH 工具调用协议(必须遵守):工具 SDK 中出现某项能力,不代表它可以作为顶层函数直接调用。',
|
|
1020
|
-
'先以当前请求真正暴露的顶层工具清单为准。若顶层只提供 `run_code`,则它是唯一允许直接调用的工具;`web_search`、知识库、SSH 等 SDK 能力必须放进 `run_code` 程序,通过生成的 `tools` SDK 按准确签名调用,例如 `await tools.web_search(...)`。绝不要直接发起名为 `web_search` 的顶层工具调用。',
|
|
1021
|
-
'`run_code` 的程序只返回完成当前任务所需的精简结果。需要顺序依赖时逐个 `await`,互不依赖的只读调用才可并行。',
|
|
1022
|
-
'若工具结果提示 “only `run_code` is callable directly”,说明路由方式错误;立即在同一轮改用 `run_code` 重试。只有规范重试也失败时,才向用户说明真正的失败原因。',
|
|
1023
|
-
'若当前请求原生暴露了目标工具,则可以直接调用该原生工具;不要臆造未出现在顶层清单或生成 SDK 中的工具。',
|
|
1024
|
-
'需要把生成的图片或文档交给用户时,最终回复必须用 Markdown 链接明确引用伙伴工作目录中的文件,例如 `[下载文件](/绝对路径/文件.pdf)`;不要只说“文件在这里”。渠道会在确认真实路径位于伙伴工作目录后发送附件。',
|
|
1025
|
-
].join('\n');
|
|
1026
|
-
}
|
|
1027
|
-
function extractAssistantText(agent, fromSeq) {
|
|
1028
|
-
const messages = [];
|
|
1029
|
-
for (const event of agent.session.events) {
|
|
1030
|
-
if (event.seq < fromSeq || event.type !== 'assistant/message' || event.data.interrupted)
|
|
1031
|
-
continue;
|
|
1032
|
-
const text = event.data.message.content
|
|
1033
|
-
.filter((block) => block.type === 'text')
|
|
1034
|
-
.map(block => block.text.trim()).filter(Boolean).join('\n');
|
|
1035
|
-
if (text)
|
|
1036
|
-
messages.push(text);
|
|
1037
|
-
}
|
|
1038
|
-
return messages.join('\n\n').trim();
|
|
1039
|
-
}
|
|
1040
1222
|
function textContent(content) {
|
|
1041
1223
|
return content.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
1042
1224
|
.map(block => block.text.trim()).filter(Boolean).join('\n');
|