evolcore 0.0.26 → 0.0.27
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/CHANGELOG.md +25 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +63 -26
- package/dist/agents/codex-runner.js +316 -411
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +12 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +522 -233
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +35 -23
- package/dist/cli/diag.js +2 -2
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +9 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +16 -2
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +76 -39
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +348 -77
- package/dist/core/command/menu-protocol.js +40 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +37 -22
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +76 -15
- package/dist/core/message/message-bridge.js +94 -11
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +709 -117
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +61 -29
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +308 -54
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +15 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -46,6 +46,8 @@ export class InteractionRouter {
|
|
|
46
46
|
clearTimeout(existing.timer);
|
|
47
47
|
const isReplacement = !!existing;
|
|
48
48
|
const pending = {
|
|
49
|
+
blocking: opts?.blocking !== false,
|
|
50
|
+
retainOnRejected: opts?.retainOnRejected === true,
|
|
49
51
|
callback,
|
|
50
52
|
sessionId,
|
|
51
53
|
initiatorId: opts?.initiatorId,
|
|
@@ -61,7 +63,8 @@ export class InteractionRouter {
|
|
|
61
63
|
if (this.handlers.get(id) !== pending)
|
|
62
64
|
return;
|
|
63
65
|
this.handlers.delete(id);
|
|
64
|
-
|
|
66
|
+
if (pending.blocking)
|
|
67
|
+
this.decPending(pending.sessionId);
|
|
65
68
|
const cancelReason = opts.timeoutCancelReason ?? 'timeout';
|
|
66
69
|
logger.debug(`[InteractionRouter] Timeout for interaction: ${id}`);
|
|
67
70
|
opts.onTimeout?.();
|
|
@@ -73,7 +76,9 @@ export class InteractionRouter {
|
|
|
73
76
|
}, opts.timeoutMs);
|
|
74
77
|
}
|
|
75
78
|
this.handlers.set(id, pending);
|
|
76
|
-
if (!
|
|
79
|
+
if (existing?.blocking && (!pending.blocking || existing.sessionId !== sessionId))
|
|
80
|
+
this.decPending(existing.sessionId);
|
|
81
|
+
if (pending.blocking && (!isReplacement || !existing?.blocking || existing.sessionId !== sessionId))
|
|
77
82
|
this.incPending(sessionId);
|
|
78
83
|
}
|
|
79
84
|
dispatch(response) {
|
|
@@ -104,10 +109,15 @@ export class InteractionRouter {
|
|
|
104
109
|
// The callback may already have resolved/cancelled this exact handler.
|
|
105
110
|
if (this.handlers.get(response.id) !== handler)
|
|
106
111
|
return accepted;
|
|
112
|
+
if (!accepted && handler.retainOnRejected && rejectionReason === 'backend_rejected') {
|
|
113
|
+
handler.inFlight = false;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
107
116
|
if (handler.timer)
|
|
108
117
|
clearTimeout(handler.timer);
|
|
109
118
|
this.handlers.delete(response.id);
|
|
110
|
-
|
|
119
|
+
if (handler.blocking)
|
|
120
|
+
this.decPending(handler.sessionId);
|
|
111
121
|
if (!accepted && handler.onCancel) {
|
|
112
122
|
Promise.resolve(handler.onCancel(rejectionReason)).catch(err => {
|
|
113
123
|
logger.error(`[InteractionRouter] Rejection cancellation error for ${response.id}:`, err);
|
|
@@ -153,10 +163,13 @@ export class InteractionRouter {
|
|
|
153
163
|
const pending = [];
|
|
154
164
|
for (const [id, handler] of this.handlers.entries()) {
|
|
155
165
|
if (handler.sessionId === sessionId) {
|
|
166
|
+
if (!handler.blocking && reason === 'new_message')
|
|
167
|
+
continue;
|
|
156
168
|
if (handler.timer)
|
|
157
169
|
clearTimeout(handler.timer);
|
|
158
170
|
this.handlers.delete(id);
|
|
159
|
-
|
|
171
|
+
if (handler.blocking)
|
|
172
|
+
this.decPending(handler.sessionId);
|
|
160
173
|
if (reason && handler.onCancel) {
|
|
161
174
|
pending.push(Promise.resolve(handler.onCancel(reason)).then(() => undefined));
|
|
162
175
|
}
|
|
@@ -170,7 +183,8 @@ export class InteractionRouter {
|
|
|
170
183
|
if (handler.timer)
|
|
171
184
|
clearTimeout(handler.timer);
|
|
172
185
|
this.handlers.delete(id);
|
|
173
|
-
|
|
186
|
+
if (handler.blocking)
|
|
187
|
+
this.decPending(handler.sessionId);
|
|
174
188
|
if (reason && handler.onCancel)
|
|
175
189
|
await handler.onCancel(reason);
|
|
176
190
|
return true;
|
|
@@ -208,10 +222,10 @@ export class InteractionRouter {
|
|
|
208
222
|
return false;
|
|
209
223
|
}
|
|
210
224
|
}
|
|
211
|
-
getPending(sessionId) {
|
|
225
|
+
getPending(sessionId, command) {
|
|
212
226
|
const ids = [];
|
|
213
227
|
for (const [id, handler] of this.handlers.entries()) {
|
|
214
|
-
if (handler.sessionId === sessionId)
|
|
228
|
+
if (handler.sessionId === sessionId && (!command || handler.fallbackCommand === command))
|
|
215
229
|
ids.push(id);
|
|
216
230
|
}
|
|
217
231
|
return ids;
|
|
@@ -281,7 +295,7 @@ export function renderActionAsText(req) {
|
|
|
281
295
|
if (fb.acceptFreeText && fb.freeTextHint) {
|
|
282
296
|
lines.push(` ${fb.freeTextHint}`);
|
|
283
297
|
}
|
|
284
|
-
if (action.allowCustomInput) {
|
|
298
|
+
if (action.allowCustomInput && req.blocking !== false) {
|
|
285
299
|
lines.push(` 或直接输入自定义内容`);
|
|
286
300
|
}
|
|
287
301
|
return lines.join('\n');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { logger } from '../../utils/logger.js';
|
|
2
|
-
import { summarizeToolInput, toolInputForDisplay } from '../../utils/tool-summary.js';
|
|
2
|
+
import { summarizeToolInput, summarizeToolInputForDisplay, toolInputForDisplay } from '../../utils/tool-summary.js';
|
|
3
3
|
import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
|
|
4
4
|
import { tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
5
5
|
import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
|
|
6
|
+
import { classifyAunToolErrorCode } from '../permission/tool-error-code.js';
|
|
6
7
|
export class IMRenderer {
|
|
7
8
|
opts;
|
|
8
9
|
itemsQueue = [];
|
|
@@ -19,6 +20,7 @@ export class IMRenderer {
|
|
|
19
20
|
hasEmittedText = false;
|
|
20
21
|
/** 自增 callId 兜底(runner 没提供时用) */
|
|
21
22
|
syntheticCallSeq = 0;
|
|
23
|
+
currentAssistantUuid;
|
|
22
24
|
constructor(opts) {
|
|
23
25
|
this.opts = opts;
|
|
24
26
|
}
|
|
@@ -61,12 +63,12 @@ export class IMRenderer {
|
|
|
61
63
|
// interactive 模式由 MessageProcessor 显式调 addText/addToolCall/... 推入 items
|
|
62
64
|
}
|
|
63
65
|
/** 强制刷新所有 pending 事件 */
|
|
64
|
-
async flush(isFinal) {
|
|
66
|
+
async flush(isFinal, attention) {
|
|
65
67
|
if (this.opts.envelope.chatmode === 'proactive') {
|
|
66
68
|
// proactive 是 fire-and-forget,无 pending buffer
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
|
-
return this.flushInternal(isFinal);
|
|
71
|
+
return this.flushInternal(isFinal, attention);
|
|
70
72
|
}
|
|
71
73
|
/** 仅 flush activities,保留 textBuffer(用于中间 complete 事件) */
|
|
72
74
|
async flushActivitiesOnly() {
|
|
@@ -122,7 +124,12 @@ export class IMRenderer {
|
|
|
122
124
|
this.textBuffer = '';
|
|
123
125
|
// 清掉已消费的 text items,包括被拦截的纯空白正文。
|
|
124
126
|
this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
|
|
125
|
-
const payload = {
|
|
127
|
+
const payload = {
|
|
128
|
+
kind: 'result.text',
|
|
129
|
+
text: rawText,
|
|
130
|
+
isFinal: false,
|
|
131
|
+
...this.forkAnchorMetadata(),
|
|
132
|
+
};
|
|
126
133
|
this.sentContent = true;
|
|
127
134
|
try {
|
|
128
135
|
await this.sendPayload(payload);
|
|
@@ -168,9 +175,14 @@ export class IMRenderer {
|
|
|
168
175
|
}
|
|
169
176
|
// ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
|
|
170
177
|
/** 添加文本片段(流式 text) */
|
|
171
|
-
addText(text, outputTokens, turn) {
|
|
178
|
+
async addText(text, outputTokens, turn, assistantUuid) {
|
|
172
179
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
173
180
|
return;
|
|
181
|
+
if (this.textBuffer && this.currentAssistantUuid !== assistantUuid && !this.suppressIntermediateText()) {
|
|
182
|
+
await this.flushText();
|
|
183
|
+
}
|
|
184
|
+
// Do not carry the previous item's anchor onto unanchored output.
|
|
185
|
+
this.currentAssistantUuid = assistantUuid;
|
|
174
186
|
this.emitProgress('text', outputTokens, turn);
|
|
175
187
|
if (!text)
|
|
176
188
|
return;
|
|
@@ -195,18 +207,19 @@ export class IMRenderer {
|
|
|
195
207
|
this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
|
|
196
208
|
if (this.suppressActivityItems())
|
|
197
209
|
return;
|
|
210
|
+
const displayInput = toolInputForDisplay(name, input);
|
|
198
211
|
this.itemsQueue.push({
|
|
199
212
|
kind: 'tool_call',
|
|
200
213
|
call_id: callId || this.synthCallId(),
|
|
201
214
|
name,
|
|
202
|
-
arguments:
|
|
203
|
-
text: descText,
|
|
215
|
+
arguments: displayInput,
|
|
216
|
+
text: displayInput !== input ? summarizeToolInput(name, displayInput || {}) : descText,
|
|
204
217
|
});
|
|
205
218
|
this.messageTimestamps.push(Date.now());
|
|
206
219
|
this.scheduleFlush();
|
|
207
220
|
}
|
|
208
221
|
/** 添加工具结果 */
|
|
209
|
-
addToolResult(name, ok, result, error, callId, durationMs, descText) {
|
|
222
|
+
addToolResult(name, ok, result, error, callId, durationMs, descText, classification) {
|
|
210
223
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
211
224
|
return;
|
|
212
225
|
this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
|
|
@@ -219,6 +232,7 @@ export class IMRenderer {
|
|
|
219
232
|
ok,
|
|
220
233
|
...(result !== undefined && { result }),
|
|
221
234
|
...(error !== undefined && { error }),
|
|
235
|
+
...(!ok && { error_code: classifyAunToolErrorCode(classification ?? {}) }),
|
|
222
236
|
...(durationMs !== undefined && { duration_ms: durationMs }),
|
|
223
237
|
...(descText !== undefined && { text: descText }),
|
|
224
238
|
});
|
|
@@ -260,6 +274,19 @@ export class IMRenderer {
|
|
|
260
274
|
logger.warn(`[IMRenderer] status notice send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
261
275
|
}
|
|
262
276
|
}
|
|
277
|
+
/** Notify the client that a new model text block has started. */
|
|
278
|
+
async sendTextStart(contentIndex, turn) {
|
|
279
|
+
const payload = {
|
|
280
|
+
kind: 'status.progress',
|
|
281
|
+
metadata: { activityType: 'text', kind: 'text_start', contentIndex, turn },
|
|
282
|
+
};
|
|
283
|
+
try {
|
|
284
|
+
await this.opts.send(payload);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
logger.debug(`[IMRenderer] text_start status send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
263
290
|
/** 兼容旧调用方:发送 warning 级别的任务 notice。 */
|
|
264
291
|
async sendWarningStatus(message, subtype, details) {
|
|
265
292
|
return this.sendStatusNotice(message, 'warn', subtype, details);
|
|
@@ -317,7 +344,11 @@ export class IMRenderer {
|
|
|
317
344
|
return false;
|
|
318
345
|
// 保持与流式正文的到达顺序:先发出已缓冲的正文,再投影运行状态。
|
|
319
346
|
await this.flushText();
|
|
320
|
-
const payload = {
|
|
347
|
+
const payload = {
|
|
348
|
+
kind: 'result.text',
|
|
349
|
+
text,
|
|
350
|
+
isFinal: false,
|
|
351
|
+
};
|
|
321
352
|
this.sentContent = true;
|
|
322
353
|
await this.sendPayload(payload);
|
|
323
354
|
this.lastFlush = Date.now();
|
|
@@ -470,7 +501,7 @@ export class IMRenderer {
|
|
|
470
501
|
* 完整 flush:把 itemsQueue 里所有 items 打包成 activity.batch 发送。
|
|
471
502
|
* 如果 isFinal=true,还会在 batch 之后单独发一条 result.text 作为最终回复。
|
|
472
503
|
*/
|
|
473
|
-
async flushInternal(isFinal) {
|
|
504
|
+
async flushInternal(isFinal, attention) {
|
|
474
505
|
if (this.timer) {
|
|
475
506
|
clearTimeout(this.timer);
|
|
476
507
|
this.timer = undefined;
|
|
@@ -501,6 +532,7 @@ export class IMRenderer {
|
|
|
501
532
|
});
|
|
502
533
|
this.itemsQueue = [];
|
|
503
534
|
const finalText = isFinal ? this.textBuffer : '';
|
|
535
|
+
const finalAnchor = this.forkAnchorMetadata();
|
|
504
536
|
if (isFinal)
|
|
505
537
|
this.textBuffer = '';
|
|
506
538
|
// 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
|
|
@@ -532,7 +564,12 @@ export class IMRenderer {
|
|
|
532
564
|
return;
|
|
533
565
|
const text = this.textBuffer;
|
|
534
566
|
this.textBuffer = '';
|
|
535
|
-
const payload = {
|
|
567
|
+
const payload = {
|
|
568
|
+
kind: 'result.text',
|
|
569
|
+
text,
|
|
570
|
+
isFinal: false,
|
|
571
|
+
...this.forkAnchorMetadata(),
|
|
572
|
+
};
|
|
536
573
|
this.sentContent = true;
|
|
537
574
|
try {
|
|
538
575
|
await this.sendPayload(payload);
|
|
@@ -546,7 +583,14 @@ export class IMRenderer {
|
|
|
546
583
|
}
|
|
547
584
|
// 2. isFinal=true 时单独发最终回复文本
|
|
548
585
|
if (isFinal && finalText.length > 0) {
|
|
549
|
-
const payload = {
|
|
586
|
+
const payload = {
|
|
587
|
+
kind: 'result.text',
|
|
588
|
+
text: finalText,
|
|
589
|
+
isFinal: true,
|
|
590
|
+
...finalAnchor,
|
|
591
|
+
};
|
|
592
|
+
if (attention !== undefined)
|
|
593
|
+
payload.attention = attention;
|
|
550
594
|
this.sentContent = true;
|
|
551
595
|
try {
|
|
552
596
|
await this.sendPayload(payload);
|
|
@@ -559,6 +603,15 @@ export class IMRenderer {
|
|
|
559
603
|
this.flushCount++;
|
|
560
604
|
}
|
|
561
605
|
}
|
|
606
|
+
forkAnchorMetadata() {
|
|
607
|
+
const assistantMessageId = this.currentAssistantUuid?.trim();
|
|
608
|
+
if (!assistantMessageId)
|
|
609
|
+
return {};
|
|
610
|
+
const anchor = {
|
|
611
|
+
assistantMessageId,
|
|
612
|
+
};
|
|
613
|
+
return { metadata: { forkAnchor: anchor } };
|
|
614
|
+
}
|
|
562
615
|
// ── 内部:status.progress 发送 ──
|
|
563
616
|
emitProgress(activityType, outputTokens, turn, extra) {
|
|
564
617
|
if (this.suppressActivityItems())
|
|
@@ -592,9 +645,16 @@ export class IMRenderer {
|
|
|
592
645
|
const item = this.mapEventToItem(event);
|
|
593
646
|
if (!item)
|
|
594
647
|
return;
|
|
648
|
+
// Proactive output is optional thought content. Blank runner blocks are
|
|
649
|
+
// normal; keep diagnostics without reporting a failed message send.
|
|
650
|
+
if ((item.kind === 'text' || (item.kind === 'summary' && !item.is_error)) && !item.text.trim()) {
|
|
651
|
+
const { envelope, agentAid } = this.opts;
|
|
652
|
+
logger.debug(`[IMRenderer] Skipping blank proactive thought: kind=${item.kind}`
|
|
653
|
+
+ ` task=${envelope.taskId} session=${envelope.sessionId ?? '<none>'}`
|
|
654
|
+
+ ` agent=${agentAid ?? envelope.agentName} textLength=${item.text.length}`);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
595
657
|
if (item.kind === 'text') {
|
|
596
|
-
if (!this.hasSendableText(item.text, 'proactive'))
|
|
597
|
-
return;
|
|
598
658
|
this.hasEmittedText = true;
|
|
599
659
|
this.allText += item.text;
|
|
600
660
|
}
|
|
@@ -622,7 +682,7 @@ export class IMRenderer {
|
|
|
622
682
|
return null;
|
|
623
683
|
return { kind: 'text', text: event.text };
|
|
624
684
|
case 'tool_use': {
|
|
625
|
-
const desc =
|
|
685
|
+
const desc = summarizeToolInputForDisplay(event.name, event.input || {});
|
|
626
686
|
const item = {
|
|
627
687
|
kind: 'tool_call',
|
|
628
688
|
call_id: event.callId || this.synthCallId(),
|
|
@@ -640,6 +700,7 @@ export class IMRenderer {
|
|
|
640
700
|
call_id: event.callId || this.synthCallId(),
|
|
641
701
|
name: event.name,
|
|
642
702
|
ok: false,
|
|
703
|
+
error_code: classifyAunToolErrorCode(event),
|
|
643
704
|
error: event.error || (typeof event.result === 'string' ? event.result : '执行失败'),
|
|
644
705
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
645
706
|
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { loadAgent } from '../../config-store.js';
|
|
2
|
+
import { initializationBlocksTasks } from '../../cli/agent-create.js';
|
|
1
3
|
import { createHash, randomBytes } from 'crypto';
|
|
4
|
+
import { agentStorageMenuService, isAgentStorageMenuRequest } from '../command/agent-storage-menu.js';
|
|
2
5
|
import { logger } from '../../utils/logger.js';
|
|
3
6
|
import { StreamDebouncer } from './stream-debouncer.js';
|
|
4
7
|
import { appendMessageLog, appendMessageLogStrict, buildInboundEntry, isTransientProtocolMessage } from './message-log.js';
|
|
5
8
|
import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from './message-utils.js';
|
|
6
9
|
import { chatDirPath } from '../session/session-fs-store.js';
|
|
10
|
+
import { readTopicLable } from '../session/session-mapper.js';
|
|
7
11
|
import { formatChannelKey, tryParseChannelKey } from '../channel-loader.js';
|
|
8
12
|
import { agentDir, resolvePaths } from '../../paths.js';
|
|
9
13
|
import { listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, } from '../../config/peer-role-resolver.js';
|
|
@@ -27,6 +31,7 @@ import { renderActionAsText } from '../interaction-router.js';
|
|
|
27
31
|
import { planApprovalRoute } from '../permission/approval-gateway.js';
|
|
28
32
|
import { guardGroupSlashCommand, parseFullAccessCommand } from '../command/slash-gate.js';
|
|
29
33
|
import { isFullAccessEnabled } from '../../config-store.js';
|
|
34
|
+
import { createAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
30
35
|
import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
31
36
|
import { consumeAdmissionReplyBudget, clearAdmissionReplyBudgets, evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission, } from './message-admission.js';
|
|
32
37
|
const CONTACT_REJECTION_NOTICE = '[rejection] Not accepting messages from you.';
|
|
@@ -113,6 +118,7 @@ export class MessageBridge {
|
|
|
113
118
|
aidStatsCollector;
|
|
114
119
|
contactBindRuntimeChecker;
|
|
115
120
|
interactionRouter;
|
|
121
|
+
groupInviteReviewService;
|
|
116
122
|
constructor(defaultProjectPath, sessionManager, processor, messageQueue, cmdHandler, eventBus, defaultDebounce, processOwnersProvider) {
|
|
117
123
|
this.defaultProjectPath = defaultProjectPath;
|
|
118
124
|
this.sessionManager = sessionManager;
|
|
@@ -144,6 +150,12 @@ export class MessageBridge {
|
|
|
144
150
|
setInteractionRouter(router) {
|
|
145
151
|
this.interactionRouter = router;
|
|
146
152
|
}
|
|
153
|
+
setGroupInviteReviewService(service) {
|
|
154
|
+
this.groupInviteReviewService = service;
|
|
155
|
+
}
|
|
156
|
+
registerGroupInviteReviewChannel(binding) {
|
|
157
|
+
this.groupInviteReviewService?.attach(binding);
|
|
158
|
+
}
|
|
147
159
|
/**
|
|
148
160
|
* AUN supplies an authenticated sender AID, while the IM-side code proves
|
|
149
161
|
* possession of the external account. This command only creates that
|
|
@@ -483,7 +495,7 @@ export class MessageBridge {
|
|
|
483
495
|
...(msg.replyContext ?? {}),
|
|
484
496
|
metadata: {
|
|
485
497
|
...(msg.replyContext?.metadata ?? {}),
|
|
486
|
-
...evolMenuResponseTransportMetadata(),
|
|
498
|
+
...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version),
|
|
487
499
|
},
|
|
488
500
|
},
|
|
489
501
|
}), {
|
|
@@ -795,6 +807,20 @@ export class MessageBridge {
|
|
|
795
807
|
}
|
|
796
808
|
return;
|
|
797
809
|
}
|
|
810
|
+
if (!trustedHandoffEcho && selfAid && actorId && this.groupInviteReviewService) {
|
|
811
|
+
const inviteCommand = await this.groupInviteReviewService.handleCommand({
|
|
812
|
+
selfAid,
|
|
813
|
+
ownerAid: actorId,
|
|
814
|
+
channelKey,
|
|
815
|
+
channelType: resolvedChannelType,
|
|
816
|
+
chatType,
|
|
817
|
+
content,
|
|
818
|
+
});
|
|
819
|
+
if (inviteCommand.handled) {
|
|
820
|
+
await this.sendAdmissionControlReply(admissionPreflight.context, msg, sendReply, inviteCommand.text ?? '群邀请命令已处理。');
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
798
824
|
if (!trustedHandoffEcho && await this.handleReservedContactControl({
|
|
799
825
|
context: admissionPreflight.context,
|
|
800
826
|
msg,
|
|
@@ -1027,6 +1053,11 @@ export class MessageBridge {
|
|
|
1027
1053
|
...(chatType === 'private' ? { routeId: msg.channelId } : {}),
|
|
1028
1054
|
});
|
|
1029
1055
|
}
|
|
1056
|
+
const initializationConfig = selfAid ? loadAgent(selfAid) : null;
|
|
1057
|
+
if (initializationConfig && initializationBlocksTasks(initializationConfig)) {
|
|
1058
|
+
await sendReply(msg.channelId, 'Agent 初始化尚未完成,请由创建者查询 ec agent show 或恢复初始化。');
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1030
1061
|
// 1. owner 绑定(按实例名绑定)
|
|
1031
1062
|
if (adapter && actorId && roleDetail.effectiveRole === 'owner') {
|
|
1032
1063
|
await this.bootstrapService?.tryStartBootstrap({
|
|
@@ -1106,6 +1137,14 @@ export class MessageBridge {
|
|
|
1106
1137
|
return sendReply(msg.channelId, text, msg.replyContext);
|
|
1107
1138
|
}, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext, msg.messageId, msg.selfAID, authSubject))
|
|
1108
1139
|
return;
|
|
1140
|
+
// Commands retain their normal route. Only authenticated direct model
|
|
1141
|
+
// input can obtain the Agent policy's per-call authorization.
|
|
1142
|
+
if (!executionPermissionOverride && selfAid && owningAgent?.aid === selfAid) {
|
|
1143
|
+
executionPermissionOverride = createAgentPolicyAuthorization({
|
|
1144
|
+
agentAid: selfAid, actor: roleDetail.actor, chatType,
|
|
1145
|
+
source: msg.source, peerType: msg.peerType,
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1109
1148
|
// 3. session 解析(使用 Channel 层填充的 chatType)
|
|
1110
1149
|
if (!(await this.canCreateThreadSession(channelName, msg, chatType))) {
|
|
1111
1150
|
// 静默丢弃:绝不向群里注入回复。
|
|
@@ -1149,7 +1188,7 @@ export class MessageBridge {
|
|
|
1149
1188
|
// has been appended and create another renewed session.
|
|
1150
1189
|
releaseAdmission = await this.acquireAdmission(admissionRoute);
|
|
1151
1190
|
const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
|
|
1152
|
-
let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity);
|
|
1191
|
+
let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLable(msg) ?? readTopicLable(msg.replyContext?.metadata));
|
|
1153
1192
|
if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
|
|
1154
1193
|
releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
|
|
1155
1194
|
}
|
|
@@ -1205,6 +1244,7 @@ export class MessageBridge {
|
|
|
1205
1244
|
mentions: msg.mentions, mentionAids: msg.mentionAids, threadId: msg.threadId,
|
|
1206
1245
|
isMentioned: msg.isMentioned,
|
|
1207
1246
|
topicName: this.extractTopicName(msg),
|
|
1247
|
+
lable: session.lable,
|
|
1208
1248
|
replyContext: msg.replyContext,
|
|
1209
1249
|
source: msg.source,
|
|
1210
1250
|
...(executionPermissionOverride ? { executionPermissionOverride } : {}),
|
|
@@ -1427,6 +1467,8 @@ export class MessageBridge {
|
|
|
1427
1467
|
const menuAuthSubject = {
|
|
1428
1468
|
...authSubject,
|
|
1429
1469
|
requestId: parsed.id,
|
|
1470
|
+
menuEncrypted: msg.encrypted === true,
|
|
1471
|
+
fromControlChannel: authSubject.fromControlChannel || msg.isControlChannel === true,
|
|
1430
1472
|
...(activeSession?.id ? { sessionId: activeSession.id } : {}),
|
|
1431
1473
|
};
|
|
1432
1474
|
const isTokenRequest = parsed.type === 'menu.token.request';
|
|
@@ -1457,6 +1499,17 @@ export class MessageBridge {
|
|
|
1457
1499
|
return;
|
|
1458
1500
|
}
|
|
1459
1501
|
}
|
|
1502
|
+
// Reauthorize even cached responses: roots expire and static owners can be revoked.
|
|
1503
|
+
const isStorageRequest = isAgentStorageMenuRequest(parsed.request);
|
|
1504
|
+
if (isStorageRequest) {
|
|
1505
|
+
try {
|
|
1506
|
+
agentStorageMenuService.preflight(parsed.request, menuAuthSubject);
|
|
1507
|
+
}
|
|
1508
|
+
catch (error) {
|
|
1509
|
+
await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, normalizeMenuError(error)));
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1460
1513
|
const scope = msg.chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
|
|
1461
1514
|
// Separate installations can generate identical request IDs. Token
|
|
1462
1515
|
// issuance and replay must remain scoped to the authenticated requester.
|
|
@@ -1474,6 +1527,16 @@ export class MessageBridge {
|
|
|
1474
1527
|
}));
|
|
1475
1528
|
return;
|
|
1476
1529
|
}
|
|
1530
|
+
// A replay may have awaited an in-flight request; check again before release.
|
|
1531
|
+
if (isStorageRequest) {
|
|
1532
|
+
try {
|
|
1533
|
+
agentStorageMenuService.preflight(parsed.request, menuAuthSubject, false);
|
|
1534
|
+
}
|
|
1535
|
+
catch (error) {
|
|
1536
|
+
await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, normalizeMenuError(error)));
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1477
1540
|
if (deduped.replayed)
|
|
1478
1541
|
this.logMenuDiagnostic('request-replay', channel, msg, parsed);
|
|
1479
1542
|
if (deduped.retry)
|
|
@@ -1488,7 +1551,20 @@ export class MessageBridge {
|
|
|
1488
1551
|
await deferredEffect();
|
|
1489
1552
|
}
|
|
1490
1553
|
catch (error) {
|
|
1491
|
-
|
|
1554
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1555
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
1556
|
+
? String(error.code ?? 'EXEC_FAILED')
|
|
1557
|
+
: 'EXEC_FAILED';
|
|
1558
|
+
logger.warn(`[MenuControl] deferred effect failed: request=${parsed.id} error=${detail}`);
|
|
1559
|
+
// File fetch is acknowledged before the upload completes. Send a
|
|
1560
|
+
// second response with the same request id so the client can finish
|
|
1561
|
+
// the pending fetch with an actionable error instead of timing out.
|
|
1562
|
+
const failedResponse = menuFailure(header, {
|
|
1563
|
+
code,
|
|
1564
|
+
message: detail.startsWith('文件发送失败') ? detail : `文件发送失败: ${detail}`,
|
|
1565
|
+
});
|
|
1566
|
+
await this.menuDeduper.replaceResult(dedupKey, menuPayloadFingerprint(parsed.raw), deduped.value, failedResponse);
|
|
1567
|
+
await this.sendMenuResponse(adapter, channel, msg, failedResponse);
|
|
1492
1568
|
}
|
|
1493
1569
|
}
|
|
1494
1570
|
}
|
|
@@ -1498,6 +1574,13 @@ export class MessageBridge {
|
|
|
1498
1574
|
const name = header.name ?? '';
|
|
1499
1575
|
const cmd = typeof request.cmd === 'string' ? request.cmd : undefined;
|
|
1500
1576
|
const args = request.args;
|
|
1577
|
+
const transportReplyContext = {
|
|
1578
|
+
...(msg.replyContext ?? {}),
|
|
1579
|
+
...(msg.threadId ? { threadId: msg.threadId } : {}),
|
|
1580
|
+
};
|
|
1581
|
+
// Explicit group context can target a different conversation. Its
|
|
1582
|
+
// sessions must not inherit the thread of the source conversation.
|
|
1583
|
+
const replyContextFor = (channelId) => channelId === msg.channelId ? transportReplyContext : undefined;
|
|
1501
1584
|
switch (request.type) {
|
|
1502
1585
|
case 'menu.list':
|
|
1503
1586
|
if (args?.view === 'catalog') {
|
|
@@ -1514,7 +1597,7 @@ export class MessageBridge {
|
|
|
1514
1597
|
case 'menu.query': {
|
|
1515
1598
|
if (name === 'menu' && args?.view === 'values') {
|
|
1516
1599
|
validateMenuValueBatchArgs(args);
|
|
1517
|
-
const data = await this.cmdHandler.execMenuValueBatch(channel, msg.channelId, authSubject, args, args.context, msg.isControlChannel ? 'control' : 'agent');
|
|
1600
|
+
const data = await this.cmdHandler.execMenuValueBatch(channel, msg.channelId, authSubject, args, args.context, msg.isControlChannel ? 'control' : 'agent', transportReplyContext);
|
|
1518
1601
|
return menuSuccess(header, data);
|
|
1519
1602
|
}
|
|
1520
1603
|
if (name === 'menu') {
|
|
@@ -1527,7 +1610,7 @@ export class MessageBridge {
|
|
|
1527
1610
|
channelId: msg.channelId,
|
|
1528
1611
|
args,
|
|
1529
1612
|
});
|
|
1530
|
-
const result = await this.cmdHandler.execMenuQuery(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject.identity, execution.subject);
|
|
1613
|
+
const result = await this.cmdHandler.execMenuQuery(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject.identity, execution.subject, 'menu', replyContextFor(execution.channelId));
|
|
1531
1614
|
if ('error' in result)
|
|
1532
1615
|
throw result;
|
|
1533
1616
|
return menuSuccess(header, result.data);
|
|
@@ -1544,7 +1627,7 @@ export class MessageBridge {
|
|
|
1544
1627
|
if ((name === 'topic' || cmd === '/topic') && args?.mode === 'fork-turns' && msg.threadId && !args?.sourceThreadId) {
|
|
1545
1628
|
optionArgs = { ...(execution.args ?? {}), sourceThreadId: msg.threadId };
|
|
1546
1629
|
}
|
|
1547
|
-
const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, optionArgs, execution.subject.identity, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject) ?? [];
|
|
1630
|
+
const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, optionArgs, execution.subject.identity, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject, 'menu', replyContextFor(execution.channelId)) ?? [];
|
|
1548
1631
|
return menuSuccess(header, data);
|
|
1549
1632
|
}
|
|
1550
1633
|
case 'menu.update': {
|
|
@@ -1555,10 +1638,7 @@ export class MessageBridge {
|
|
|
1555
1638
|
channelId: msg.channelId,
|
|
1556
1639
|
args,
|
|
1557
1640
|
});
|
|
1558
|
-
const result = await this.cmdHandler.execMenuUpdate(this.resolveCmd(name, cmd), request.value, channel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.fromControlChannel, execution.args, execution.subject, 'menu',
|
|
1559
|
-
...(msg.replyContext ?? {}),
|
|
1560
|
-
...(msg.threadId ? { threadId: msg.threadId } : {}),
|
|
1561
|
-
});
|
|
1641
|
+
const result = await this.cmdHandler.execMenuUpdate(this.resolveCmd(name, cmd), request.value, channel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.fromControlChannel, execution.args, execution.subject, 'menu', replyContextFor(execution.channelId));
|
|
1562
1642
|
if ('error' in result)
|
|
1563
1643
|
throw result;
|
|
1564
1644
|
return menuSuccess(header, result.data);
|
|
@@ -1621,9 +1701,11 @@ export class MessageBridge {
|
|
|
1621
1701
|
const agentName = this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>';
|
|
1622
1702
|
const replyContext = {
|
|
1623
1703
|
...(msg.replyContext ?? {}),
|
|
1704
|
+
...(msg.peerDeviceId ? { toDeviceId: msg.peerDeviceId } : {}),
|
|
1624
1705
|
metadata: {
|
|
1625
1706
|
...(msg.replyContext?.metadata ?? {}),
|
|
1626
|
-
...evolMenuResponseTransportMetadata(),
|
|
1707
|
+
...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version),
|
|
1708
|
+
...(isAgentStorageMenuRequest(request) ? { encrypted: true } : {}),
|
|
1627
1709
|
},
|
|
1628
1710
|
};
|
|
1629
1711
|
if (msg.channelType === 'aun' && !replyContext.delivery) {
|
|
@@ -1890,6 +1972,7 @@ export class MessageBridge {
|
|
|
1890
1972
|
}
|
|
1891
1973
|
/** 注销单个渠道的 debouncer(热重载断开渠道时调用) */
|
|
1892
1974
|
removeChannel(channelName) {
|
|
1975
|
+
this.groupInviteReviewService?.detach(channelName);
|
|
1893
1976
|
const d = this.debouncers.get(channelName);
|
|
1894
1977
|
if (d) {
|
|
1895
1978
|
d.dispose();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { appendJsonl, chatDirPath } from '../session/session-fs-store.js';
|
|
3
|
+
import { appendJsonl, appendJsonlDurable, chatDirPath } from '../session/session-fs-store.js';
|
|
4
4
|
import { logger } from '../../utils/logger.js';
|
|
5
5
|
const TRANSIENT_PROTOCOL_TYPE_PREFIXES = [
|
|
6
6
|
'contact.request.',
|
|
@@ -313,13 +313,13 @@ export function appendMessageLog(chatDir, entry) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
/** Strict conversation-history append used by handoff reply binding. */
|
|
316
|
-
export function appendMessageLogStrict(chatDir, entry) {
|
|
316
|
+
export function appendMessageLogStrict(chatDir, entry, durable = false) {
|
|
317
317
|
if (isTransientProtocolMessage(entry))
|
|
318
318
|
return;
|
|
319
319
|
if (entry.dir === 'in' && hasDuplicate(chatDir, entry.msgId))
|
|
320
320
|
return;
|
|
321
321
|
fs.mkdirSync(chatDir, { recursive: true });
|
|
322
|
-
appendJsonl(messageLogPath(chatDir), entry);
|
|
322
|
+
(durable ? appendJsonlDurable : appendJsonl)(messageLogPath(chatDir), entry);
|
|
323
323
|
if (entry.dir === 'in')
|
|
324
324
|
rememberMessageId(chatDir, entry.msgId);
|
|
325
325
|
}
|
|
@@ -343,6 +343,8 @@ export function buildInboundEntry(opts) {
|
|
|
343
343
|
msgType: isCommand ? 'command' : (opts.msgType ?? 'text'),
|
|
344
344
|
payloadType: opts.payloadType,
|
|
345
345
|
payloadSummary: opts.payloadSummary,
|
|
346
|
+
groupAttention: opts.groupAttention,
|
|
347
|
+
groupDelivery: opts.groupDelivery,
|
|
346
348
|
content: opts.content,
|
|
347
349
|
replyTo: opts.replyTo ?? null,
|
|
348
350
|
agent: null,
|