@lemoncat7/dsh-partner 1.8.2 → 1.9.2
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 +34 -2
- package/docs/architecture.md +104 -12
- package/lib/agent-runtime.d.ts +15 -2
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +164 -106
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api/features/workspace-api.d.ts.map +1 -1
- package/lib/api/features/workspace-api.js +1 -10
- package/lib/api/features/workspace-api.js.map +1 -1
- package/lib/api.js +21 -44
- package/lib/api.js.map +1 -1
- package/lib/channels/delivery-policy.d.ts +7 -0
- package/lib/channels/delivery-policy.d.ts.map +1 -0
- package/lib/channels/delivery-policy.js +47 -0
- package/lib/channels/delivery-policy.js.map +1 -0
- package/lib/channels/manager.d.ts +1 -2
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +5 -9
- package/lib/channels/manager.js.map +1 -1
- package/lib/client-api.d.ts +1 -0
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +1255 -1100
- package/lib/client.js.map +4 -4
- package/lib/collaboration/composition.d.ts.map +1 -1
- package/lib/collaboration/composition.js +37 -13
- package/lib/collaboration/composition.js.map +1 -1
- package/lib/collaboration/service.d.ts +4 -0
- package/lib/collaboration/service.d.ts.map +1 -1
- package/lib/collaboration/service.js +109 -35
- package/lib/collaboration/service.js.map +1 -1
- package/lib/collaboration/task-dispatch.d.ts +18 -0
- package/lib/collaboration/task-dispatch.d.ts.map +1 -0
- package/lib/collaboration/task-dispatch.js +44 -0
- package/lib/collaboration/task-dispatch.js.map +1 -0
- package/lib/collaboration/task-recovery.d.ts +8 -0
- package/lib/collaboration/task-recovery.d.ts.map +1 -0
- package/lib/collaboration/task-recovery.js +30 -0
- package/lib/collaboration/task-recovery.js.map +1 -0
- package/lib/companions/directory-cleanup.d.ts +4 -0
- package/lib/companions/directory-cleanup.d.ts.map +1 -0
- package/lib/companions/directory-cleanup.js +77 -0
- package/lib/companions/directory-cleanup.js.map +1 -0
- package/lib/companions/management-tool.d.ts +1 -1
- package/lib/companions/management-tool.d.ts.map +1 -1
- package/lib/companions/management-tool.js +1 -1
- package/lib/companions/management-tool.js.map +1 -1
- package/lib/companions/management.d.ts +4 -2
- package/lib/companions/management.d.ts.map +1 -1
- package/lib/companions/management.js +2 -2
- package/lib/companions/management.js.map +1 -1
- package/lib/companions/removal.d.ts +20 -0
- package/lib/companions/removal.d.ts.map +1 -0
- package/lib/companions/removal.js +84 -0
- package/lib/companions/removal.js.map +1 -0
- package/lib/companions/service.d.ts +3 -0
- package/lib/companions/service.d.ts.map +1 -1
- package/lib/companions/service.js +4 -0
- package/lib/companions/service.js.map +1 -1
- package/lib/companions/session-configuration.d.ts +19 -0
- package/lib/companions/session-configuration.d.ts.map +1 -0
- package/lib/companions/session-configuration.js +38 -0
- package/lib/companions/session-configuration.js.map +1 -0
- package/lib/companions/workspace-cleanup.d.ts +4 -0
- package/lib/companions/workspace-cleanup.d.ts.map +1 -0
- package/lib/companions/workspace-cleanup.js +37 -0
- package/lib/companions/workspace-cleanup.js.map +1 -0
- package/lib/daily-review.d.ts +1 -0
- package/lib/daily-review.d.ts.map +1 -1
- package/lib/daily-review.js +3 -0
- package/lib/daily-review.js.map +1 -1
- package/lib/execution/service.d.ts.map +1 -1
- package/lib/execution/service.js +2 -0
- package/lib/execution/service.js.map +1 -1
- package/lib/heartbeat.d.ts +1 -0
- package/lib/heartbeat.d.ts.map +1 -1
- package/lib/heartbeat.js +3 -0
- package/lib/heartbeat.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -12
- package/lib/index.js.map +1 -1
- package/lib/pendant/notice-motion.d.ts.map +1 -1
- package/lib/pendant/notice-motion.js +12 -6
- package/lib/pendant/notice-motion.js.map +1 -1
- package/lib/pendant/sheen.d.ts +1 -1
- package/lib/pendant/sheen.d.ts.map +1 -1
- package/lib/pendant/sheen.js +9 -8
- package/lib/pendant/sheen.js.map +1 -1
- package/lib/pendant/widget.js +1 -1
- package/lib/pendant/widget.js.map +1 -1
- package/lib/pendant-renderer.js +17 -17
- package/lib/skills/builtin.d.ts.map +1 -1
- package/lib/skills/builtin.js +3 -2
- package/lib/skills/builtin.js.map +1 -1
- package/lib/skills/service.js +1 -1
- package/lib/skills/service.js.map +1 -1
- package/lib/skills/task-planning.d.ts +3 -0
- package/lib/skills/task-planning.d.ts.map +1 -0
- package/lib/skills/task-planning.js +31 -0
- package/lib/skills/task-planning.js.map +1 -0
- package/lib/store.d.ts +6 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +12 -0
- package/lib/store.js.map +1 -1
- package/lib/tasks/domain.d.ts +2 -0
- package/lib/tasks/domain.d.ts.map +1 -1
- package/lib/tasks/progress-notifier.d.ts +4 -0
- package/lib/tasks/progress-notifier.d.ts.map +1 -0
- package/lib/tasks/progress-notifier.js +20 -0
- package/lib/tasks/progress-notifier.js.map +1 -0
- package/lib/tasks/service.d.ts.map +1 -1
- package/lib/tasks/service.js +22 -2
- package/lib/tasks/service.js.map +1 -1
- package/lib/ui/capability-editor.d.ts +7 -0
- package/lib/ui/capability-editor.d.ts.map +1 -0
- package/lib/ui/capability-editor.js +85 -0
- package/lib/ui/capability-editor.js.map +1 -0
- package/lib/ui/companion-draft.d.ts +12 -0
- package/lib/ui/companion-draft.d.ts.map +1 -0
- package/lib/ui/companion-draft.js +2 -0
- package/lib/ui/companion-draft.js.map +1 -0
- package/lib/ui/identity-editor.d.ts +11 -0
- package/lib/ui/identity-editor.d.ts.map +1 -0
- package/lib/ui/identity-editor.js +78 -0
- package/lib/ui/identity-editor.js.map +1 -0
- package/lib/ui/task-board-panel.d.ts.map +1 -1
- package/lib/ui/task-board-panel.js +15 -10
- package/lib/ui/task-board-panel.js.map +1 -1
- package/lib/ui/task-board-stage.d.ts +12 -0
- package/lib/ui/task-board-stage.d.ts.map +1 -0
- package/lib/ui/task-board-stage.js +13 -0
- package/lib/ui/task-board-stage.js.map +1 -0
- package/package.json +1 -1
package/lib/agent-runtime.js
CHANGED
|
@@ -3,6 +3,9 @@ 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
5
|
import { createScope, scopeOf, scopeParentOf } from '@deepseek-ai/dsh-scope';
|
|
6
|
+
import { SessionConfigurationIndex } from './companions/session-configuration.js';
|
|
7
|
+
import { removeOwnedWorkspace } from './companions/workspace-cleanup.js';
|
|
8
|
+
import { assertOwnedDirectory, removeOwnedDirectory } from './companions/directory-cleanup.js';
|
|
6
9
|
import { concernObservationPrompt } from './autonomy.js';
|
|
7
10
|
import { boundedConcernCheckMinutes } from './concern-domain.js';
|
|
8
11
|
import { PARTNER_MEDIA_MAX_BYTES, safeMediaName } from './channel-message.js';
|
|
@@ -10,6 +13,7 @@ import { listConcernFileSources } from './concern-sources.js';
|
|
|
10
13
|
import { CONCERN_CREATED_NOTICE, renderConcernCreatedNotice } from './concern-notification.js';
|
|
11
14
|
import { HEARTBEAT_LOCAL_COMMAND, heartbeatLocalCommandTool } from './heartbeat-command.js';
|
|
12
15
|
import { assistantTextAfter, renderPartnerPersona, renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
16
|
+
import { channelReplyTextAfter } from './channels/delivery-policy.js';
|
|
13
17
|
export { renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
14
18
|
const HEARTBEAT_TIMEOUT_MS = 115_000;
|
|
15
19
|
const HEARTBEAT_DISCOVERY_BUDGET_MS = 72_000;
|
|
@@ -54,6 +58,12 @@ export class PartnerAgentRuntime {
|
|
|
54
58
|
restoredCompositions = new Map();
|
|
55
59
|
restoredCompositionJobs = new Map();
|
|
56
60
|
disposeAgentListener;
|
|
61
|
+
disposeConfigurationListener;
|
|
62
|
+
configurations;
|
|
63
|
+
preparedTurns = new WeakMap();
|
|
64
|
+
observationJobs = new Map();
|
|
65
|
+
agentSetupJobs = new Map();
|
|
66
|
+
closed = false;
|
|
57
67
|
questionAnswerer;
|
|
58
68
|
constructor(ctx, store, defaultCwd, memory, reflection, concerns, composer) {
|
|
59
69
|
this.ctx = ctx;
|
|
@@ -63,8 +73,15 @@ export class PartnerAgentRuntime {
|
|
|
63
73
|
this.reflection = reflection;
|
|
64
74
|
this.concerns = concerns;
|
|
65
75
|
this.composer = composer;
|
|
76
|
+
this.configurations = new SessionConfigurationIndex(store);
|
|
77
|
+
this.disposeConfigurationListener = typeof this.ctx.on === 'function'
|
|
78
|
+
? this.ctx.on('agent/pre-step', async ({ agent, turn, signal }, next) => {
|
|
79
|
+
await this.prepareAgentTurn(agent, turn, signal);
|
|
80
|
+
return next();
|
|
81
|
+
}) : () => { };
|
|
66
82
|
this.disposeAgentListener = typeof this.ctx.on === 'function'
|
|
67
83
|
? this.ctx.on('agent/disposed', ({ agent }) => {
|
|
84
|
+
this.preparedTurns.delete(agent);
|
|
68
85
|
const restored = this.restoredCompositions.get(agent.session.id);
|
|
69
86
|
if (restored?.agent !== agent)
|
|
70
87
|
return;
|
|
@@ -145,6 +162,8 @@ export class PartnerAgentRuntime {
|
|
|
145
162
|
return route;
|
|
146
163
|
}
|
|
147
164
|
async ensureLocalSessionRecord(companionId) {
|
|
165
|
+
if (this.store.isCompanionRemoving(companionId))
|
|
166
|
+
throw new Error('伙伴正在删除,不能创建会话');
|
|
148
167
|
const companion = this.store.snapshot().companions.find(item => item.id === companionId);
|
|
149
168
|
if (companion === undefined)
|
|
150
169
|
throw new Error('伙伴身份不存在');
|
|
@@ -164,6 +183,8 @@ export class PartnerAgentRuntime {
|
|
|
164
183
|
lastMessageAt: now,
|
|
165
184
|
};
|
|
166
185
|
await this.store.update(state => {
|
|
186
|
+
if (this.store.isCompanionRemoving(companion.id) || !state.companions.some(item => item.id === companion.id))
|
|
187
|
+
throw new Error('伙伴正在删除或已不存在');
|
|
167
188
|
state.sessions = state.sessions.filter(item => !(item.companionId === companion.id && item.kind === 'local'));
|
|
168
189
|
state.sessions.push(route);
|
|
169
190
|
});
|
|
@@ -191,9 +212,9 @@ export class PartnerAgentRuntime {
|
|
|
191
212
|
const agent = await this.ensureAgent(companion, route);
|
|
192
213
|
const isReviewer = task.status === 'review' && task.reviewerCompanionId === companion.id;
|
|
193
214
|
const instruction = isReviewer
|
|
194
|
-
? '
|
|
215
|
+
? '这是伙伴内部验收,不是用户的新消息。执行伙伴已提交结果,现在默认由你验收。请根据任务要求真实核验已有产出;通过后必须调用 partner_task_board accept,不通过则调用 reject 并写明原因。不要重新执行该任务,不要向用户播报核验过程或 accept 操作;最终结果由系统直接投递。'
|
|
195
216
|
: task.status === 'review'
|
|
196
|
-
? '
|
|
217
|
+
? '这是伙伴内部进度事件。执行结果已提交并等待指定伙伴验收;不要重新执行,也不要向用户播报中间进度。只在看板中推进必要的协作,最终结果由系统直接投递。'
|
|
197
218
|
: task.status === 'done'
|
|
198
219
|
? '这是你创建并分配的看板任务终态事件。不要重新执行已完成的工作。请检查依赖它的后续任务是否解锁,需要时继续在看板上分配。对用户的终态结果由系统直接投递,不要只改写成“已完成”或进度摘要。'
|
|
199
220
|
: '这是你创建并分配的看板任务受阻事件。请保留已有产出、检查是否能调整分工或解锁依赖;需要用户介入时,明确说明阻塞原因与所需动作。';
|
|
@@ -364,6 +385,25 @@ export class PartnerAgentRuntime {
|
|
|
364
385
|
}
|
|
365
386
|
}
|
|
366
387
|
async observeSessionEvent(session, event) {
|
|
388
|
+
if (event.type !== 'turn/end' || event.data.reason.kind !== 'completed')
|
|
389
|
+
return;
|
|
390
|
+
const route = this.store.snapshot().sessions.find(item => item.sessionId === session.id);
|
|
391
|
+
if (!route || this.store.isCompanionRemoving(route.companionId))
|
|
392
|
+
return;
|
|
393
|
+
const id = route.companionId;
|
|
394
|
+
this.observationJobs.set(id, (this.observationJobs.get(id) ?? 0) + 1);
|
|
395
|
+
try {
|
|
396
|
+
await this.processSessionEvent(session, event);
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
const left = (this.observationJobs.get(id) ?? 1) - 1;
|
|
400
|
+
if (left)
|
|
401
|
+
this.observationJobs.set(id, left);
|
|
402
|
+
else
|
|
403
|
+
this.observationJobs.delete(id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async processSessionEvent(session, event) {
|
|
367
407
|
if (event.type !== 'turn/end' || event.data.reason.kind !== 'completed')
|
|
368
408
|
return;
|
|
369
409
|
const route = this.store.snapshot().sessions.find(item => item.sessionId === session.id);
|
|
@@ -476,35 +516,65 @@ export class PartnerAgentRuntime {
|
|
|
476
516
|
await this.releaseCompanion(companionId);
|
|
477
517
|
await this.store.update(state => { state.sessions = state.sessions.filter(item => item.companionId !== companionId); });
|
|
478
518
|
}
|
|
519
|
+
async removeCompanionWorkspace(companionId) {
|
|
520
|
+
const cwd = partnerCwd(this.defaultCwd, companionId);
|
|
521
|
+
const sharedPaths = this.store.snapshot().sessions.filter(route => route.companionId !== companionId).map(route => route.cwd ?? this.defaultCwd);
|
|
522
|
+
await removeOwnedWorkspace(this.ctx.workspaceRegistry, cwd, companionId, sharedPaths);
|
|
523
|
+
this.workspaces.delete(cwd);
|
|
524
|
+
}
|
|
525
|
+
sharedDirectoryPaths(companionId) {
|
|
526
|
+
const state = this.store.snapshot();
|
|
527
|
+
const cwd = resolve(partnerCwd(this.defaultCwd, companionId));
|
|
528
|
+
return [
|
|
529
|
+
...state.companions.filter(item => item.id !== companionId).map(item => partnerCwd(this.defaultCwd, item.id)),
|
|
530
|
+
...state.sessions.filter(route => route.companionId !== companionId).map(route => route.cwd ?? this.defaultCwd),
|
|
531
|
+
...this.ctx.workspaceRegistry.list().filter(item => resolve(item.path) !== cwd).map(item => item.path),
|
|
532
|
+
];
|
|
533
|
+
}
|
|
534
|
+
async validateCompanionDirectory(companionId) {
|
|
535
|
+
await assertOwnedDirectory(this.defaultCwd, companionId, this.sharedDirectoryPaths(companionId));
|
|
536
|
+
}
|
|
537
|
+
async removeCompanionDirectory(companionId) {
|
|
538
|
+
await removeOwnedDirectory(this.defaultCwd, companionId, this.sharedDirectoryPaths(companionId));
|
|
539
|
+
}
|
|
479
540
|
async reloadCompanion(companionId) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
await this.releaseCompanion(companionId);
|
|
485
|
-
// Releasing a restored composition does not close the browser-owned Agent.
|
|
486
|
-
// Recompose it now: staying in the same conversation must not leave it with
|
|
487
|
-
// no partner tools until the user happens to click "open session" again.
|
|
488
|
-
for (const route of activeRoutes) {
|
|
489
|
-
const state = this.store.snapshot();
|
|
490
|
-
const companion = state.companions.find(item => item.id === companionId);
|
|
491
|
-
const currentRoute = state.sessions.find(item => item.id === route.id && item.companionId === companionId);
|
|
492
|
-
if (companion && currentRoute)
|
|
493
|
-
await this.ensureAgent(companion, currentRoute);
|
|
494
|
-
}
|
|
541
|
+
// Compatibility for callers that saved a configuration. The index already
|
|
542
|
+
// observes committed writes; do not tear down or wake any Agent here.
|
|
543
|
+
if (!this.configurations.companion(companionId))
|
|
544
|
+
throw new Error('伙伴不存在');
|
|
495
545
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
546
|
+
/** Awaited by the host before prompt/tool assembly, never from a running tool. */
|
|
547
|
+
async prepareAgentTurn(agent, turn, signal) {
|
|
548
|
+
if (this.closed)
|
|
549
|
+
throw new Error('伙伴运行时已关闭');
|
|
550
|
+
if (this.preparedTurns.get(agent) === turn)
|
|
551
|
+
return;
|
|
552
|
+
const configuration = this.configurations.forSession(agent.session.id);
|
|
553
|
+
if (!configuration)
|
|
554
|
+
return;
|
|
555
|
+
if (this.store.isCompanionRemoving(configuration.companion.id))
|
|
556
|
+
throw new Error('伙伴正在删除,不能启动新轮次');
|
|
557
|
+
signal.throwIfAborted();
|
|
558
|
+
const installed = this.restoredCompositions.get(agent.session.id);
|
|
559
|
+
if (installed?.agent !== agent || installed.revision !== configuration.revision) {
|
|
560
|
+
const profile = await this.memory?.profileSnapshot(configuration.companion.id, memoryScope(configuration.route.channelId, configuration.route.userId)).catch(() => undefined);
|
|
561
|
+
signal.throwIfAborted();
|
|
562
|
+
if (this.closed)
|
|
563
|
+
throw new Error('伙伴运行时已关闭');
|
|
564
|
+
const job = this.composeRestoredAgent(agent, configuration.companion, configuration.route, configuration.revision, profile, signal);
|
|
565
|
+
this.restoredCompositionJobs.set(agent.session.id, job);
|
|
566
|
+
try {
|
|
567
|
+
await job;
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
if (this.restoredCompositionJobs.get(agent.session.id) === job)
|
|
571
|
+
this.restoredCompositionJobs.delete(agent.session.id);
|
|
572
|
+
}
|
|
504
573
|
}
|
|
574
|
+
this.preparedTurns.set(agent, turn);
|
|
505
575
|
}
|
|
506
576
|
isCompanionBusy(companionId) {
|
|
507
|
-
return this.store.snapshot().sessions.some(route => route.companionId === companionId
|
|
577
|
+
return this.observationJobs.has(companionId) || this.agentSetupJobs.has(companionId) || this.store.snapshot().sessions.some(route => route.companionId === companionId
|
|
508
578
|
&& (this.handles.get(route.sessionId)?.agent ?? this.ctx.agents.get(route.sessionId))?.status === 'running');
|
|
509
579
|
}
|
|
510
580
|
async releaseCompanion(companionId) {
|
|
@@ -529,7 +599,10 @@ export class PartnerAgentRuntime {
|
|
|
529
599
|
await this.store.update(state => { state.sessions = state.sessions.filter(item => item.channelId !== channelId); });
|
|
530
600
|
}
|
|
531
601
|
async close() {
|
|
602
|
+
this.closed = true;
|
|
603
|
+
this.disposeConfigurationListener();
|
|
532
604
|
this.disposeAgentListener();
|
|
605
|
+
this.configurations.close();
|
|
533
606
|
await Promise.allSettled(this.restoredCompositionJobs.values());
|
|
534
607
|
for (const restored of this.restoredCompositions.values())
|
|
535
608
|
restored.dispose();
|
|
@@ -545,6 +618,8 @@ export class PartnerAgentRuntime {
|
|
|
545
618
|
async releaseRestoredComposition(sessionId) {
|
|
546
619
|
await this.restoredCompositionJobs.get(sessionId)?.catch(() => { });
|
|
547
620
|
const restored = this.restoredCompositions.get(sessionId);
|
|
621
|
+
if (restored)
|
|
622
|
+
this.preparedTurns.delete(restored.agent);
|
|
548
623
|
restored?.dispose();
|
|
549
624
|
this.restoredCompositions.delete(sessionId);
|
|
550
625
|
}
|
|
@@ -575,7 +650,7 @@ export class PartnerAgentRuntime {
|
|
|
575
650
|
source: { kind: 'user' },
|
|
576
651
|
}));
|
|
577
652
|
await agent.whenIdle();
|
|
578
|
-
const response =
|
|
653
|
+
const response = channelReplyTextAfter(agent.session.snapshotEvents(), startSeq);
|
|
579
654
|
await this.store.update(state => {
|
|
580
655
|
const target = state.sessions.find(item => item.id === session.id);
|
|
581
656
|
if (target)
|
|
@@ -635,6 +710,22 @@ export class PartnerAgentRuntime {
|
|
|
635
710
|
return session;
|
|
636
711
|
}
|
|
637
712
|
async ensureAgent(companion, route) {
|
|
713
|
+
if (this.store.isCompanionRemoving(companion.id) || !this.store.snapshot().companions.some(item => item.id === companion.id))
|
|
714
|
+
throw new Error('伙伴正在删除或已不存在');
|
|
715
|
+
const id = companion.id;
|
|
716
|
+
this.agentSetupJobs.set(id, (this.agentSetupJobs.get(id) ?? 0) + 1);
|
|
717
|
+
try {
|
|
718
|
+
return await this.ensureAgentReady(companion, route);
|
|
719
|
+
}
|
|
720
|
+
finally {
|
|
721
|
+
const left = (this.agentSetupJobs.get(id) ?? 1) - 1;
|
|
722
|
+
if (left)
|
|
723
|
+
this.agentSetupJobs.set(id, left);
|
|
724
|
+
else
|
|
725
|
+
this.agentSetupJobs.delete(id);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async ensureAgentReady(companion, route) {
|
|
638
729
|
const cwd = route.cwd ?? this.defaultCwd;
|
|
639
730
|
await mkdir(cwd, { recursive: true, mode: 0o700 });
|
|
640
731
|
const profile = await this.memory?.profileSnapshot(companion.id, memoryScope(route.channelId, route.userId)).catch(() => undefined);
|
|
@@ -647,7 +738,6 @@ export class PartnerAgentRuntime {
|
|
|
647
738
|
}
|
|
648
739
|
const live = this.ctx.agents.get(route.sessionId);
|
|
649
740
|
if (live !== undefined) {
|
|
650
|
-
await this.ensureRestoredComposition(live, companion, route, profile);
|
|
651
741
|
await this.attachSession(companion, route);
|
|
652
742
|
if (profile)
|
|
653
743
|
this.injectProfileUpdate(live, route.sessionId, profile);
|
|
@@ -659,24 +749,8 @@ export class PartnerAgentRuntime {
|
|
|
659
749
|
if (presets === undefined)
|
|
660
750
|
throw new Error('伙伴会话缺少 Agent Presets 服务');
|
|
661
751
|
await presets.mount(agentCtx, companion.presetId);
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
order: -10,
|
|
665
|
-
text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation'),
|
|
666
|
-
});
|
|
667
|
-
agentCtx.systemPrompt.section({
|
|
668
|
-
name: 'partner-tool-routing',
|
|
669
|
-
order: -9,
|
|
670
|
-
text: renderToolProtocol(),
|
|
671
|
-
});
|
|
672
|
-
if (profile && profile.entries.length > 0)
|
|
673
|
-
agentCtx.systemPrompt.section({
|
|
674
|
-
name: 'partner-user-profile',
|
|
675
|
-
order: -8,
|
|
676
|
-
text: renderProfile(profile),
|
|
677
|
-
});
|
|
678
|
-
this.questionAnswerer?.(agentCtx, route);
|
|
679
|
-
await this.composer?.compose(agentCtx, companion);
|
|
752
|
+
// Partner composition is installed at the same awaited pre-step boundary
|
|
753
|
+
// for factory-created and native browser-resumed Agents alike.
|
|
680
754
|
};
|
|
681
755
|
let handle;
|
|
682
756
|
try {
|
|
@@ -703,69 +777,53 @@ export class PartnerAgentRuntime {
|
|
|
703
777
|
await this.attachSession(companion, route);
|
|
704
778
|
return handle.agent;
|
|
705
779
|
}
|
|
706
|
-
async
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (this.restoredCompositionJobs.get(route.sessionId) === job)
|
|
720
|
-
this.restoredCompositionJobs.delete(route.sessionId);
|
|
780
|
+
async composeRestoredAgent(agent, companion, route, revision, profile, signal) {
|
|
781
|
+
// Already inside the driver's awaited pre-step hook. Waiting for idle or
|
|
782
|
+
// runMaintenance here would deadlock the very turn we are preparing.
|
|
783
|
+
signal.throwIfAborted();
|
|
784
|
+
const agentCtx = agent.ctx;
|
|
785
|
+
const key = scopeOf(agentCtx);
|
|
786
|
+
if (key === undefined)
|
|
787
|
+
throw new Error('伙伴会话缺少 Agent 作用域');
|
|
788
|
+
if (scopeParentOf(key) === undefined) {
|
|
789
|
+
const presets = agentCtx.get('agentPresets');
|
|
790
|
+
if (presets === undefined)
|
|
791
|
+
throw new Error('伙伴会话缺少 Agent Presets 服务');
|
|
792
|
+
await presets.mount(agentCtx, companion.presetId);
|
|
721
793
|
}
|
|
722
|
-
}
|
|
723
|
-
async composeRestoredAgent(agent, companion, route, profile) {
|
|
724
|
-
await agent.whenIdle();
|
|
725
|
-
let dispose;
|
|
726
|
-
await agent.runMaintenance(async (signal) => {
|
|
727
|
-
if (signal.aborted)
|
|
728
|
-
throw signal.reason;
|
|
729
|
-
const agentCtx = agent.ctx;
|
|
730
|
-
const key = scopeOf(agentCtx);
|
|
731
|
-
if (key === undefined)
|
|
732
|
-
throw new Error('伙伴会话缺少 Agent 作用域');
|
|
733
|
-
if (scopeParentOf(key) === undefined) {
|
|
734
|
-
const presets = agentCtx.get('agentPresets');
|
|
735
|
-
if (presets === undefined)
|
|
736
|
-
throw new Error('伙伴会话缺少 Agent Presets 服务');
|
|
737
|
-
await presets.mount(agentCtx, companion.presetId);
|
|
738
|
-
}
|
|
739
|
-
const disposers = [];
|
|
740
|
-
try {
|
|
741
|
-
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-identity', order: -10, text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation') }));
|
|
742
|
-
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-tool-routing', order: -9, text: renderToolProtocol() }));
|
|
743
|
-
if (profile && profile.entries.length > 0)
|
|
744
|
-
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-user-profile', order: -8, text: renderProfile(profile) }));
|
|
745
|
-
if (this.questionAnswerer)
|
|
746
|
-
disposers.push(this.questionAnswerer(agentCtx, route));
|
|
747
|
-
if (this.composer)
|
|
748
|
-
disposers.push(await this.composer.compose(agentCtx, companion));
|
|
749
|
-
}
|
|
750
|
-
catch (error) {
|
|
751
|
-
for (let index = disposers.length - 1; index >= 0; index -= 1)
|
|
752
|
-
disposers[index]?.();
|
|
753
|
-
throw error;
|
|
754
|
-
}
|
|
755
|
-
let disposed = false;
|
|
756
|
-
dispose = () => {
|
|
757
|
-
if (disposed)
|
|
758
|
-
return;
|
|
759
|
-
disposed = true;
|
|
760
|
-
for (let index = disposers.length - 1; index >= 0; index -= 1)
|
|
761
|
-
disposers[index]?.();
|
|
762
|
-
};
|
|
763
|
-
});
|
|
764
|
-
if (dispose === undefined)
|
|
765
|
-
throw new Error('伙伴会话恢复组合未完成');
|
|
766
794
|
const previous = this.restoredCompositions.get(route.sessionId);
|
|
767
795
|
previous?.dispose();
|
|
768
|
-
this.restoredCompositions.
|
|
796
|
+
this.restoredCompositions.delete(route.sessionId);
|
|
797
|
+
const disposers = [];
|
|
798
|
+
try {
|
|
799
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-identity', order: -10, text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation') }));
|
|
800
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-tool-routing', order: -9, text: renderToolProtocol() }));
|
|
801
|
+
if (profile && profile.entries.length > 0)
|
|
802
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-user-profile', order: -8, text: renderProfile(profile) }));
|
|
803
|
+
if (this.questionAnswerer)
|
|
804
|
+
disposers.push(this.questionAnswerer(agentCtx, route));
|
|
805
|
+
if (this.composer)
|
|
806
|
+
disposers.push(await this.composer.compose(agentCtx, companion));
|
|
807
|
+
signal.throwIfAborted();
|
|
808
|
+
if (this.closed)
|
|
809
|
+
throw new Error('伙伴运行时已关闭');
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
for (let index = disposers.length - 1; index >= 0; index -= 1)
|
|
813
|
+
disposers[index]?.();
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
let disposed = false;
|
|
817
|
+
const dispose = () => {
|
|
818
|
+
if (disposed)
|
|
819
|
+
return;
|
|
820
|
+
disposed = true;
|
|
821
|
+
for (let index = disposers.length - 1; index >= 0; index -= 1)
|
|
822
|
+
disposers[index]?.();
|
|
823
|
+
};
|
|
824
|
+
// Keep the captured revision: a concurrent save must be applied next turn,
|
|
825
|
+
// never incorrectly recorded as already installed by this composition.
|
|
826
|
+
this.restoredCompositions.set(route.sessionId, { agent, dispose, revision });
|
|
769
827
|
}
|
|
770
828
|
injectProfileUpdate(agent, sessionId, profile) {
|
|
771
829
|
const previous = this.profileVersions.get(sessionId);
|