@lemoncat7/dsh-partner 2.21.5 → 2.21.13
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 -1
- package/docs/THIRD_PARTY_LICENSES.md +1 -1
- package/docs/conversation-autonomy.md +27 -0
- package/docs/releases/2.21.10.md +5 -0
- package/docs/releases/2.21.11.md +7 -0
- package/docs/releases/2.21.12.md +5 -0
- package/docs/releases/2.21.13.md +13 -0
- package/docs/releases/2.21.6.md +6 -0
- package/docs/releases/2.21.7.md +11 -0
- package/docs/releases/2.21.8.md +9 -0
- package/docs/releases/2.21.9.md +6 -0
- package/lib/agent-runtime.d.ts +10 -2
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +116 -53
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +2 -1
- package/lib/api.js.map +1 -1
- package/lib/channels/authored-progress.d.ts +16 -0
- package/lib/channels/authored-progress.d.ts.map +1 -0
- package/lib/channels/authored-progress.js +38 -0
- package/lib/channels/authored-progress.js.map +1 -0
- package/lib/channels/direct/message-format.js +127 -127
- package/lib/channels/direct/message-format.js.map +1 -1
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +9 -6
- package/lib/channels/manager.js.map +1 -1
- package/lib/channels/qq/actions.js +1 -1
- package/lib/channels/qq/actions.js.map +1 -1
- package/lib/channels/qq/attention-classifier.js +5 -5
- package/lib/channels/qq/attention-classifier.js.map +1 -1
- package/lib/channels/qq/authority.d.ts +2 -1
- package/lib/channels/qq/authority.d.ts.map +1 -1
- package/lib/channels/qq/authority.js +4 -4
- package/lib/channels/qq/authority.js.map +1 -1
- package/lib/channels/qq/identity-binding.d.ts +1 -1
- package/lib/channels/qq/identity-binding.d.ts.map +1 -1
- package/lib/channels/qq/identity-binding.js +5 -3
- package/lib/channels/qq/identity-binding.js.map +1 -1
- package/lib/channels/qq/reply-tool.d.ts.map +1 -1
- package/lib/channels/qq/reply-tool.js +15 -4
- package/lib/channels/qq/reply-tool.js.map +1 -1
- package/lib/channels/qq/transport.js +1 -1
- package/lib/channels/qq/transport.js.map +1 -1
- package/lib/character/activity-tool.d.ts +3 -0
- package/lib/character/activity-tool.d.ts.map +1 -1
- package/lib/character/activity-tool.js +8 -1
- package/lib/character/activity-tool.js.map +1 -1
- package/lib/character/affection.d.ts +2 -2
- package/lib/character/conversation-behavior.d.ts +5 -3
- package/lib/character/conversation-behavior.d.ts.map +1 -1
- package/lib/character/conversation-behavior.js +29 -22
- package/lib/character/conversation-behavior.js.map +1 -1
- package/lib/character/fatigue.d.ts +15 -0
- package/lib/character/fatigue.d.ts.map +1 -0
- package/lib/character/fatigue.js +37 -0
- package/lib/character/fatigue.js.map +1 -0
- package/lib/character/identity-guide.d.ts +38 -1
- package/lib/character/identity-guide.d.ts.map +1 -1
- package/lib/character/identity-guide.js +7 -1
- package/lib/character/identity-guide.js.map +1 -1
- package/lib/character/idle-contact.d.ts +1 -1
- package/lib/character/idle-contact.d.ts.map +1 -1
- package/lib/character/idle-contact.js +25 -2
- package/lib/character/idle-contact.js.map +1 -1
- package/lib/character/persona-templates.d.ts +23 -0
- package/lib/character/persona-templates.d.ts.map +1 -0
- package/lib/character/persona-templates.js +33 -0
- package/lib/character/persona-templates.js.map +1 -0
- package/lib/character/post-reply.js +2 -2
- package/lib/character/post-reply.js.map +1 -1
- package/lib/character/role-reference-data.d.ts.map +1 -0
- package/lib/character/role-reference-data.js.map +1 -0
- package/lib/character/sleep-service.d.ts +10 -1
- package/lib/character/sleep-service.d.ts.map +1 -1
- package/lib/character/sleep-service.js +156 -75
- package/lib/character/sleep-service.js.map +1 -1
- package/lib/character/sleep.d.ts +25 -3
- package/lib/character/sleep.d.ts.map +1 -1
- package/lib/character/sleep.js +15 -6
- package/lib/character/sleep.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +3464 -3321
- package/lib/client.js.map +4 -4
- package/lib/collaboration/tool-discovery.d.ts +1 -1
- package/lib/collaboration/tool-discovery.d.ts.map +1 -1
- package/lib/collaboration/tool-discovery.js +14 -6
- package/lib/collaboration/tool-discovery.js.map +1 -1
- package/lib/companions/identity-tool.d.ts +1 -1
- package/lib/companions/identity-tool.d.ts.map +1 -1
- package/lib/companions/identity-tool.js +9 -7
- package/lib/companions/identity-tool.js.map +1 -1
- 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 +3 -2
- package/lib/companions/management-tool.js.map +1 -1
- package/lib/companions/management.d.ts +28 -1
- package/lib/companions/management.d.ts.map +1 -1
- package/lib/companions/management.js +3 -2
- package/lib/companions/management.js.map +1 -1
- package/lib/core/semaphore.d.ts.map +1 -1
- package/lib/core/semaphore.js +6 -3
- package/lib/core/semaphore.js.map +1 -1
- package/lib/execution/reply-progress.d.ts +3 -1
- package/lib/execution/reply-progress.d.ts.map +1 -1
- package/lib/execution/reply-progress.js +16 -0
- package/lib/execution/reply-progress.js.map +1 -1
- package/lib/execution/task-preamble.d.ts +15 -0
- package/lib/execution/task-preamble.d.ts.map +1 -0
- package/lib/execution/task-preamble.js +107 -0
- package/lib/execution/task-preamble.js.map +1 -0
- package/lib/execution/tool-access-adapter.d.ts +3 -0
- package/lib/execution/tool-access-adapter.d.ts.map +1 -1
- package/lib/execution/tool-access-adapter.js.map +1 -1
- package/lib/index.js +2 -3
- package/lib/index.js.map +1 -1
- package/lib/memory-admission.d.ts +5 -0
- package/lib/memory-admission.d.ts.map +1 -0
- package/lib/memory-admission.js +20 -0
- package/lib/memory-admission.js.map +1 -0
- package/lib/memory-reflection.js +1 -1
- package/lib/ui/channels-panel.d.ts.map +1 -1
- package/lib/ui/channels-panel.js +2 -1
- package/lib/ui/channels-panel.js.map +1 -1
- package/lib/ui/character-editor.d.ts +1 -1
- package/lib/ui/character-editor.d.ts.map +1 -1
- package/lib/ui/character-editor.js +2 -3
- package/lib/ui/character-editor.js.map +1 -1
- package/lib/ui/companion-deletion.js +1 -1
- package/lib/ui/companion-deletion.js.map +1 -1
- package/lib/ui/companion-vitals.d.ts +6 -0
- package/lib/ui/companion-vitals.d.ts.map +1 -0
- package/lib/ui/companion-vitals.js +44 -0
- package/lib/ui/companion-vitals.js.map +1 -0
- package/lib/ui/identity-editor.d.ts.map +1 -1
- package/lib/ui/identity-editor.js +2 -1
- package/lib/ui/identity-editor.js.map +1 -1
- package/lib/ui/persona-template.d.ts +5 -0
- package/lib/ui/persona-template.d.ts.map +1 -0
- package/lib/ui/persona-template.js +7 -0
- package/lib/ui/persona-template.js.map +1 -0
- package/lib/ui/push-draft.d.ts +2 -2
- package/lib/ui/qq-channel-settings.d.ts.map +1 -1
- package/lib/ui/qq-channel-settings.js +2 -1
- package/lib/ui/qq-channel-settings.js.map +1 -1
- package/lib/ui/qq-connection-guide.d.ts +2 -0
- package/lib/ui/qq-connection-guide.d.ts.map +1 -0
- package/lib/ui/qq-connection-guide.js +5 -0
- package/lib/ui/qq-connection-guide.js.map +1 -0
- package/lib/ui/role-reference.d.ts.map +1 -1
- package/lib/ui/role-reference.js +3 -2
- package/lib/ui/role-reference.js.map +1 -1
- package/lib/ui/sleep-editor.d.ts.map +1 -1
- package/lib/ui/sleep-editor.js +15 -5
- package/lib/ui/sleep-editor.js.map +1 -1
- package/package.json +11 -2
- package/lib/ui/role-reference-data.d.ts.map +0 -1
- package/lib/ui/role-reference-data.js.map +0 -1
- /package/lib/{ui → character}/role-reference-data.d.ts +0 -0
- /package/lib/{ui → character}/role-reference-data.js +0 -0
package/lib/agent-runtime.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { isActivitySilenceRequested } from './character/activity-tool.js';
|
|
2
|
+
import { installTaskPreamble } from './execution/task-preamble.js';
|
|
3
|
+
import { AsyncSemaphore } from './core/semaphore.js';
|
|
4
|
+
import { automaticMemoryAllowed } from './memory-admission.js';
|
|
5
|
+
import { personaReferenceRequested, renderPersonaReference } from './character/identity-guide.js';
|
|
1
6
|
import { activityPolicyCurrent, postReplyAllowed, postReplyCurrent, postReplyProbability, POST_REPLY_PROMPT } from './character/post-reply.js';
|
|
2
7
|
import { PARTNER_TOOL_ENTRIES } from './collaboration/tool-discovery.js';
|
|
3
8
|
import { setReplyTiming } from './channels/qq/latency.js';
|
|
@@ -7,6 +12,7 @@ import { affectionEnabled, awardAffection, renderAffection } from './character/a
|
|
|
7
12
|
import { affectionTurnEligible, currentAffectionAudience } from './character/affection-context.js';
|
|
8
13
|
import { currentQqIdentityRelation, renderQqIdentityRelation } from './channels/qq/identity-binding.js';
|
|
9
14
|
import { qqSettings, qqSessionTarget } from './channels/qq/settings.js';
|
|
15
|
+
import { fatigueValue } from './character/fatigue.js';
|
|
10
16
|
import { renderSleepContext, isSleeping } from './character/sleep.js';
|
|
11
17
|
import { questionOrigin } from './channels/question-origin.js';
|
|
12
18
|
import { renderCharacterContext } from './character/domain.js';
|
|
@@ -70,6 +76,7 @@ export class PartnerAgentRuntime {
|
|
|
70
76
|
latestPostReplyInput = new Map();
|
|
71
77
|
handles = new Map();
|
|
72
78
|
queues = new Map();
|
|
79
|
+
replySlots = new AsyncSemaphore(6);
|
|
73
80
|
heartbeatQueues = new Map();
|
|
74
81
|
taskQueues = new Map();
|
|
75
82
|
steeringQueues = new Map();
|
|
@@ -113,7 +120,7 @@ export class PartnerAgentRuntime {
|
|
|
113
120
|
const configuration = this.configurations.forSession(context.agent.session.id);
|
|
114
121
|
if (configuration?.companion.toolDiscovery)
|
|
115
122
|
return { ...assembly, tools: assembly.tools.filter(tool => PARTNER_TOOL_ENTRIES.some(name => name === tool.name)) };
|
|
116
|
-
const access = resolveToolAccess({ agent: context.agent, state: this.store.snapshot() });
|
|
123
|
+
const access = resolveToolAccess({ agent: context.agent, state: this.store.snapshot(), ...(this.admittedInputs.get(context.agent)?.length ? { events: this.inputEvents(context.agent) } : {}) });
|
|
117
124
|
return { ...assembly, tools: !access.sendTools ? [] : access.allowedTools ? assembly.tools.filter(tool => access.allowedTools.includes(tool.name)) : assembly.tools };
|
|
118
125
|
};
|
|
119
126
|
if (this.rebuildingAssemblies.has(context))
|
|
@@ -131,15 +138,33 @@ export class PartnerAgentRuntime {
|
|
|
131
138
|
}
|
|
132
139
|
}) : () => { };
|
|
133
140
|
this.disposeConfigurationListener = typeof this.ctx.on === 'function'
|
|
134
|
-
? this.ctx.on('agent/pre-step', async ({ agent, turn, signal }, next) => {
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
? this.ctx.on('agent/pre-step', async ({ agent, turn, signal, messages }, next) => {
|
|
142
|
+
try {
|
|
143
|
+
await this.prepareAgentTurn(agent, turn, signal, messages);
|
|
144
|
+
const decision = await next();
|
|
145
|
+
if (decision.kind === 'reject') {
|
|
146
|
+
this.admittedInputs.delete(agent);
|
|
147
|
+
this.contextRevisions.delete(agent);
|
|
148
|
+
}
|
|
149
|
+
else if (decision.kind === 'enter') {
|
|
150
|
+
this.admittedInputs.set(agent, decision.messages);
|
|
151
|
+
await this.sleepService?.acceptInput(agent.session.id, decision.messages);
|
|
152
|
+
await this.prepareConversationContext(agent, signal);
|
|
153
|
+
}
|
|
154
|
+
return decision;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
this.admittedInputs.delete(agent);
|
|
158
|
+
this.contextRevisions.delete(agent);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
137
161
|
}) : () => { };
|
|
138
162
|
this.disposeAgentListener = typeof this.ctx.on === 'function'
|
|
139
163
|
? this.ctx.on('agent/disposed', ({ agent }) => {
|
|
140
164
|
this.replyPresentations.get(agent)?.();
|
|
141
165
|
this.replyPresentations.delete(agent);
|
|
142
166
|
this.preparedTurns.delete(agent);
|
|
167
|
+
this.admittedInputs.delete(agent);
|
|
143
168
|
this.characterContexts.get(agent.session.id)?.();
|
|
144
169
|
this.characterContexts.delete(agent.session.id);
|
|
145
170
|
this.contextRevisions.delete(agent);
|
|
@@ -151,12 +176,14 @@ export class PartnerAgentRuntime {
|
|
|
151
176
|
})
|
|
152
177
|
: () => { };
|
|
153
178
|
}
|
|
179
|
+
sleepService;
|
|
180
|
+
setSleepService(service) { this.sleepService = service; }
|
|
154
181
|
setQuestionAnswerer(answerer) {
|
|
155
182
|
this.questionAnswerer = answerer;
|
|
156
183
|
}
|
|
157
184
|
/** Cancel pending/claimed continuations as soon as another real input is admitted. */
|
|
158
|
-
async cancelPostReply(companionId, keepUserMessageId) {
|
|
159
|
-
const needsCancel = (r) => r.companionId === companionId && r.postReplyOpportunity
|
|
185
|
+
async cancelPostReply(companionId, keepUserMessageId, sessionId) {
|
|
186
|
+
const needsCancel = (r) => r.companionId === companionId && (!sessionId || r.sessionId === sessionId) && r.postReplyOpportunity
|
|
160
187
|
&& ['pending', 'claimed'].includes(r.postReplyOpportunity.status) && r.postReplyOpportunity.userMessageId !== keepUserMessageId;
|
|
161
188
|
if (!this.store.snapshot().sessions.some(needsCancel))
|
|
162
189
|
return;
|
|
@@ -180,21 +207,21 @@ export class PartnerAgentRuntime {
|
|
|
180
207
|
const companion = state.companions.find(c => c.id === route?.companionId);
|
|
181
208
|
if (!route || !companion || !postReplyAllowed(state, companion, route) || this.closed || this.isArchived(route) || this.store.isCompanionRemoving(companion.id))
|
|
182
209
|
return;
|
|
183
|
-
const key =
|
|
210
|
+
const key = route.sessionId;
|
|
184
211
|
if (this.queues.has(key) || this.steeringQueues.has(key))
|
|
185
212
|
return;
|
|
186
213
|
const agent = await this.ensureAgent(companion, route);
|
|
187
|
-
if (agent.status !== 'idle' || (this.latestPostReplyInput.has(
|
|
214
|
+
if (agent.status !== 'idle' || (this.latestPostReplyInput.has(route.sessionId) && this.latestPostReplyInput.get(route.sessionId) !== source.userMessageId) || lastIdleContactUser(agent.session.snapshotEvents())?.id !== source.userMessageId)
|
|
188
215
|
return;
|
|
189
216
|
await this.store.update(draft => {
|
|
190
217
|
const current = draft.companions.find(c => c.id === companion.id), target = draft.sessions.find(r => r.id === route.id);
|
|
191
218
|
if (!current || !target || target.sessionId !== source.sessionId || !postReplyAllowed(draft, current, target)
|
|
192
219
|
|| target.postReplyOpportunity?.userMessageId === source.userMessageId || agent.status !== 'idle'
|
|
193
|
-
|| this.queues.has(key) || this.steeringQueues.has(key) || (this.latestPostReplyInput.has(
|
|
220
|
+
|| this.queues.has(key) || this.steeringQueues.has(key) || (this.latestPostReplyInput.has(route.sessionId) && this.latestPostReplyInput.get(route.sessionId) !== source.userMessageId) || lastIdleContactUser(agent.session.snapshotEvents())?.id !== source.userMessageId)
|
|
194
221
|
return;
|
|
195
|
-
//
|
|
222
|
+
// Shared history has one opportunity; unrelated conversations remain independent.
|
|
196
223
|
for (const other of draft.sessions)
|
|
197
|
-
if (other.
|
|
224
|
+
if (other.sessionId === route.sessionId && other.postReplyOpportunity?.status === 'pending')
|
|
198
225
|
other.postReplyOpportunity.status = 'cancelled';
|
|
199
226
|
const now = Date.now(), probability = postReplyProbability(current);
|
|
200
227
|
target.postReplyOpportunity = { userMessageId: source.userMessageId, sessionId: source.sessionId, policyRevision: current.updatedAt,
|
|
@@ -205,16 +232,17 @@ export class PartnerAgentRuntime {
|
|
|
205
232
|
/** Use the existing timer and real conversation; never impersonate user input. */
|
|
206
233
|
async idleContact(companionId, timeZone = 'Asia/Shanghai') {
|
|
207
234
|
const key = `companion:${companionId}`;
|
|
208
|
-
if (this.closed || this.
|
|
235
|
+
if (this.closed || this.taskQueues.has(key) || this.heartbeatQueues.has(key) || this.store.isCompanionRemoving(companionId))
|
|
209
236
|
return;
|
|
210
237
|
const state = this.store.snapshot(), companion = state.companions.find(c => c.id === companionId);
|
|
211
|
-
const
|
|
238
|
+
const activity = this.companionActivity(companionId);
|
|
239
|
+
const pendingRoute = state.sessions.filter(r => r.companionId === companionId && r.postReplyOpportunity?.status === 'pending' && !this.queues.has(r.sessionId) && !this.steeringQueues.has(r.sessionId) && !activity.some(a => a.sessionId === r.sessionId)).sort((a, b) => a.postReplyOpportunity.dueAt - b.postReplyOpportunity.dueAt)[0];
|
|
212
240
|
let route = pendingRoute ?? state.sessions.find(r => r.id === companion?.idleContact?.routeId && r.companionId === companionId);
|
|
213
241
|
const continuation = !!pendingRoute;
|
|
214
242
|
if (pendingRoute && companion) {
|
|
215
243
|
const pending = pendingRoute.postReplyOpportunity;
|
|
216
244
|
if (!postReplyCurrent(state, companion, pendingRoute) || Date.now() > pending.expiresAt) {
|
|
217
|
-
await this.cancelPostReply(companionId);
|
|
245
|
+
await this.cancelPostReply(companionId, undefined, route?.sessionId);
|
|
218
246
|
return;
|
|
219
247
|
}
|
|
220
248
|
if (Date.now() < pending.dueAt)
|
|
@@ -226,7 +254,7 @@ export class PartnerAgentRuntime {
|
|
|
226
254
|
if (channel?.platform === 'qq' && route.qqSessionMode !== (qqSettings(channel.qq).mainSessionPeers.includes(qqSessionTarget(route.userId)) ? 'main' : 'separate'))
|
|
227
255
|
route = await this.ensureSession(companion, route.channelId, route.userId);
|
|
228
256
|
const agent = await this.ensureAgent(companion, route);
|
|
229
|
-
if (agent.status !== 'idle' || this.companionActivity(companionId).some(a => a.
|
|
257
|
+
if (agent.status !== 'idle' || this.companionActivity(companionId).some(a => a.sessionId === route.sessionId) || this.queues.has(route.sessionId) || this.steeringQueues.has(route.sessionId) || this.taskQueues.has(key))
|
|
230
258
|
return;
|
|
231
259
|
const user = lastIdleContactUser(agent.session.snapshotEvents());
|
|
232
260
|
const completed = [...agent.session.snapshotEvents()].reverse().find(e => e.type === 'turn/end');
|
|
@@ -234,14 +262,14 @@ export class PartnerAgentRuntime {
|
|
|
234
262
|
user.at = Math.max(user.at, completed.time);
|
|
235
263
|
if (continuation) {
|
|
236
264
|
if (user?.id !== route.postReplyOpportunity?.userMessageId) {
|
|
237
|
-
await this.cancelPostReply(companionId);
|
|
265
|
+
await this.cancelPostReply(companionId, undefined, route?.sessionId);
|
|
238
266
|
return;
|
|
239
267
|
}
|
|
240
268
|
}
|
|
241
269
|
else if (!idleContactDue(this.store.snapshot(), companion, user, Date.now(), timeZone))
|
|
242
270
|
return;
|
|
243
271
|
const stableSeq = agent.session.seq;
|
|
244
|
-
const message = createUserMessage({ content: [{ type: 'text', text: idleActivityPrompt(this.store.snapshot(), companion) + (continuation ? '\n' + POST_REPLY_PROMPT : '')
|
|
272
|
+
const message = createUserMessage({ content: [{ type: 'text', text: idleActivityPrompt(this.store.snapshot(), companion, agent.session.snapshotEvents()) + (continuation ? '\n' + POST_REPLY_PROMPT : '')
|
|
245
273
|
}], source: { kind: 'plugin:@lemoncat7/dsh-partner', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: IDLE_CONTACT_NOTICE } });
|
|
246
274
|
let claimed = false;
|
|
247
275
|
await this.store.update(draft => {
|
|
@@ -260,7 +288,7 @@ export class PartnerAgentRuntime {
|
|
|
260
288
|
claimed = true;
|
|
261
289
|
});
|
|
262
290
|
// Claim before generation survives restart. Don't replay uncertain sends or steal active turns.
|
|
263
|
-
if (!claimed || this.closed || this.store.isCompanionRemoving(companionId) || agent.status !== 'idle' || agent.session.seq !== stableSeq || this.queues.has(
|
|
291
|
+
if (!claimed || this.closed || this.store.isCompanionRemoving(companionId) || agent.status !== 'idle' || agent.session.seq !== stableSeq || this.queues.has(route.sessionId) || this.taskQueues.has(key))
|
|
264
292
|
return;
|
|
265
293
|
const currentState = this.store.snapshot(), current = currentState.companions.find(c => c.id === companionId), currentRoute = currentState.sessions.find(r => r.id === route.id);
|
|
266
294
|
if (!current || !currentRoute || !activityPolicyCurrent(currentState, current, currentRoute) || isSleeping(current) || current.updatedAt !== companion.updatedAt || this.isArchived(route))
|
|
@@ -276,16 +304,25 @@ export class PartnerAgentRuntime {
|
|
|
276
304
|
if (channel?.platform === 'qq' && route.qqSessionMode !== (qqSettings(channel.qq).mainSessionPeers.includes(qqSessionTarget(route.userId)) ? 'main' : 'separate'))
|
|
277
305
|
return false;
|
|
278
306
|
const agent = await this.ensureAgent(companion, route);
|
|
279
|
-
|
|
307
|
+
const events = agent.session.snapshotEvents();
|
|
308
|
+
const noticeIndex = events.findIndex(e => e.type === 'user/message' && e.data.id === origin.messageId);
|
|
309
|
+
if (noticeIndex >= 0) {
|
|
310
|
+
const tail = events.slice(noticeIndex + 1);
|
|
311
|
+
const boundary = tail.findIndex(e => e.type === 'turn/start' || e.type === 'turn/end' || e.type === 'user/message' && e.data.source.kind === 'user');
|
|
312
|
+
if (tail.slice(0, boundary < 0 ? tail.length : boundary).some(isActivitySilenceRequested))
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
return lastIdleContactUser(events)?.id === origin.userMessageId;
|
|
280
316
|
}
|
|
281
317
|
async reply(companion, channelId, userId, message, progress) {
|
|
282
|
-
const
|
|
283
|
-
|
|
318
|
+
const route = await this.ensureSession(companion, channelId, userId, message.sessionTitle);
|
|
319
|
+
const key = route.sessionId;
|
|
320
|
+
await this.cancelPostReply(companion.id, undefined, route.sessionId);
|
|
284
321
|
const queuedAt = Date.now();
|
|
285
322
|
const previous = this.queues.get(key) ?? Promise.resolve();
|
|
286
323
|
let output = { text: '', attachments: [] };
|
|
287
324
|
reportProgress(progress, 'queued');
|
|
288
|
-
const current = previous.catch(() => { }).then(async () => { output = await this.drive(companion, channelId, userId, message, progress, queuedAt); });
|
|
325
|
+
const current = previous.catch(() => { }).then(async () => { output = await this.replySlots.use(() => this.drive(companion, channelId, userId, message, progress, queuedAt, route)); });
|
|
289
326
|
this.queues.set(key, current);
|
|
290
327
|
try {
|
|
291
328
|
await current;
|
|
@@ -297,8 +334,9 @@ export class PartnerAgentRuntime {
|
|
|
297
334
|
}
|
|
298
335
|
}
|
|
299
336
|
async steer(companion, channelId, userId, message) {
|
|
300
|
-
await this.
|
|
301
|
-
const key =
|
|
337
|
+
const route = await this.ensureSession(companion, channelId, userId, message.sessionTitle);
|
|
338
|
+
const key = route.sessionId;
|
|
339
|
+
await this.cancelPostReply(companion.id, undefined, route.sessionId);
|
|
302
340
|
const previous = this.steeringQueues.get(key) ?? Promise.resolve();
|
|
303
341
|
let accepted = false;
|
|
304
342
|
const current = previous.catch(() => { }).then(async () => { accepted = await this.deliverSteering(companion, channelId, userId, message); });
|
|
@@ -700,13 +738,13 @@ export class PartnerAgentRuntime {
|
|
|
700
738
|
}
|
|
701
739
|
async runHeartbeatWhenIdle(key, companion, route, concerns) {
|
|
702
740
|
for (;;) {
|
|
703
|
-
await this.queues.get(
|
|
741
|
+
await this.queues.get(route.sessionId)?.catch(() => { });
|
|
704
742
|
const conversation = await this.ensureAgent(companion, route);
|
|
705
743
|
if (conversation.status !== 'idle')
|
|
706
744
|
await conversation.whenIdle();
|
|
707
745
|
const stableSeq = conversation.session.seq;
|
|
708
746
|
await delay(2_000);
|
|
709
|
-
if (this.queues.has(
|
|
747
|
+
if (this.queues.has(route.sessionId))
|
|
710
748
|
continue;
|
|
711
749
|
if (conversation.status !== 'idle' || conversation.session.seq !== stableSeq)
|
|
712
750
|
continue;
|
|
@@ -714,11 +752,16 @@ export class PartnerAgentRuntime {
|
|
|
714
752
|
}
|
|
715
753
|
}
|
|
716
754
|
async observeSessionEvent(session, event) {
|
|
755
|
+
if (event.type === 'turn/end') {
|
|
756
|
+
const agent = this.ctx?.agents?.get(session.id);
|
|
757
|
+
if (agent)
|
|
758
|
+
this.admittedInputs?.delete(agent);
|
|
759
|
+
}
|
|
717
760
|
if (event.type === 'user/message' && event.data.source.kind === 'user') {
|
|
718
761
|
const owner = this.store.snapshot().sessions.find(r => r.sessionId === session.id)?.companionId;
|
|
719
762
|
if (owner) {
|
|
720
|
-
this.latestPostReplyInput.set(
|
|
721
|
-
await this.cancelPostReply(owner, event.data.id);
|
|
763
|
+
this.latestPostReplyInput.set(session.id, event.data.id);
|
|
764
|
+
await this.cancelPostReply(owner, event.data.id, session.id);
|
|
722
765
|
}
|
|
723
766
|
return;
|
|
724
767
|
}
|
|
@@ -760,7 +803,7 @@ export class PartnerAgentRuntime {
|
|
|
760
803
|
return;
|
|
761
804
|
const scopeId = memoryScope(route.channelId, route.userId);
|
|
762
805
|
const concernDirective = identityRelation && !identityRelation.matched ? undefined : await this.concerns?.applyUserDirective(companion.id, scopeId, userText, event.time);
|
|
763
|
-
const created = companion.automation.memory.enabled && this.reflection !== undefined ? await this.reflection.reflect(companion, {
|
|
806
|
+
const created = companion.automation.memory.enabled && automaticMemoryAllowed(session.id, events, this.store.snapshot().sessions) && this.reflection !== undefined ? await this.reflection.reflect(companion, {
|
|
764
807
|
id: `turn-${createHash('sha256').update(JSON.stringify([session.id, event.time, userText])).digest('hex').slice(0, 32)}`, companionId: companion.id, scopeId: conversationMemoryScope(route, this.store.snapshot().sessions),
|
|
765
808
|
characterEnabled: companion.characterCard?.enabled === true,
|
|
766
809
|
...(identityRelation ? { identityRelation } : {}),
|
|
@@ -883,12 +926,12 @@ export class PartnerAgentRuntime {
|
|
|
883
926
|
throw new Error('伙伴不存在');
|
|
884
927
|
}
|
|
885
928
|
/** Awaited by the host before prompt/tool assembly, never from a running tool. */
|
|
886
|
-
async prepareAgentTurn(agent, turn, signal) {
|
|
929
|
+
async prepareAgentTurn(agent, turn, signal, messages = []) {
|
|
887
930
|
if (this.closed)
|
|
888
931
|
throw new Error('伙伴运行时已关闭');
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
932
|
+
this.admittedInputs.set(agent, messages);
|
|
933
|
+
if (this.preparedTurns.get(agent) !== turn)
|
|
934
|
+
await this.prepareAgentAssembly(agent, signal);
|
|
892
935
|
await this.prepareConversationContext(agent, signal);
|
|
893
936
|
this.preparedTurns.set(agent, turn);
|
|
894
937
|
}
|
|
@@ -908,7 +951,7 @@ export class PartnerAgentRuntime {
|
|
|
908
951
|
if (this.store.isCompanionRemoving(configuration.companion.id))
|
|
909
952
|
throw new Error('伙伴正在删除,不能启动新轮次');
|
|
910
953
|
signal.throwIfAborted();
|
|
911
|
-
const access = resolveToolAccess({ agent, state: this.store.snapshot() });
|
|
954
|
+
const access = resolveToolAccess({ agent, state: this.store.snapshot(), ...(this.admittedInputs.get(agent)?.length ? { events: this.inputEvents(agent) } : {}) });
|
|
912
955
|
let presentationChanged = false;
|
|
913
956
|
const stableNative = configuration.companion.toolDiscovery || !!access.allowedTools;
|
|
914
957
|
if (stableNative && !this.replyPresentations.has(agent) && typeof agent.ctx.tools?.presentAs === 'function') {
|
|
@@ -942,6 +985,18 @@ export class PartnerAgentRuntime {
|
|
|
942
985
|
}
|
|
943
986
|
replyPresentations = new Map();
|
|
944
987
|
contextRevisions = new WeakMap();
|
|
988
|
+
admittedInputs = new WeakMap();
|
|
989
|
+
/** Projection only: the host commits admitted input after prompt preparation. */
|
|
990
|
+
inputEvents(agent) {
|
|
991
|
+
const events = agent.session.snapshotEvents();
|
|
992
|
+
const pending = this.admittedInputs?.get(agent) ?? [];
|
|
993
|
+
if (!pending.length)
|
|
994
|
+
return events;
|
|
995
|
+
const committed = new Set(events.flatMap(e => e.type === 'user/message' ? [e.data.id] : []));
|
|
996
|
+
return [...events, ...pending.filter(m => !committed.has(m.id)).map((data, index) => ({
|
|
997
|
+
type: 'user/message', data, seq: (agent.session.seq + index + 1), time: Date.now(), surfaceOp: 'append',
|
|
998
|
+
}))];
|
|
999
|
+
}
|
|
945
1000
|
async prepareConversationContext(agent, signal) {
|
|
946
1001
|
const config = this.configurations.forSession(agent.session.id);
|
|
947
1002
|
if (!config)
|
|
@@ -949,22 +1004,25 @@ export class PartnerAgentRuntime {
|
|
|
949
1004
|
const { companion, route } = config;
|
|
950
1005
|
const state = this.store.snapshot();
|
|
951
1006
|
const relationEvents = state.channels?.some(c => c.platform === 'qq' && state.sessions.some(r => r.sessionId === agent.session.id && r.channelId === c.id))
|
|
952
|
-
?
|
|
1007
|
+
? this.inputEvents(agent) : undefined;
|
|
953
1008
|
const identityRelation = relationEvents ? currentQqIdentityRelation(agent.session.id, relationEvents, state.sessions, state.channels) : undefined;
|
|
954
|
-
|
|
1009
|
+
const committedMessages = agent.session.deriveMessages?.() ?? [];
|
|
1010
|
+
const committedIds = new Set(committedMessages.map(m => m.id));
|
|
1011
|
+
const messages = [...committedMessages, ...(this.admittedInputs?.get(agent) ?? []).filter(m => !committedIds.has(m.id))];
|
|
1012
|
+
const last = [...messages].reverse().find(m => m.role === 'user' && m.source.kind === 'user');
|
|
1013
|
+
const query = last && last.role === 'user' ? textContent(last.content) : '';
|
|
1014
|
+
if (!identityRelation && !relationshipTextForIdentity(companion) && (!this.memory || !companion.automation.memory.enabled) && !companion.characterCard?.enabled && !renderSleepContext(companion, route.id) && !personaReferenceRequested(query)) {
|
|
955
1015
|
this.characterContexts.get(agent.session.id)?.();
|
|
956
1016
|
this.characterContexts.delete(agent.session.id);
|
|
957
1017
|
this.contextRevisions.delete(agent);
|
|
958
1018
|
return false;
|
|
959
1019
|
}
|
|
960
|
-
const
|
|
961
|
-
const last = [...messages].reverse().find(m => m.role === 'user' && m.source.kind === 'user');
|
|
962
|
-
const query = last && last.role === 'user' ? textContent(last.content) : '';
|
|
963
|
-
const events = relationEvents ?? agent.session.snapshotEvents();
|
|
1020
|
+
const events = relationEvents ?? this.inputEvents(agent);
|
|
964
1021
|
const origin = [...events].reverse().find(e => e.type === 'user/message' && (e.data.source.kind === 'user' || isIdleContactNotice(e) || isInternalTaskNotice(e)));
|
|
965
1022
|
const autonomous = Boolean(origin && isIdleContactNotice(origin));
|
|
966
1023
|
const affectionAudience = currentAffectionAudience(state, companion.id, agent.session.id, events);
|
|
967
|
-
const
|
|
1024
|
+
const access = resolveToolAccess({ agent, state, events });
|
|
1025
|
+
const revision = `${JSON.stringify(access)}:${JSON.stringify(identityRelation)}:${config.revision}:${last?.id ?? ''}:${autonomous && origin?.type === 'user/message' ? origin.data.id : ''}:${JSON.stringify([companion.sleepState?.episode, companion.sleepState?.reception, Math.round(fatigueValue(companion))])}:${companion.sleepState?.dreams?.at(-1)?.id ?? ''}:${affectionAudience}:${companion.affection?.updatedAt ?? 0}`;
|
|
968
1026
|
if (this.contextRevisions.get(agent) === revision)
|
|
969
1027
|
return false;
|
|
970
1028
|
const scope = conversationMemoryScope(route, this.store.snapshot().sessions);
|
|
@@ -979,16 +1037,15 @@ export class PartnerAgentRuntime {
|
|
|
979
1037
|
}
|
|
980
1038
|
signal.throwIfAborted();
|
|
981
1039
|
const relationshipAllowed = !identityRelation || identityRelation.matched;
|
|
982
|
-
const
|
|
983
|
-
const text = [access.allowedTools || !access.sendTools ? access.denialReason + ' 回复能力通过 partner_tools 查询和调用(未开启统一入口时使用原工具);语音能力为 partner_qq_reply;不能代办操作,不承诺使用私有能力。' : '',
|
|
1040
|
+
const text = [origin?.type === 'user/message' && origin.data.source.kind === 'user' && access.sendTools && (!access.allowedTools || access.allowedTools.includes('partner_identity')) && personaReferenceRequested(query) ? renderPersonaReference() : '', access.allowedTools || !access.sendTools ? access.denialReason + ' 回复能力通过 partner_tools 查询和调用(未开启统一入口时使用原工具);语音能力为 partner_qq_reply;不能代办操作,不承诺使用私有能力。' : '',
|
|
984
1041
|
renderIdentityRelationship(companion, identityRelation ? { channel: 'qq', matched: identityRelation.matched } : { channel: 'default' }),
|
|
985
1042
|
renderAffection(companion, affectionAudience),
|
|
986
1043
|
renderCharacterContext(companion.characterCard, messages.slice(-6).filter(m => m.role === 'user' || m.role === 'assistant').map(m => textContent(m.content)).join('\n').slice(-12000), relationshipAllowed ? story : undefined),
|
|
987
1044
|
recalled ? renderMemory(recalled.relevant, recalled.connections, recalled.scenes, recalled.history) : '',
|
|
988
1045
|
recalled && relationshipAllowed ? renderProfile(recalled.profile) : '',
|
|
989
1046
|
renderConversationTurn(companion.characterCard, consecutiveUserMessages, autonomous),
|
|
990
|
-
renderQqIdentityRelation(identityRelation),
|
|
991
|
-
renderSleepContext(companion, this.store.snapshot().sessions.filter(r => r.companionId === companion.id && r.sessionId === route.sessionId).map(r => r.id)),
|
|
1047
|
+
renderQqIdentityRelation(identityRelation, autonomous && access.sendTools && !access.allowedTools),
|
|
1048
|
+
renderSleepContext(companion, this.store.snapshot().sessions.filter(r => r.companionId === companion.id && r.sessionId === route.sessionId).map(r => r.id), autonomous ? undefined : last?.id),
|
|
992
1049
|
].filter(Boolean).join('\n\n');
|
|
993
1050
|
this.characterContexts.get(agent.session.id)?.();
|
|
994
1051
|
// A changing .section() appends an entire system prompt on in-history routes.
|
|
@@ -1007,7 +1064,7 @@ export class PartnerAgentRuntime {
|
|
|
1007
1064
|
const companion = this.configurations.companion(companionId);
|
|
1008
1065
|
if (!companion?.automation.memory.enabled)
|
|
1009
1066
|
return;
|
|
1010
|
-
const routes =
|
|
1067
|
+
const routes = this.store.snapshot().sessions.filter(r => r.companionId === companionId && r.kind === 'local');
|
|
1011
1068
|
for (const route of routes) {
|
|
1012
1069
|
if (this.isArchived(route))
|
|
1013
1070
|
continue;
|
|
@@ -1031,6 +1088,8 @@ export class PartnerAgentRuntime {
|
|
|
1031
1088
|
replies.push(textContent(next.content));
|
|
1032
1089
|
}
|
|
1033
1090
|
const original = agent.session.snapshotEvents().find(e => e.type === 'user/message' && e.data.id === item.id);
|
|
1091
|
+
if (!original || !automaticMemoryAllowed(route.sessionId, [original], this.store.snapshot().sessions))
|
|
1092
|
+
continue;
|
|
1034
1093
|
await this.memory.enqueue({ id: 'renewal-' + createHash('sha256').update(route.sessionId + ':' + item.id).digest('hex').slice(0, 32),
|
|
1035
1094
|
companionId, scopeId: scope, sessionId: route.sessionId, at: original?.time ?? Date.now(), userMessageIds: [item.id],
|
|
1036
1095
|
user: textContent(item.content), assistant: replies.join('\n\n') || '(本条消息没有已完成的回复,仅整理用户原话,不推测处理结果。)',
|
|
@@ -1039,17 +1098,17 @@ export class PartnerAgentRuntime {
|
|
|
1039
1098
|
}
|
|
1040
1099
|
}
|
|
1041
1100
|
async renewReviewedContexts(companionId, day) {
|
|
1042
|
-
if (!this.memory || this.isCompanionBusy(companionId) || this.queues.has(
|
|
1101
|
+
if (!this.memory || this.isCompanionBusy(companionId) || this.store.snapshot().sessions.some(r => r.companionId === companionId && this.queues.has(r.sessionId)))
|
|
1043
1102
|
return 0;
|
|
1044
1103
|
const companion = this.configurations.companion(companionId);
|
|
1045
1104
|
if (!companion || companion.automation.memory.contextRenewalEnabled === false)
|
|
1046
1105
|
return 0;
|
|
1047
|
-
const learning = companion.automation.memory.enabled;
|
|
1048
1106
|
let renewed = 0;
|
|
1049
1107
|
const routes = [...new Map(this.store.snapshot().sessions.filter(r => r.companionId === companionId).map(r => [r.sessionId, r])).values()];
|
|
1050
1108
|
for (const route of routes) {
|
|
1051
1109
|
if (this.ctx.workspaceRegistry.archivedSessionIds.includes(route.sessionId))
|
|
1052
1110
|
continue;
|
|
1111
|
+
const learning = companion.automation.memory.enabled && this.store.snapshot().sessions.some(r => r.kind === 'local' && r.sessionId === route.sessionId && r.companionId === companionId);
|
|
1053
1112
|
const scope = conversationMemoryScope(route, this.store.snapshot().sessions);
|
|
1054
1113
|
const agent = await this.ensureAgent(companion, route);
|
|
1055
1114
|
if (agent.session.surface.nodes.length <= 1) {
|
|
@@ -1068,7 +1127,7 @@ export class PartnerAgentRuntime {
|
|
|
1068
1127
|
return false;
|
|
1069
1128
|
if (!learning)
|
|
1070
1129
|
return true;
|
|
1071
|
-
return this.memory.coversContext(companionId, scope, agent.session.deriveMessages().filter(m => m.role === 'user' && m.source.kind === 'user').map(m => ({ id: m.id, text: textContent(m.content) })));
|
|
1130
|
+
return this.memory.coversContext(companionId, scope, agent.session.deriveMessages().filter(m => m.role === 'user' && m.source.kind === 'user' && automaticMemoryAllowed(route.sessionId, agent.session.snapshotEvents().filter(e => e.type === 'user/message' && e.data.id === m.id), this.store.snapshot().sessions)).map(m => ({ id: m.id, text: textContent(m.content) })));
|
|
1072
1131
|
}, session => this.ctx.sessions.flush(session), learning);
|
|
1073
1132
|
if (removed > 0 || agent.session.deriveMessages().some(m => m.role === 'user' && m.source.kind === 'plugin:@lemoncat7/dsh-partner' && m.source.form === 'notice' && m.source.summary === `上下文焕新 ${day}`)) {
|
|
1074
1133
|
await this.memory.markRenewed(companionId, route.sessionId, day, removed);
|
|
@@ -1153,11 +1212,13 @@ export class PartnerAgentRuntime {
|
|
|
1153
1212
|
restored?.dispose();
|
|
1154
1213
|
this.restoredCompositions.delete(sessionId);
|
|
1155
1214
|
}
|
|
1156
|
-
async drive(companion, channelId, userId, message, progress, queuedAt = Date.now()) {
|
|
1215
|
+
async drive(companion, channelId, userId, message, progress, queuedAt = Date.now(), preparedRoute) {
|
|
1157
1216
|
reportProgress(progress, 'working');
|
|
1158
1217
|
const enteredAt = Date.now();
|
|
1159
1218
|
let submittedAt = enteredAt, modelEndedAt = enteredAt, lastTurnEnd;
|
|
1160
|
-
const session = await this.ensureSession(companion, channelId, userId, message.sessionTitle);
|
|
1219
|
+
const session = preparedRoute ?? await this.ensureSession(companion, channelId, userId, message.sessionTitle);
|
|
1220
|
+
if (!this.store.snapshot().sessions.some(r => r.id === session.id && r.sessionId === session.sessionId))
|
|
1221
|
+
throw new Error('会话配置已变化,请重新发送消息');
|
|
1161
1222
|
const agent = await this.ensureAgent(companion, session);
|
|
1162
1223
|
if (agent.status !== 'idle')
|
|
1163
1224
|
await agent.whenIdle();
|
|
@@ -1179,7 +1240,7 @@ export class PartnerAgentRuntime {
|
|
|
1179
1240
|
if (event.type === 'turn/start' || event.type === 'turn/end' || (isInternalTaskNotice(event) && !isRequirementDeliveryReceipt(event)))
|
|
1180
1241
|
directProgress = false;
|
|
1181
1242
|
if (event.type === 'user/message' && event.data.source.kind === 'user')
|
|
1182
|
-
directProgress =
|
|
1243
|
+
directProgress = questionOrigin(agent.session.id, agent.session.snapshotEvents(), this.store.snapshot().sessions)?.id === session.id;
|
|
1183
1244
|
const stage = replyStage(event);
|
|
1184
1245
|
if (stage)
|
|
1185
1246
|
reportProgress(progress, stage);
|
|
@@ -1189,6 +1250,7 @@ export class PartnerAgentRuntime {
|
|
|
1189
1250
|
reportProgress(progress, activity);
|
|
1190
1251
|
}
|
|
1191
1252
|
});
|
|
1253
|
+
const detachPreamble = progress?.acknowledge ? installTaskPreamble(agent, progress.acknowledge, () => questionOrigin(agent.session.id, agent.session.snapshotEvents(), this.store.snapshot().sessions)?.id === session.id) : undefined;
|
|
1192
1254
|
try {
|
|
1193
1255
|
const userMessage = createUserMessage({
|
|
1194
1256
|
content: inbound.content,
|
|
@@ -1202,6 +1264,7 @@ export class PartnerAgentRuntime {
|
|
|
1202
1264
|
modelEndedAt = Date.now();
|
|
1203
1265
|
}
|
|
1204
1266
|
finally {
|
|
1267
|
+
detachPreamble?.();
|
|
1205
1268
|
detach();
|
|
1206
1269
|
}
|
|
1207
1270
|
const response = channelReplyPartsAfter(agent.session.snapshotEvents(), startSeq);
|
|
@@ -1273,7 +1336,7 @@ export class PartnerAgentRuntime {
|
|
|
1273
1336
|
return existing;
|
|
1274
1337
|
const sharedScope = conversationMemoryScope(primary, [primary]);
|
|
1275
1338
|
const sources = isQq ? snapshot.sessions.filter(r => r.channelId === channelId && r.companionId === companion.id && qqSessionTarget(r.userId) === target && r.memoryScopeId !== sharedScope).map(r => memoryScope(r.channelId, r.userId)) : [];
|
|
1276
|
-
if (existing?.memoryScopeId !== sharedScope || sources.length)
|
|
1339
|
+
if ((!isQq || !target.startsWith('group:') && qqSettings(channel?.qq).mainSessionPeers.includes(target)) && (existing?.memoryScopeId !== sharedScope || sources.length))
|
|
1277
1340
|
await this.memory?.mergeScopes(companion.id, sharedScope, [...new Set([...sources, memoryScope(channelId, userId)])], recoverLeases);
|
|
1278
1341
|
let result;
|
|
1279
1342
|
await this.store.update(state => {
|