evolcore 0.0.32 → 0.0.34
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 +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- 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/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -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 +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- 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 +217 -9
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -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 +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -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 +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -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 +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- 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 +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- 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
package/dist/paths.js
CHANGED
|
@@ -208,6 +208,26 @@ export function channelStatePath(channelKey, ...parts) {
|
|
|
208
208
|
export function agentTriggersDir(aid) {
|
|
209
209
|
return path.join(agentDir(aid), 'triggers');
|
|
210
210
|
}
|
|
211
|
+
/** 话题数据的统一根目录:索引、摘要、续读锚点、跨会话收件箱都收在这里。 */
|
|
212
|
+
export function agentThreadsDir(aid) {
|
|
213
|
+
return path.join(agentDataDir(aid), 'threads');
|
|
214
|
+
}
|
|
215
|
+
export function agentThreadIndexPath(aid) {
|
|
216
|
+
return path.join(agentThreadsDir(aid), 'index.json');
|
|
217
|
+
}
|
|
218
|
+
export function agentThreadSummariesDir(aid) {
|
|
219
|
+
return path.join(agentThreadsDir(aid), 'summaries');
|
|
220
|
+
}
|
|
221
|
+
export function agentThreadSummaryPath(aid, sessionId) {
|
|
222
|
+
return path.join(agentThreadSummariesDir(aid), `${sessionId}.summary.jsonl`);
|
|
223
|
+
}
|
|
224
|
+
/** Thread Context 收件箱:跨话题发送后在目标会话侧留下的注入提醒。 */
|
|
225
|
+
export function agentThreadInboxDir(aid) {
|
|
226
|
+
return path.join(agentThreadsDir(aid), 'inbox');
|
|
227
|
+
}
|
|
228
|
+
export function agentThreadInboxPath(aid, targetSessionId) {
|
|
229
|
+
return path.join(agentThreadInboxDir(aid), `${targetSessionId}.jsonl`);
|
|
230
|
+
}
|
|
211
231
|
function stripTrailingWindowsSeparators(value) {
|
|
212
232
|
const stripped = value.replace(/[\\/]+$/g, '');
|
|
213
233
|
// Keep a drive-root separator. `C:` is a drive-relative path on Windows.
|
|
@@ -414,8 +434,81 @@ export function kitsSchemasDir() {
|
|
|
414
434
|
export function eckDebugDir() {
|
|
415
435
|
return path.join(resolveRoot(), 'data', 'eck-debug');
|
|
416
436
|
}
|
|
437
|
+
export function debugSessionsDir() {
|
|
438
|
+
return path.join(resolveRoot(), 'data', 'debug-sessions');
|
|
439
|
+
}
|
|
417
440
|
export function getPackageRoot() {
|
|
418
441
|
// import.meta.dirname is available in Node.js 21.2+ and always returns
|
|
419
442
|
// the correct OS-native path, regardless of Git Bash or MSYS2 environment.
|
|
420
443
|
return path.resolve(import.meta.dirname, '..');
|
|
421
444
|
}
|
|
445
|
+
// ── FileIntegrityStore 路径 ──
|
|
446
|
+
export function integrityDir() {
|
|
447
|
+
return path.join(resolveRoot(), 'data', 'integrity');
|
|
448
|
+
}
|
|
449
|
+
export function integrityDbPath() {
|
|
450
|
+
return path.join(integrityDir(), 'file-integrity.db');
|
|
451
|
+
}
|
|
452
|
+
export function integritySnapshotDir() {
|
|
453
|
+
return path.join(integrityDir(), 'snapshots');
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* 判断当前是否为开发仓运行模式。
|
|
457
|
+
* 与 ec start 显示的安装类型保持口径一致:包路径包含 node_modules 则为安装模式,否则为开发仓。
|
|
458
|
+
* 调用方据此决定是否跳过 FileIntegrityStore 的 verify 调用。
|
|
459
|
+
*
|
|
460
|
+
* 唯一覆盖来源是 `ec start --as-mode` 写下的一次性文件(见 asModeOverridePath),
|
|
461
|
+
* 由 daemon 启动时读取并立即删除。**不读环境变量**——环境变量会被 restart 之类的
|
|
462
|
+
* 路径静默继承,导致模式在操作者不知情的情况下改变。
|
|
463
|
+
*/
|
|
464
|
+
export function isDevMode() {
|
|
465
|
+
if (!_asModeResolved) {
|
|
466
|
+
_asModeResolved = true;
|
|
467
|
+
_asModeOverride = consumeAsModeOverride();
|
|
468
|
+
}
|
|
469
|
+
if (_asModeOverride)
|
|
470
|
+
return _asModeOverride === 'dev';
|
|
471
|
+
return !getPackageRoot().includes('node_modules');
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* `ec start --as-mode <mode>` 的一次性覆盖文件。
|
|
475
|
+
*
|
|
476
|
+
* 走文件而不是环境变量:环境变量会被 `ec restart`(全量继承 process.env)
|
|
477
|
+
* 静默带进新 daemon,操作者不重启终端就撤不掉。文件由 CLI 在启动前写入,
|
|
478
|
+
* daemon 首次调用 isDevMode() 时读走并删除,只影响这一次启动。
|
|
479
|
+
*/
|
|
480
|
+
export function asModeOverridePath() {
|
|
481
|
+
return path.join(resolveRoot(), 'data', 'daemon', 'as-mode.override');
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* 本次进程的覆盖值。**必须缓存**:文件读一次就删,若每次都去读,
|
|
485
|
+
* 第二次调用起就会悄悄退回按包路径判断——模式在进程运行中途改变是最坏的情况。
|
|
486
|
+
*/
|
|
487
|
+
let _asModeResolved = false;
|
|
488
|
+
let _asModeOverride = null;
|
|
489
|
+
/** 仅供测试:清掉缓存,让下一次 isDevMode() 重新读覆盖文件。 */
|
|
490
|
+
export function _resetAsModeCache() {
|
|
491
|
+
_asModeResolved = false;
|
|
492
|
+
_asModeOverride = null;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* 读出并删除覆盖文件(一次性)。返回 null 表示没有覆盖。
|
|
496
|
+
* 只应由 daemon 启动流程(或 isDevMode 的首次求值)调用一次。
|
|
497
|
+
*/
|
|
498
|
+
export function consumeAsModeOverride() {
|
|
499
|
+
const file = asModeOverridePath();
|
|
500
|
+
if (!fs.existsSync(file))
|
|
501
|
+
return null;
|
|
502
|
+
let value = null;
|
|
503
|
+
try {
|
|
504
|
+
value = fs.readFileSync(file, 'utf-8').trim();
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
value = null;
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
fs.rmSync(file, { force: true });
|
|
511
|
+
}
|
|
512
|
+
catch { /* ignore */ }
|
|
513
|
+
return value === 'dev' || value === 'install' ? value : null;
|
|
514
|
+
}
|
|
@@ -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) {
|
package/dist/stats/billing.js
CHANGED
|
@@ -180,24 +180,36 @@ function loadSpecs(evolcoreHome) {
|
|
|
180
180
|
_specCacheTs = now;
|
|
181
181
|
return _specCache;
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
* 带窗口后缀的 ID 复用基础模型的参数行,但窗口由后缀决定(网关的权威声明)。 */
|
|
185
|
-
export function resolveModelSpec(evolcoreHome, model, ts) {
|
|
183
|
+
export function findModelSpec(evolcoreHome, model, ts = Date.now()) {
|
|
186
184
|
const specs = loadSpecs(evolcoreHome);
|
|
187
|
-
const t = ts ?? Date.now();
|
|
188
185
|
const suffixWindow = parseModelId(model).contextWindow;
|
|
189
186
|
for (const key of modelLookupKeys(evolcoreHome, model)) {
|
|
190
|
-
const candidates = specs.filter(
|
|
187
|
+
const candidates = specs.filter(spec => spec.model === key && spec.effective_from <= ts);
|
|
191
188
|
if (!candidates.length)
|
|
192
189
|
continue;
|
|
193
|
-
const row = candidates.reduce((a, b) => a.effective_from
|
|
190
|
+
const row = candidates.reduce((a, b) => a.effective_from > b.effective_from ? a : b);
|
|
194
191
|
if (suffixWindow === undefined || suffixWindow === row.context_window)
|
|
195
192
|
return row;
|
|
196
193
|
// 后缀声明的窗口覆盖基础行,输入上限按原行的预留比例等比缩放。
|
|
197
194
|
const ratio = row.context_window > 0 ? row.max_input_tokens / row.context_window : 0.9;
|
|
198
195
|
return { ...row, model, context_window: suffixWindow, max_input_tokens: Math.floor(suffixWindow * ratio) };
|
|
199
196
|
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
/** 取模型在 ts 时刻的能力参数。找不到时返回 fallback 或默认值。
|
|
200
|
+
* 带窗口后缀的 ID 复用基础模型的参数行,但窗口由后缀决定(网关的权威声明)。 */
|
|
201
|
+
export function resolveModelSpec(evolcoreHome, model, ts, fallback) {
|
|
202
|
+
const spec = findModelSpec(evolcoreHome, model, ts);
|
|
203
|
+
if (spec)
|
|
204
|
+
return spec;
|
|
205
|
+
const suffixWindow = parseModelId(model).contextWindow;
|
|
200
206
|
// 默认值
|
|
201
|
-
const contextWindow = suffixWindow ?? 200000;
|
|
202
|
-
return {
|
|
207
|
+
const contextWindow = suffixWindow ?? fallback?.context_window ?? 200000;
|
|
208
|
+
return {
|
|
209
|
+
model,
|
|
210
|
+
effective_from: 0,
|
|
211
|
+
context_window: contextWindow,
|
|
212
|
+
max_input_tokens: Math.floor(contextWindow * 0.9),
|
|
213
|
+
max_output_tokens: fallback?.max_output_tokens ?? 8192,
|
|
214
|
+
};
|
|
203
215
|
}
|