evolcore 0.0.31 → 0.0.33
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 +35 -0
- package/bin/install-codex-managed-hooks.mjs +124 -37
- package/dist/agents/claude-runner.js +29 -2
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +76 -44
- package/dist/agents/codex-runner.js +51 -24
- package/dist/agents/ecagent-runner.js +9 -13
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +38 -137
- package/dist/channels/daemon.js +257 -2
- package/dist/cli/agent-command.js +64 -31
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +16 -70
- package/dist/cli/aun-commands.js +512 -32
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +82 -13
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +18 -5
- package/dist/cli/index.js +81 -64
- package/dist/cli/init.js +2 -2
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +93 -12
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +51 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +85 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +16 -0
- package/dist/cli/thread-command.js +620 -0
- package/dist/cli/trigger-command.js +76 -50
- package/dist/config/builtin-roles.js +9 -7
- package/dist/config/config-field-policy.js +2 -3
- package/dist/config/config-manager.js +7 -2
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +15 -0
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +42 -2
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-authorizer.js +24 -1
- package/dist/core/auth/operation-catalog.js +44 -3
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/command-handler.js +224 -45
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +48 -1
- package/dist/core/command/menu-handler.js +92 -16
- package/dist/core/command/menu-protocol.js +24 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +193 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +210 -9
- package/dist/core/memory/consolidation-context.js +49 -16
- package/dist/core/memory/consolidation-ops-records.js +35 -0
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +174 -180
- package/dist/core/message/aun-ipc-sender.js +207 -8
- package/dist/core/message/im-renderer.js +149 -6
- package/dist/core/message/message-bridge.js +13 -3
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +379 -69
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +126 -7
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +54 -9
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +4 -1
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +777 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +10 -7
- package/dist/ipc.js +234 -17
- package/dist/paths.js +23 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/trigger/authorization.js +59 -29
- package/dist/trigger/feedback.js +7 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/parser.js +63 -1
- package/dist/trigger/script-sandbox.js +58 -77
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/error-utils.js +22 -1
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +39 -28
- package/kits/docs/evolcore/agent.md +19 -4
- package/kits/docs/evolcore/ec-capabilities.md +4 -0
- package/kits/docs/evolcore/group-collaboration.md +3 -5
- package/kits/docs/evolcore/group.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +1 -1
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/msg.md +5 -8
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/eck_manifest.json +61 -26
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +7 -1
- package/kits/rules/07-memory-and-thread.md +202 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +5 -0
- package/kits/templates/memory-consolidation/generalize.md +5 -0
- package/kits/templates/memory-consolidation/profile.md +5 -0
- package/kits/templates/memory-consolidation/promote.md +5 -0
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +66 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +2 -1
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/reply.md +3 -2
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +3 -1
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -12,20 +12,18 @@ const STATE_KEY = 'proactive';
|
|
|
12
12
|
const SNAPSHOT_KEY = 'proactive:continuation';
|
|
13
13
|
const OWNER_KEY = 'proactive:owner';
|
|
14
14
|
const SNAPSHOT_MAX_AGE_MS = 30 * 60 * 1_000;
|
|
15
|
-
|
|
15
|
+
/** 计数器初值与提示档位:计数 = 该值时放行并提示,> 该值时只放行意图表达 */
|
|
16
|
+
export const INTENT_GATE_INTERVAL = 15;
|
|
17
|
+
const TOOL_REPORT_INTERVAL = INTENT_GATE_INTERVAL;
|
|
18
|
+
/** 队列未读提醒长度跨轮次持久化的 sessionState key(问题3修复) */
|
|
19
|
+
const QUEUE_REMINDER_SESSION_KEY = 'proactive:lastQueueReminderLen';
|
|
20
|
+
/** 熔断已触发标记,存 sessionState 供 afterProcess 发渠道消息(F5修复) */
|
|
21
|
+
const CIRCUIT_BREAK_SESSION_KEY = 'proactive:circuitBreak';
|
|
22
|
+
/** 单轮内允许的最大违规次数;超过后触发熔断而非继续注入提醒(问题1/2修复) */
|
|
23
|
+
export const VIOLATION_CIRCUIT_BREAK_THRESHOLD = 2;
|
|
16
24
|
const SEND_RECEIPT_RE = /["']?(?:message[_ -]?id|handoff[_ -]?id)["']?\s*[:=]\s*["']?([A-Za-z0-9][A-Za-z0-9._:-]*)/gi;
|
|
17
25
|
const HANDOFF_TEXT_RECEIPT_RE = /\bhandoff\s+(h-[A-Za-z0-9._-]+)/i;
|
|
18
26
|
const RETURN_CONTENT_RE = /["']?(?:return[_ -]?content|returnContent)["']?\s*[:=]\s*["']?([^}\n,]*)/i;
|
|
19
|
-
function hasRequiredReturnFlag(toolInput) {
|
|
20
|
-
if (!toolInput || typeof toolInput !== 'object')
|
|
21
|
-
return false;
|
|
22
|
-
const input = toolInput;
|
|
23
|
-
const commandValues = ['command', 'cmd', 'script'].flatMap(key => {
|
|
24
|
-
const value = input[key];
|
|
25
|
-
return typeof value === 'string' ? [value] : [];
|
|
26
|
-
});
|
|
27
|
-
return commandValues.some(command => /(?:^|\s)--return(?:=|\s+)['"]?required(?:['"]?)(?=\s|$)/i.test(command));
|
|
28
|
-
}
|
|
29
27
|
function hasSuccessfulSendReceipt(result, isError, requiresBusinessReturn = false) {
|
|
30
28
|
if (isError === true)
|
|
31
29
|
return false;
|
|
@@ -137,6 +135,57 @@ function hasSuccessfulSendReceipt(result, isError, requiresBusinessReturn = fals
|
|
|
137
135
|
return true;
|
|
138
136
|
return false;
|
|
139
137
|
}
|
|
138
|
+
function sendHint(state) {
|
|
139
|
+
return state.chatType === 'group'
|
|
140
|
+
? { cmdHint: 'ec group send', target: '群里' }
|
|
141
|
+
: { cmdHint: 'ec msg send', target: '对方' };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 结算当前轮:本轮有意图表达成功 → 清零(不再 +1);否则 +1。
|
|
145
|
+
* 被拒绝的调用不会产生成功回执,因此不能触发清零。
|
|
146
|
+
*/
|
|
147
|
+
function settleRound(state) {
|
|
148
|
+
if (!state.roundOpen)
|
|
149
|
+
return;
|
|
150
|
+
state.roundOpen = false;
|
|
151
|
+
state.roundPending = 0;
|
|
152
|
+
state.roundCounter = state.roundHasIntentSuccess ? 0 : state.roundCounter + 1;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 进入一轮。turn 为 runner 提供的模型返回序号:
|
|
156
|
+
* - 有 turn:同一 turn 视为同一轮;turn 变化时先结算上一轮
|
|
157
|
+
* - 无 turn(runner 不支持):已有未结算的轮则沿用,结算由飞行数归零触发
|
|
158
|
+
*/
|
|
159
|
+
function beginRound(state, turn) {
|
|
160
|
+
if (typeof turn === 'number' && Number.isFinite(turn)) {
|
|
161
|
+
state.turnAware = true;
|
|
162
|
+
// 先由拿不到 turn 的调用开了轮:认领这个 turn,而不是当成新轮结算
|
|
163
|
+
if (state.roundOpen && state.roundTurn === undefined) {
|
|
164
|
+
state.roundTurn = turn;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (state.roundOpen && state.roundTurn === turn)
|
|
168
|
+
return;
|
|
169
|
+
settleRound(state);
|
|
170
|
+
}
|
|
171
|
+
else if (state.roundOpen) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
state.roundOpen = true;
|
|
175
|
+
state.roundTurn = typeof turn === 'number' ? turn : undefined;
|
|
176
|
+
state.roundStartCounter = state.roundCounter;
|
|
177
|
+
state.roundPending = 0;
|
|
178
|
+
state.roundHasIntentAttempt = false;
|
|
179
|
+
state.roundHasIntentSuccess = false;
|
|
180
|
+
state.roundWarned = false;
|
|
181
|
+
}
|
|
182
|
+
/** 新输入(新消息、插入消息、队列合并、续跑、任务恢复)到来:计数重置为初值 */
|
|
183
|
+
export function resetIntentCounter(state) {
|
|
184
|
+
state.roundOpen = false;
|
|
185
|
+
state.roundPending = 0;
|
|
186
|
+
state.roundTurn = undefined;
|
|
187
|
+
state.roundCounter = INTENT_GATE_INTERVAL;
|
|
188
|
+
}
|
|
140
189
|
function isHumanFacingMessage(message) {
|
|
141
190
|
if (message.source === 'trigger')
|
|
142
191
|
return false;
|
|
@@ -161,7 +210,17 @@ function buildState(message, cfg) {
|
|
|
161
210
|
firstSendRequired: preTool1stMsgChk && humanFacing,
|
|
162
211
|
toolReportRequired: toolUseReminder && humanFacing,
|
|
163
212
|
toolReportInterval: TOOL_REPORT_INTERVAL,
|
|
164
|
-
|
|
213
|
+
violationCount: 0,
|
|
214
|
+
intentGateRequired: (preTool1stMsgChk || toolUseReminder) && humanFacing,
|
|
215
|
+
roundCounter: INTENT_GATE_INTERVAL,
|
|
216
|
+
roundOpen: false,
|
|
217
|
+
roundStartCounter: INTENT_GATE_INTERVAL,
|
|
218
|
+
roundTurn: undefined,
|
|
219
|
+
turnAware: false,
|
|
220
|
+
roundPending: 0,
|
|
221
|
+
roundHasIntentAttempt: false,
|
|
222
|
+
roundHasIntentSuccess: false,
|
|
223
|
+
roundWarned: false,
|
|
165
224
|
};
|
|
166
225
|
}
|
|
167
226
|
function normalizedThread(threadId) {
|
|
@@ -169,7 +228,10 @@ function normalizedThread(threadId) {
|
|
|
169
228
|
}
|
|
170
229
|
function isResumableTerminal(terminal) {
|
|
171
230
|
return terminal?.status === 'interrupted' && terminal.reason === 'daemon_restart'
|
|
172
|
-
|| terminal?.status === 'cancelled' && (terminal.reason === 'stop'
|
|
231
|
+
|| terminal?.status === 'cancelled' && (terminal.reason === 'stop'
|
|
232
|
+
|| terminal.reason === 'explicit_cancel'
|
|
233
|
+
// 熔断中断走 agent.interrupt() 默认路径,reason 为 explicit_cancel,此处已覆盖
|
|
234
|
+
);
|
|
173
235
|
}
|
|
174
236
|
function canRestoreSnapshot(ctx, state, snapshot) {
|
|
175
237
|
const previous = ctx.previousTurnState;
|
|
@@ -182,7 +244,7 @@ function canRestoreSnapshot(ctx, state, snapshot) {
|
|
|
182
244
|
const age = Date.now() - snapshot.finishedAt;
|
|
183
245
|
if (!Number.isFinite(age) || age < 0 || age > SNAPSHOT_MAX_AGE_MS
|
|
184
246
|
|| !Number.isSafeInteger(snapshot.toolCount) || snapshot.toolCount < 0
|
|
185
|
-
|| typeof snapshot.firstToolDone !== 'boolean'
|
|
247
|
+
|| typeof snapshot.firstToolDone !== 'boolean'
|
|
186
248
|
|| snapshot.sessionId !== ctx.session.id || snapshot.selfAid !== ctx.session.selfAID
|
|
187
249
|
|| snapshot.channel !== ctx.session.channel || snapshot.channelId !== ctx.session.channelId
|
|
188
250
|
|| snapshot.chatType !== ctx.message.chatType || snapshot.chatType !== ctx.session.chatType
|
|
@@ -230,10 +292,10 @@ function saveContinuation(ctx) {
|
|
|
230
292
|
finishedAt: terminal.finishedAt,
|
|
231
293
|
firstToolDone: state.firstToolDone,
|
|
232
294
|
toolCount: state.toolCount,
|
|
233
|
-
toolReportPending: state.toolReportPending,
|
|
234
295
|
firstSendRequired: state.firstSendRequired,
|
|
235
296
|
toolReportRequired: state.toolReportRequired,
|
|
236
297
|
toolReportInterval: state.toolReportInterval,
|
|
298
|
+
violationCount: state.violationCount, // F4修复:跨续跑保留违规计数
|
|
237
299
|
};
|
|
238
300
|
ctx.sessionState.set(SNAPSHOT_KEY, snapshot);
|
|
239
301
|
}
|
|
@@ -265,10 +327,25 @@ export const proactiveFlow = {
|
|
|
265
327
|
const state = buildState(ctx.message, ctx.modeConfig ?? {});
|
|
266
328
|
const snapshot = ctx.sessionState?.get(SNAPSHOT_KEY);
|
|
267
329
|
ctx.sessionState?.delete(SNAPSHOT_KEY);
|
|
268
|
-
|
|
330
|
+
const snapshotRestored = snapshot && canRestoreSnapshot(ctx, state, snapshot);
|
|
331
|
+
if (snapshotRestored) {
|
|
269
332
|
state.firstToolDone = snapshot.firstToolDone;
|
|
270
333
|
state.toolCount = snapshot.toolCount;
|
|
271
|
-
|
|
334
|
+
// F4修复:跨续跑恢复违规计数,防止每次续跑都重置熔断配额
|
|
335
|
+
if (typeof snapshot.violationCount === 'number')
|
|
336
|
+
state.violationCount = snapshot.violationCount;
|
|
337
|
+
// 轮次计数不恢复:续跑/任务恢复属于新输入,buildState 已置为初值
|
|
338
|
+
}
|
|
339
|
+
else if (snapshot) {
|
|
340
|
+
// F8修复:快照存在但恢复失败(channel/chatType 变化等),
|
|
341
|
+
// 状态已不连续,清除过期的队列提醒长度,避免错误抑制新轮次的提醒
|
|
342
|
+
ctx.sessionState?.delete(QUEUE_REMINDER_SESSION_KEY);
|
|
343
|
+
}
|
|
344
|
+
// 问题3修复:从 sessionState 恢复跨轮次的队列提醒长度,
|
|
345
|
+
// 避免每轮重置为 0 后队列 ≥ 3 就反复触发未读提醒。
|
|
346
|
+
const persistedQueueLen = ctx.sessionState?.get(QUEUE_REMINDER_SESSION_KEY);
|
|
347
|
+
if (typeof persistedQueueLen === 'number' && persistedQueueLen > 0) {
|
|
348
|
+
state.lastQueueReminderLen = persistedQueueLen;
|
|
272
349
|
}
|
|
273
350
|
ctx.state.set(STATE_KEY, state);
|
|
274
351
|
ctx.sessionState?.set(OWNER_KEY, state);
|
|
@@ -279,109 +356,121 @@ export const proactiveFlow = {
|
|
|
279
356
|
if (!state)
|
|
280
357
|
return undefined;
|
|
281
358
|
return {
|
|
282
|
-
policyHook: (toolName, toolInput) => {
|
|
359
|
+
policyHook: (toolName, toolInput, meta) => {
|
|
283
360
|
// Codex only invokes this hook for local tool paths. Hosted tools stay
|
|
284
|
-
// enabled and do not satisfy
|
|
361
|
+
// enabled and do not satisfy the intent gate.
|
|
285
362
|
const isSendCommand = ctx.isSendCommand(toolName, toolInput);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
363
|
+
const { cmdHint, target } = sendHint(state);
|
|
364
|
+
beginRound(state, meta?.turn);
|
|
365
|
+
// 意图表达工具始终放行;是否清零看执行结果(onToolResult)
|
|
366
|
+
if (isSendCommand) {
|
|
367
|
+
state.roundHasIntentAttempt = true;
|
|
368
|
+
return undefined;
|
|
290
369
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
policyCode: 'proactive_first_send_required',
|
|
298
|
-
reason: `请先用 ${cmdHint} 向${target}说明你的意图,再执行其他工具`,
|
|
299
|
-
};
|
|
370
|
+
// 非 send 命令:先检查格式是否"像 send 但解析失败"(F2修复:计入违规)
|
|
371
|
+
const sendFailure = ctx.describeSendCommandFailure?.(toolName, toolInput);
|
|
372
|
+
if (sendFailure) {
|
|
373
|
+
state.violationCount++;
|
|
374
|
+
if (state.violationCount >= VIOLATION_CIRCUIT_BREAK_THRESHOLD) {
|
|
375
|
+
return { block: true, policyCode: 'proactive_circuit_break', reason: `连续违规 ${state.violationCount} 次,任务已中断,请重新描述需求` };
|
|
300
376
|
}
|
|
377
|
+
return { block: true, policyCode: 'proactive_send_invalid', reason: sendFailure };
|
|
301
378
|
}
|
|
302
|
-
if (state.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
379
|
+
if (!state.intentGateRequired)
|
|
380
|
+
return undefined;
|
|
381
|
+
// 计数 < 15:本条消息内已有意图表达,放行
|
|
382
|
+
if (state.roundStartCounter < INTENT_GATE_INTERVAL)
|
|
383
|
+
return undefined;
|
|
384
|
+
// 计数 = 15:全部放行,提示下一轮先做意图表达(本轮已有发送则不提示)
|
|
385
|
+
if (state.roundStartCounter === INTENT_GATE_INTERVAL) {
|
|
386
|
+
if (!state.roundHasIntentAttempt && !state.roundWarned) {
|
|
387
|
+
state.roundWarned = true;
|
|
388
|
+
state.pendingRoundReminder = `⚠️ 本轮未包含意图表达,下一轮必须先用 ${cmdHint} 向${target}说明当前情况和下一步意图,否则其他工具会被拒绝。`;
|
|
311
389
|
}
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
// 计数 ≥ 16:只放行意图表达,其他调用全部拒绝
|
|
393
|
+
state.violationCount++;
|
|
394
|
+
if (state.violationCount >= VIOLATION_CIRCUIT_BREAK_THRESHOLD) {
|
|
395
|
+
return { block: true, policyCode: 'proactive_circuit_break', reason: `连续违规 ${state.violationCount} 次,任务已中断,请重新描述需求` };
|
|
312
396
|
}
|
|
313
|
-
return
|
|
397
|
+
return {
|
|
398
|
+
block: true,
|
|
399
|
+
policyCode: 'proactive_intent_required',
|
|
400
|
+
reason: `上一轮未包含意图表达,本轮只允许 ${cmdHint};请先向${target}说明当前情况和下一步意图,再执行其他工具`,
|
|
401
|
+
};
|
|
314
402
|
},
|
|
315
403
|
};
|
|
316
404
|
},
|
|
317
|
-
// ─── onToolUse
|
|
405
|
+
// ─── onToolUse:轮次计数 + 队列未读提醒 + 工具汇报提醒 ───
|
|
318
406
|
onToolUse(ctx) {
|
|
319
407
|
const state = ctx.state.get(STATE_KEY);
|
|
320
408
|
if (!state)
|
|
321
409
|
return;
|
|
322
|
-
//
|
|
323
|
-
|
|
410
|
+
// 轮次追踪:按模型返回序号进轮,每次工具调用开始时 pending+1
|
|
411
|
+
beginRound(state, ctx.modelTurn);
|
|
412
|
+
state.roundPending++;
|
|
413
|
+
// 15 档提示由 policyHook 写入,这里注入给模型
|
|
414
|
+
if (state.pendingRoundReminder) {
|
|
415
|
+
ctx.injectToModel(state.pendingRoundReminder);
|
|
416
|
+
ctx.recordReminder?.({ kind: 'tool_report', text: state.pendingRoundReminder, toolCount: state.roundStartCounter });
|
|
417
|
+
state.pendingRoundReminder = undefined;
|
|
418
|
+
}
|
|
324
419
|
const isAllowedTool = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
|
|
325
420
|
if (!state.toolUseReminder)
|
|
326
421
|
return;
|
|
327
|
-
//
|
|
422
|
+
// 非发送工具才计入 toolCount(仅审计)
|
|
328
423
|
if (state.toolReportRequired && !isAllowedTool)
|
|
329
424
|
state.toolCount++;
|
|
330
|
-
//
|
|
331
|
-
// 避免连续工具调用时每次队列数量变化都重复注入。
|
|
425
|
+
// 队列未读变化提醒
|
|
332
426
|
const queueLen = ctx.getQueueLength();
|
|
333
427
|
const queueChanged = queueLen !== state.lastQueueReminderLen;
|
|
334
|
-
const toolReportDue = state.toolReportRequired
|
|
335
|
-
&& !isAllowedTool
|
|
336
|
-
&& state.toolCount > 0
|
|
337
|
-
&& state.toolCount % state.toolReportInterval === 0;
|
|
338
428
|
let queueReminder;
|
|
339
429
|
const queueReminderDue = state.lastQueueReminderLen === 0
|
|
340
430
|
? queueLen >= 3
|
|
341
|
-
:
|
|
431
|
+
: false; // 汇报间隔改由轮次控制
|
|
342
432
|
if (queueLen === 0) {
|
|
343
433
|
state.lastQueueReminderLen = 0;
|
|
434
|
+
ctx.sessionState?.delete(QUEUE_REMINDER_SESSION_KEY);
|
|
344
435
|
}
|
|
345
436
|
else if (queueChanged && queueReminderDue) {
|
|
346
437
|
state.lastQueueReminderLen = queueLen;
|
|
438
|
+
ctx.sessionState?.set(QUEUE_REMINDER_SESSION_KEY, queueLen);
|
|
347
439
|
queueReminder = queueLen >= 5
|
|
348
440
|
? `⚠️ 有 ${queueLen} 条消息未读,请尽快完成当前任务,或使用 ec ctl queue 读取后同步处理。`
|
|
349
441
|
: `⚠️ 有 ${queueLen} 条消息未读,可使用 ec ctl queue 读取。`;
|
|
350
442
|
ctx.recordReminder?.({ kind: 'queue_unread', text: queueReminder, queueLength: queueLen });
|
|
351
|
-
}
|
|
352
|
-
if (queueReminder && !toolReportDue) {
|
|
353
443
|
ctx.injectToModel(queueReminder);
|
|
354
444
|
}
|
|
355
|
-
// 每 N 次工具调用警告
|
|
356
|
-
// 只有计数推进到新阈值时才提醒;发送工具不能重新打开已解除的汇报门禁。
|
|
357
|
-
if (toolReportDue) {
|
|
358
|
-
state.toolReportPending = true;
|
|
359
|
-
const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
|
|
360
|
-
const target = state.chatType === 'group' ? '群里' : '对方';
|
|
361
|
-
const reportMsg = `⚠️ 工具调用已达到 ${state.toolCount} 次,请立即用 ${cmdHint} 向${target}汇报当前情况和下一步意图。`;
|
|
362
|
-
ctx.recordReminder?.({ kind: 'tool_report', text: reportMsg, toolCount: state.toolCount });
|
|
363
|
-
ctx.injectToModel(queueReminder ? `${queueReminder}\n${reportMsg}` : reportMsg);
|
|
364
|
-
}
|
|
365
445
|
},
|
|
366
|
-
// ─── onToolResult
|
|
446
|
+
// ─── onToolResult:轮次推进 + 成功发送回执后更新标志位 ───
|
|
367
447
|
onToolResult(ctx) {
|
|
368
448
|
const state = ctx.state.get(STATE_KEY);
|
|
369
449
|
if (!state)
|
|
370
450
|
return;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
state.
|
|
451
|
+
const isSend = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
|
|
452
|
+
// tool_use 事件可能先于 PreToolUse 到达,15 档提示在这里补注入
|
|
453
|
+
if (state.pendingRoundReminder) {
|
|
454
|
+
ctx.injectToModel(state.pendingRoundReminder);
|
|
455
|
+
ctx.recordReminder?.({ kind: 'tool_report', text: state.pendingRoundReminder, toolCount: state.roundStartCounter });
|
|
456
|
+
state.pendingRoundReminder = undefined;
|
|
457
|
+
}
|
|
458
|
+
// 先记成功,再结算:被拒绝/失败的调用不能触发清零
|
|
459
|
+
const succeeded = !ctx.isError && !ctx.error
|
|
460
|
+
&& ctx.executionState !== 'blocked' && ctx.executionState !== 'failed'
|
|
461
|
+
&& ctx.decision !== 'deny' && ctx.executed !== false;
|
|
462
|
+
if (isSend && succeeded) {
|
|
463
|
+
state.roundHasIntentSuccess = true;
|
|
464
|
+
if (!state.firstToolDone)
|
|
465
|
+
state.firstToolDone = true;
|
|
466
|
+
state.violationCount = 0;
|
|
467
|
+
}
|
|
468
|
+
// 轮次追踪:pending-1。拿得到模型返回序号时由下一轮 beginRound 结算;
|
|
469
|
+
// 拿不到时(runner 不提供 turn)退回飞行数归零结算
|
|
470
|
+
if (state.roundPending > 0)
|
|
471
|
+
state.roundPending--;
|
|
472
|
+
if (state.roundPending === 0 && !state.turnAware)
|
|
473
|
+
settleRound(state);
|
|
385
474
|
},
|
|
386
475
|
// ─── onComplete:标志位检查 ───
|
|
387
476
|
async onComplete(ctx) {
|
|
@@ -8,6 +8,7 @@ import { validateModelSelectionForRole } from '../core/model/model-permission.js
|
|
|
8
8
|
import { isValidAunDeliveryForChannel } from '../core/message/outbound.js';
|
|
9
9
|
import { tryParseChannelKey } from '../core/channel-loader.js';
|
|
10
10
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from '../core/role/runtime-policy.js';
|
|
11
|
+
import { resolveThreadRouteTarget } from '../core/thread/thread-route.js';
|
|
11
12
|
export function originFromActorSession(session, authenticatedPeerId, authenticatedChannelKey, authenticatedChatType) {
|
|
12
13
|
const peerId = authenticatedPeerId || session.metadata?.peerId;
|
|
13
14
|
if (!peerId)
|
|
@@ -15,16 +16,21 @@ export function originFromActorSession(session, authenticatedPeerId, authenticat
|
|
|
15
16
|
const sessionChannelKey = session.metadata?.channelKey || session.channel;
|
|
16
17
|
const channelKey = authenticatedChannelKey || sessionChannelKey;
|
|
17
18
|
const sameConversation = channelKey === sessionChannelKey;
|
|
19
|
+
if (authenticatedChatType && (authenticatedChatType === 'group') !== (session.chatType === 'group')) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
18
22
|
const isGroup = authenticatedChatType === 'group' || session.chatType === 'group';
|
|
23
|
+
const channelId = isGroup ? session.channelId : peerId;
|
|
24
|
+
const sessionKind = sameConversation && session.threadId ? 'thread' : 'main';
|
|
19
25
|
return {
|
|
20
26
|
channelKey,
|
|
21
27
|
channelType: session.channelType || tryParseChannelKey(channelKey)?.type || channelKey,
|
|
22
|
-
channelId
|
|
23
|
-
session:
|
|
24
|
-
threadId:
|
|
28
|
+
channelId,
|
|
29
|
+
session: sessionKind,
|
|
30
|
+
threadId: sessionKind === 'thread' ? session.threadId : undefined,
|
|
25
31
|
peerId,
|
|
26
|
-
sessionKey: sameConversation
|
|
27
|
-
delivery: { chatType: 'private' },
|
|
32
|
+
sessionKey: sameConversation ? session.sessionKey : undefined,
|
|
33
|
+
delivery: isGroup ? { chatType: 'group', groupId: channelId } : { chatType: 'private' },
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
export function daemonPrivilegedTriggerReason(definition) {
|
|
@@ -76,6 +82,36 @@ export function validateTriggerFeedbackChannels(definition, getTriggerChannel) {
|
|
|
76
82
|
export function hasDaemonTriggerAuthority(subject) {
|
|
77
83
|
return subject.isDaemonOwner || subject.processRole === 'daemon-service' || subject.dataScope === 'daemon';
|
|
78
84
|
}
|
|
85
|
+
function triggerOriginChatType(origin) {
|
|
86
|
+
return origin.delivery?.chatType ?? 'private';
|
|
87
|
+
}
|
|
88
|
+
function feedbackTargetMatchesOrigin(target, origin) {
|
|
89
|
+
return target.channelKey === origin.channelKey
|
|
90
|
+
&& target.channelId === origin.channelId
|
|
91
|
+
&& target.session === origin.session
|
|
92
|
+
&& (target.threadId ?? '') === (origin.threadId ?? '')
|
|
93
|
+
&& (target.chatType === undefined || target.chatType === triggerOriginChatType(origin));
|
|
94
|
+
}
|
|
95
|
+
function feedbackTargetIsSamePeerThread(definition, target, origin) {
|
|
96
|
+
if (!target || !origin || target.session !== 'thread' || !target.threadId)
|
|
97
|
+
return false;
|
|
98
|
+
if (target.channelKey !== origin.channelKey || target.channelId !== origin.channelId)
|
|
99
|
+
return false;
|
|
100
|
+
const originChatType = triggerOriginChatType(origin);
|
|
101
|
+
if (target.chatType !== originChatType)
|
|
102
|
+
return false;
|
|
103
|
+
return !!resolveThreadRouteTarget(definition.agentAid, target.threadId, target.chatType === 'group' ? 'group' : 'msg', target.channelId);
|
|
104
|
+
}
|
|
105
|
+
function feedbackTargetAllowedForNonManagement(definition, origin) {
|
|
106
|
+
const target = definition.feedback.target;
|
|
107
|
+
if (!target || !origin)
|
|
108
|
+
return false;
|
|
109
|
+
if (feedbackTargetMatchesOrigin(target, origin))
|
|
110
|
+
return true;
|
|
111
|
+
if (target.chatType === 'group')
|
|
112
|
+
return false;
|
|
113
|
+
return feedbackTargetIsSamePeerThread(definition, target, origin);
|
|
114
|
+
}
|
|
79
115
|
export function validateTriggerDefinitionForActor(definition, actor, dependencies) {
|
|
80
116
|
if (definition.execution.permissionMode === 'fullaccess') {
|
|
81
117
|
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
@@ -127,14 +163,8 @@ export function validateTriggerDefinitionForActor(definition, actor, dependencie
|
|
|
127
163
|
throw new Error('event-session feedback requires owner/admin');
|
|
128
164
|
}
|
|
129
165
|
if (!actor.management && definition.feedback.strategy === 'target') {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!target || !origin
|
|
133
|
-
|| target.channelKey !== origin.channelKey
|
|
134
|
-
|| target.channelId !== origin.channelId
|
|
135
|
-
|| target.session !== origin.session
|
|
136
|
-
|| (target.threadId ?? '') !== (origin.threadId ?? '')) {
|
|
137
|
-
throw new Error('non-management trigger target must be the creator private conversation');
|
|
166
|
+
if (!feedbackTargetAllowedForNonManagement(definition, actor.origin)) {
|
|
167
|
+
throw new Error('non-management trigger target must be the creator conversation or an existing same-peer private thread');
|
|
138
168
|
}
|
|
139
169
|
}
|
|
140
170
|
}
|
|
@@ -187,13 +217,15 @@ export function authorizeTriggerExecution(definition, dependencies) {
|
|
|
187
217
|
const channelType = origin.channelType || tryParseChannelKey(origin.channelKey)?.type;
|
|
188
218
|
if (!channelType)
|
|
189
219
|
return { allowed: false, reason: 'trigger origin channel type is missing' };
|
|
220
|
+
const chatType = triggerOriginChatType(origin);
|
|
221
|
+
const conversationId = chatType === 'group' ? origin.channelId : origin.peerId;
|
|
190
222
|
const roleDetail = resolvePeerRoleDetail({
|
|
191
223
|
selfAid: definition.agentAid,
|
|
192
224
|
channelKey: origin.channelKey,
|
|
193
225
|
channelType,
|
|
194
|
-
chatType
|
|
226
|
+
chatType,
|
|
195
227
|
actorId: origin.peerId,
|
|
196
|
-
conversationId
|
|
228
|
+
conversationId,
|
|
197
229
|
});
|
|
198
230
|
const role = roleDetail.effectiveRole;
|
|
199
231
|
if (!checkRoleAccess(role, definition.agentAid)) {
|
|
@@ -205,8 +237,8 @@ export function authorizeTriggerExecution(definition, dependencies) {
|
|
|
205
237
|
channel: origin.channelKey,
|
|
206
238
|
channelType,
|
|
207
239
|
channelId: origin.channelId,
|
|
208
|
-
chatType
|
|
209
|
-
conversationId
|
|
240
|
+
chatType,
|
|
241
|
+
conversationId,
|
|
210
242
|
roleDetail,
|
|
211
243
|
identity: roleToSessionIdentity(role),
|
|
212
244
|
processOwners: dependencies.getDaemonOwners(),
|
|
@@ -236,18 +268,13 @@ export function authorizeTriggerExecution(definition, dependencies) {
|
|
|
236
268
|
return { allowed: false, reason: 'event-session feedback requires current owner/admin role' };
|
|
237
269
|
}
|
|
238
270
|
if (definition.feedback.strategy === 'target' && !isManagementRole(role)) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|| target.channelKey !== origin.channelKey
|
|
242
|
-
|| target.channelId !== origin.channelId
|
|
243
|
-
|| target.session !== origin.session
|
|
244
|
-
|| (target.threadId ?? '') !== (origin.threadId ?? '')) {
|
|
245
|
-
return { allowed: false, reason: `cross-conversation target requires current owner/admin role (role=${role ?? 'none'})` };
|
|
271
|
+
if (!feedbackTargetAllowedForNonManagement(definition, origin)) {
|
|
272
|
+
return { allowed: false, reason: `cross-conversation target requires current owner/admin role or an existing same-peer private thread (role=${role ?? 'none'})` };
|
|
246
273
|
}
|
|
247
274
|
}
|
|
248
275
|
const permissionDecision = constrainRuntimePermissionMode({
|
|
249
276
|
selfAid: definition.agentAid,
|
|
250
|
-
role,
|
|
277
|
+
role: typeof role === 'string' ? role : undefined,
|
|
251
278
|
requestedValue: definition.execution.permissionMode,
|
|
252
279
|
});
|
|
253
280
|
if (definition.execution.permissionMode && permissionDecision.constrained) {
|
|
@@ -260,6 +287,7 @@ export function authorizeTriggerExecution(definition, dependencies) {
|
|
|
260
287
|
allowed: true,
|
|
261
288
|
scope: privilegedReason && hasDaemonTriggerAuthority(subject) ? 'daemon' : 'agent',
|
|
262
289
|
permissionMode: permissionDecision.effectiveValue,
|
|
290
|
+
role: role ?? undefined,
|
|
263
291
|
};
|
|
264
292
|
}
|
|
265
293
|
export async function authenticatedTriggerActor(dependencies, agentAid, actorSessionId, delegationToken, delegationCommandHash, controlToken, contextOnly = false) {
|
|
@@ -313,13 +341,15 @@ export async function authenticatedTriggerActor(dependencies, agentAid, actorSes
|
|
|
313
341
|
if (validation.grant.actorId !== actorOrigin.peerId) {
|
|
314
342
|
throw new Error('trigger delegation actor does not match session peer');
|
|
315
343
|
}
|
|
344
|
+
const actorChatType = triggerOriginChatType(actorOrigin);
|
|
345
|
+
const actorConversationId = actorChatType === 'group' ? actorOrigin.channelId : actorOrigin.peerId;
|
|
316
346
|
const roleDetail = resolvePeerRoleDetail({
|
|
317
347
|
selfAid: agentAid,
|
|
318
348
|
channelKey: validation.grant.channel,
|
|
319
349
|
channelType: validation.grant.channelType,
|
|
320
|
-
chatType:
|
|
350
|
+
chatType: actorChatType,
|
|
321
351
|
actorId: validation.grant.actorId,
|
|
322
|
-
conversationId:
|
|
352
|
+
conversationId: actorConversationId,
|
|
323
353
|
});
|
|
324
354
|
const currentRole = roleDetail.effectiveRole;
|
|
325
355
|
const subject = buildAuthSubject({
|
|
@@ -328,8 +358,8 @@ export async function authenticatedTriggerActor(dependencies, agentAid, actorSes
|
|
|
328
358
|
channel: validation.grant.channel,
|
|
329
359
|
channelType: validation.grant.channelType,
|
|
330
360
|
channelId: actorOrigin.channelId,
|
|
331
|
-
chatType:
|
|
332
|
-
conversationId:
|
|
361
|
+
chatType: actorChatType,
|
|
362
|
+
conversationId: actorConversationId,
|
|
333
363
|
roleDetail,
|
|
334
364
|
identity: roleToSessionIdentity(currentRole),
|
|
335
365
|
processOwners: dependencies.getDaemonOwners(),
|
package/dist/trigger/feedback.js
CHANGED
|
@@ -272,10 +272,16 @@ export class TriggerFeedbackDispatcher {
|
|
|
272
272
|
originChannelKey: eventSession ? target.channelKey : input.trigger.origin?.channelKey,
|
|
273
273
|
originChannelType: eventSession ? channelType : input.trigger.origin?.channelType
|
|
274
274
|
?? (input.trigger.origin?.channelKey ? channelTypeFromKey(input.trigger.origin.channelKey) : undefined),
|
|
275
|
-
|
|
275
|
+
originChatType: eventSession ? messageChatType : input.trigger.origin?.delivery?.chatType,
|
|
276
|
+
originConversationId: eventSession
|
|
277
|
+
? target.channelId
|
|
278
|
+
: input.trigger.origin?.delivery?.chatType === 'group'
|
|
279
|
+
? input.trigger.origin.channelId
|
|
280
|
+
: input.trigger.origin?.peerId,
|
|
276
281
|
originControl: !eventSession && input.trigger.origin?.channelKey === 'daemon',
|
|
277
282
|
noopSentinel: input.trigger.execution.noopSentinel,
|
|
278
283
|
authorizedBy: input.trigger.authorizedBy,
|
|
284
|
+
labels: input.trigger.labels,
|
|
279
285
|
},
|
|
280
286
|
};
|
|
281
287
|
outcome = this.waitForTriggerOutcome(input.runId, input.attemptId, delivery.signal);
|