evolcore 0.0.22 → 0.0.24
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 +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
|
@@ -325,44 +325,71 @@ function withFallbackModel(override, candidate) {
|
|
|
325
325
|
...(candidate.effort ? { effort: candidate.effort } : { effortMode: 'model_default' }),
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Keep the per-task fullaccess fact in the user-message prompt rather than in
|
|
330
|
+
* the long-lived system/developer prompt. This avoids changing a runner's
|
|
331
|
+
* system-prompt cache while still giving the model an explicit, auditable
|
|
332
|
+
* execution-mode signal for the privileged task.
|
|
333
|
+
*/
|
|
334
|
+
const FULL_ACCESS_MESSAGE_PROMPT_CONTEXT = '【当前权限模式提升为fullaccess,可不受限制执行命令,仅当前轮 task 有效】';
|
|
335
|
+
/**
|
|
336
|
+
* Put the one-task permission notice between the rendered message envelope and
|
|
337
|
+
* the user's literal input. The normal message fragment ends its envelope in
|
|
338
|
+
* `›`; matching the dated normal-message form avoids placing the notice inside
|
|
339
|
+
* an owner-hint envelope when one is present before the real inbound message.
|
|
340
|
+
*/
|
|
341
|
+
function insertMessagePromptContext(prompt, context, userContent) {
|
|
342
|
+
if (!context)
|
|
343
|
+
return prompt;
|
|
344
|
+
const headers = [...prompt.matchAll(/‹\d{4}-\d{2}-\d{2} [^\n]*· from:[^\n]*›/gu)];
|
|
345
|
+
const renderedHeader = headers[0];
|
|
346
|
+
const headerEnd = renderedHeader
|
|
347
|
+
? (renderedHeader.index ?? 0) + renderedHeader[0].length
|
|
348
|
+
: -1;
|
|
349
|
+
if (headerEnd >= 0) {
|
|
350
|
+
const remainder = prompt.slice(headerEnd).replace(/^\n+/u, '');
|
|
351
|
+
return `${prompt.slice(0, headerEnd)}\n${context}${remainder ? `\n${remainder}` : ''}`;
|
|
352
|
+
}
|
|
353
|
+
if (userContent?.trim()) {
|
|
354
|
+
const contentStart = prompt.indexOf(userContent);
|
|
355
|
+
if (contentStart >= 0) {
|
|
356
|
+
return `${prompt.slice(0, contentStart)}${context}\n${prompt.slice(contentStart)}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Internal recovery prompts have no message envelope; keep their context at
|
|
360
|
+
// the front so the runner still sees it for the same task.
|
|
361
|
+
return `${context}\n\n${prompt}`;
|
|
362
|
+
}
|
|
328
363
|
function getStreamErrorText(result) {
|
|
329
|
-
|
|
364
|
+
const parts = [
|
|
330
365
|
result.errors?.join('\n'),
|
|
331
|
-
result.lastReplyText,
|
|
332
|
-
result.fullText,
|
|
333
366
|
result.subtype,
|
|
334
367
|
result.terminalReason,
|
|
335
368
|
]
|
|
336
|
-
.filter((part) => typeof part === 'string' && part.trim().length > 0)
|
|
337
|
-
|
|
369
|
+
.filter((part) => typeof part === 'string' && part.trim().length > 0);
|
|
370
|
+
// Some older runners put the terminal diagnostic in `result`, but never
|
|
371
|
+
// treat streamed assistant text as an error merely because the turn failed.
|
|
372
|
+
const fullText = result.fullText.trim();
|
|
373
|
+
const fullTextLooksLikeDiagnostic = fullText.length > 0 && (isRetryableError(new Error(fullText))
|
|
374
|
+
|| isContextTooLongText(fullText));
|
|
375
|
+
if (result.isError && !result.hasReceivedText && fullText
|
|
376
|
+
&& (parts.length === 0 || fullTextLooksLikeDiagnostic)) {
|
|
377
|
+
parts.unshift(fullText);
|
|
378
|
+
}
|
|
379
|
+
return parts.join('\n');
|
|
338
380
|
}
|
|
339
381
|
function getStreamErrorMessage(result, includeEmoji = false) {
|
|
340
382
|
const raw = getStreamErrorText(result) || '任务执行失败';
|
|
341
383
|
const mapped = getErrorMessage(new Error(raw), result.terminalReason, includeEmoji);
|
|
342
384
|
const generic = includeEmoji ? '❌ 处理消息时出错,请稍后重试' : '处理消息时出错,请稍后重试';
|
|
343
|
-
|
|
385
|
+
// Unknown runner-specific terminal reasons do not have a friendly mapping.
|
|
386
|
+
// Preserve their diagnostic text instead of collapsing it to the generic
|
|
387
|
+
// "处理消息时出错" message.
|
|
388
|
+
if (raw && mapped === generic) {
|
|
344
389
|
return includeEmoji ? `❌ ${raw}` : raw;
|
|
345
390
|
}
|
|
346
391
|
return mapped;
|
|
347
392
|
}
|
|
348
|
-
function getRuntimeErrorMessage(raw, includeEmoji = false) {
|
|
349
|
-
const fallback = raw || '任务执行失败';
|
|
350
|
-
const mapped = getErrorMessage(new Error(fallback), undefined, includeEmoji);
|
|
351
|
-
const generic = includeEmoji ? '❌ 处理消息时出错,请稍后重试' : '处理消息时出错,请稍后重试';
|
|
352
|
-
if (mapped === generic)
|
|
353
|
-
return includeEmoji ? `❌ ${fallback}` : fallback;
|
|
354
|
-
return mapped;
|
|
355
|
-
}
|
|
356
|
-
function normalizeComparableText(text) {
|
|
357
|
-
return text.replace(/\s+/g, ' ').trim();
|
|
358
|
-
}
|
|
359
|
-
function isPendingTextSameAsStreamError(pendingText, errorText) {
|
|
360
|
-
const pending = normalizeComparableText(pendingText);
|
|
361
|
-
const error = normalizeComparableText(errorText);
|
|
362
|
-
if (!pending || !error || pending.length < 20)
|
|
363
|
-
return false;
|
|
364
|
-
return pending === error || pending.includes(error) || error.includes(pending);
|
|
365
|
-
}
|
|
366
393
|
function streamHitContextLimit(result) {
|
|
367
394
|
return result.terminalReason === 'prompt_too_long' ||
|
|
368
395
|
isContextTooLongText(result.lastReplyText) ||
|
|
@@ -446,12 +473,6 @@ function getContextTooLongHint(agent) {
|
|
|
446
473
|
}
|
|
447
474
|
return '上下文过长,请精简提问,或使用 /new 新建会话后继续';
|
|
448
475
|
}
|
|
449
|
-
function getContextCompactFailedHint(agent) {
|
|
450
|
-
if (canCompactAgent(agent)) {
|
|
451
|
-
return '上下文过长,自动压缩失败,请手动输入 /compact 重试';
|
|
452
|
-
}
|
|
453
|
-
return '上下文过长,请精简提问,或使用 /new 新建会话后继续';
|
|
454
|
-
}
|
|
455
476
|
function canCompactAgent(agent) {
|
|
456
477
|
return hasCompact(agent) && agent.capabilities?.compact !== false;
|
|
457
478
|
}
|
|
@@ -496,6 +517,7 @@ export class ResponseEngine {
|
|
|
496
517
|
/** 已主动发送开始提示时,忽略 runner 对同一次 compact 的回调。 */
|
|
497
518
|
announcedCompactStarts = new Set();
|
|
498
519
|
agentMap;
|
|
520
|
+
legacySingleRunnerMode;
|
|
499
521
|
primaryRunnerKey;
|
|
500
522
|
interruptedSessions = new Map(); // sessionId → reason ('new_message' | 'stop' | ...)
|
|
501
523
|
timeoutErrors = new Map();
|
|
@@ -531,27 +553,42 @@ export class ResponseEngine {
|
|
|
531
553
|
* session/config mismatch and must not silently route to a different backend.
|
|
532
554
|
*/
|
|
533
555
|
getAgent(channel, baseagent, selfAID) {
|
|
534
|
-
|
|
535
|
-
|
|
556
|
+
const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
|
|
557
|
+
// Keep the explicit single-backend map shape usable by embedders and
|
|
558
|
+
// focused tests. This is an exact key lookup, not a cross-agent fallback.
|
|
559
|
+
if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
|
|
560
|
+
return this.agentMap.get(requestedBaseagent);
|
|
561
|
+
}
|
|
562
|
+
// A caller that passed one runner explicitly has already selected the
|
|
563
|
+
// backend; this compatibility path does not exist for multi-agent maps.
|
|
564
|
+
if (this.legacySingleRunnerMode && this.agentMap.size === 1
|
|
565
|
+
&& this.agentMap.values().next().value?.name === requestedBaseagent) {
|
|
566
|
+
return this.agentMap.values().next().value;
|
|
567
|
+
}
|
|
568
|
+
if (selfAID && requestedBaseagent) {
|
|
569
|
+
const key = `${selfAID}::${requestedBaseagent}`;
|
|
536
570
|
if (this.agentMap.has(key))
|
|
537
571
|
return this.agentMap.get(key);
|
|
538
572
|
}
|
|
539
|
-
if (channel &&
|
|
573
|
+
if (channel && requestedBaseagent) {
|
|
540
574
|
const owner = this.agentRegistry?.resolveByChannel(channel);
|
|
541
575
|
const evolName = owner?.name || '<unknown>';
|
|
542
|
-
const key = `${evolName}::${
|
|
576
|
+
const key = `${evolName}::${requestedBaseagent}`;
|
|
543
577
|
if (this.agentMap.has(key))
|
|
544
578
|
return this.agentMap.get(key);
|
|
545
|
-
const singleRunnerKey = `<unknown>::${baseagent}`;
|
|
546
|
-
if (this.agentMap.has(singleRunnerKey))
|
|
547
|
-
return this.agentMap.get(singleRunnerKey);
|
|
548
579
|
if (owner) {
|
|
549
|
-
throw new BaseagentRunnerUnavailableError(evolName,
|
|
580
|
+
throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
|
|
550
581
|
}
|
|
582
|
+
// Control/test channels may have no registry owner. Resolve only when
|
|
583
|
+
// exactly one loaded runner advertises this baseagent; never choose the
|
|
584
|
+
// primary runner or the first map entry as a backend substitute.
|
|
585
|
+
const matches = [...this.agentMap.entries()]
|
|
586
|
+
.filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
|
|
587
|
+
if (matches.length === 1)
|
|
588
|
+
return matches[0][1];
|
|
589
|
+
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, [...new Set(matches.map(([runnerKey]) => runnerKey.slice(runnerKey.lastIndexOf('::') + 2)))]);
|
|
551
590
|
}
|
|
552
|
-
|
|
553
|
-
return this.agentMap.get(this.primaryRunnerKey);
|
|
554
|
-
return this.agentMap.values().next().value;
|
|
591
|
+
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
|
|
555
592
|
}
|
|
556
593
|
getTextInferenceProvider(channel, baseagent, selfAID) {
|
|
557
594
|
if (!baseagent)
|
|
@@ -627,11 +664,13 @@ export class ResponseEngine {
|
|
|
627
664
|
this.turnCoordinator = new SessionTurnCoordinator(sessionManager);
|
|
628
665
|
if (agentRunnerOrMap instanceof Map) {
|
|
629
666
|
this.agentMap = agentRunnerOrMap;
|
|
667
|
+
this.legacySingleRunnerMode = false;
|
|
630
668
|
this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
|
|
631
669
|
}
|
|
632
670
|
else {
|
|
633
671
|
// 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
|
|
634
672
|
this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
673
|
+
this.legacySingleRunnerMode = true;
|
|
635
674
|
this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
|
|
636
675
|
}
|
|
637
676
|
// 监听中断事件,标记被中断的 session
|
|
@@ -886,14 +925,10 @@ export class ResponseEngine {
|
|
|
886
925
|
if (owning)
|
|
887
926
|
owning.lastActivity = Date.now();
|
|
888
927
|
}
|
|
889
|
-
inferPrimaryBaseagent() {
|
|
890
|
-
const idx = this.primaryRunnerKey.lastIndexOf('::');
|
|
891
|
-
return idx >= 0 ? this.primaryRunnerKey.slice(idx + 2) : this.primaryRunnerKey || undefined;
|
|
892
|
-
}
|
|
893
928
|
ensureSessionBaseagent(session, fallback) {
|
|
894
|
-
|
|
895
|
-
if (!session.baseagent) {
|
|
896
|
-
session.
|
|
929
|
+
void fallback;
|
|
930
|
+
if (typeof session.baseagent !== 'string' || !session.baseagent.trim()) {
|
|
931
|
+
throw new Error(`[ResponseEngine] session ${session.id} has no baseagent binding; refusing implicit fallback`);
|
|
897
932
|
}
|
|
898
933
|
}
|
|
899
934
|
inferDirectSessionIdentity(message, owningAgent) {
|
|
@@ -1042,7 +1077,7 @@ export class ResponseEngine {
|
|
|
1042
1077
|
return true;
|
|
1043
1078
|
}
|
|
1044
1079
|
async retryAfterContextRecovery(prompt, opts) {
|
|
1045
|
-
const { streamKey, renderer, agent, session, absoluteProjectPath, effectiveSystemPrompt, modelOverride, runtimeEnv, resetTimer, shouldSuppress, proactive, turnLease, beforeRunQuery, } = opts;
|
|
1080
|
+
const { streamKey, renderer, agent, session, absoluteProjectPath, effectiveSystemPrompt, modelOverride, runtimeEnv, resetTimer, shouldSuppress, proactive, turnLease, beforeRunQuery, messagePromptContext, } = opts;
|
|
1046
1081
|
if (!session.agentSessionId || !canCompactAgent(agent)) {
|
|
1047
1082
|
throw new Error('CONTEXT_COMPACT_FAILED');
|
|
1048
1083
|
}
|
|
@@ -1081,7 +1116,7 @@ export class ResponseEngine {
|
|
|
1081
1116
|
await this.emitOperationalNotice(renderer, '✅ 压缩完成,继续处理...', 'info', 'compact-retry');
|
|
1082
1117
|
await renderer.flush();
|
|
1083
1118
|
beforeRunQuery?.();
|
|
1084
|
-
const retryStream = await agent.runQuery(session.id, prompt, absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, recoveryModelOverride, runtimeEnv);
|
|
1119
|
+
const retryStream = await agent.runQuery(session.id, insertMessagePromptContext(prompt, messagePromptContext), absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, recoveryModelOverride, runtimeEnv);
|
|
1085
1120
|
if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, turnLease.taskId)) {
|
|
1086
1121
|
throw new Error('RUNNER_START_SUPERSEDED');
|
|
1087
1122
|
}
|
|
@@ -1365,11 +1400,11 @@ export class ResponseEngine {
|
|
|
1365
1400
|
'/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork',
|
|
1366
1401
|
'/stop', '/pause', '/resume', '/clear', '/compact', '/renew', '/del', '/perm', '/file', '/check',
|
|
1367
1402
|
'/s ', '/name ', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode',
|
|
1368
|
-
'/aid', '/upgrade', '/evolagent',
|
|
1403
|
+
'/aid', '/upgrade', '/evolagent', '/pair ',
|
|
1369
1404
|
];
|
|
1370
1405
|
/** 判断消息内容是否为已知命令 */
|
|
1371
1406
|
isKnownCommand(content) {
|
|
1372
|
-
return content === '/s' ||
|
|
1407
|
+
return content === '/s' || content === '/pair' ||
|
|
1373
1408
|
ResponseEngine.COMMAND_PREFIXES.some(cmd => content.startsWith(cmd));
|
|
1374
1409
|
}
|
|
1375
1410
|
/**
|
|
@@ -1626,6 +1661,19 @@ export class ResponseEngine {
|
|
|
1626
1661
|
totalToolCalls: result.state.totalToolCalls,
|
|
1627
1662
|
lastToolName: result.state.lastToolName,
|
|
1628
1663
|
});
|
|
1664
|
+
const idleNotice = timeoutControl.idleNotice;
|
|
1665
|
+
if (idleNotice) {
|
|
1666
|
+
void Promise.resolve(idleNotice({
|
|
1667
|
+
action: result.action,
|
|
1668
|
+
idleSec: result.idleSec,
|
|
1669
|
+
message: result.message,
|
|
1670
|
+
totalEvents: result.state.totalEvents,
|
|
1671
|
+
totalToolCalls: result.state.totalToolCalls,
|
|
1672
|
+
lastToolName: result.state.lastToolName,
|
|
1673
|
+
})).catch(error => {
|
|
1674
|
+
logger.debug(`[ResponseEngine] Idle notice send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1629
1677
|
}
|
|
1630
1678
|
result = monitor.check();
|
|
1631
1679
|
}
|
|
@@ -2380,8 +2428,10 @@ export class ResponseEngine {
|
|
|
2380
2428
|
opts.title = '\u2705 \u6700\u7ec8\u56de\u590d:';
|
|
2381
2429
|
}
|
|
2382
2430
|
opts.metadata = withTaskMetadata(opts.metadata);
|
|
2383
|
-
|
|
2431
|
+
// status.notice is a diagnostic observation, not execution activity.
|
|
2432
|
+
if (payload.kind.startsWith('status.') && payload.kind !== 'status.notice') {
|
|
2384
2433
|
this.touchAgentActivity(channelKey);
|
|
2434
|
+
}
|
|
2385
2435
|
const enrichedEnvelope = withEnvelopeReplyContext(envelope, opts);
|
|
2386
2436
|
snapshot.pushOutbound(session.id, taskId, { kind: payload.kind, decision: 'sent' });
|
|
2387
2437
|
return await adapter.send(enrichedEnvelope, payload);
|
|
@@ -2392,6 +2442,19 @@ export class ResponseEngine {
|
|
|
2392
2442
|
renderer,
|
|
2393
2443
|
suppressActivities: shouldSuppress(),
|
|
2394
2444
|
});
|
|
2445
|
+
// Idle notify/warn are operational diagnostic notices. Keep them on the
|
|
2446
|
+
// current task's renderer so envelope, stale-turn, background, and
|
|
2447
|
+
// channel-specific suppression rules remain identical to other sends.
|
|
2448
|
+
if (timeoutControl) {
|
|
2449
|
+
timeoutControl.idleNotice = async ({ action, idleSec, message: idleMessage, totalEvents, totalToolCalls, lastToolName }) => {
|
|
2450
|
+
await renderer.sendStatusNotice(idleMessage, action === 'warn' ? 'warn' : 'info', `idle-${action}`, {
|
|
2451
|
+
idleSec,
|
|
2452
|
+
totalEvents,
|
|
2453
|
+
totalToolCalls,
|
|
2454
|
+
lastToolName,
|
|
2455
|
+
});
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2395
2458
|
renderer.addLifecycle('started');
|
|
2396
2459
|
if (isProactive) {
|
|
2397
2460
|
logger.info(`[ResponseEngine] proactive mode: outputs via thought.put task=${taskId}`);
|
|
@@ -2689,6 +2752,7 @@ export class ResponseEngine {
|
|
|
2689
2752
|
: body;
|
|
2690
2753
|
// 先用裸文本兜底;vars 构造完成后用消息渲染层重算(见下方 effectivePrompt 重赋值)。
|
|
2691
2754
|
let effectivePrompt = wrapPrompt(message.content);
|
|
2755
|
+
let fullAccessMessagePromptContext;
|
|
2692
2756
|
let renderResult;
|
|
2693
2757
|
let effectiveSystemPrompt;
|
|
2694
2758
|
let modelOverride;
|
|
@@ -2784,6 +2848,9 @@ export class ResponseEngine {
|
|
|
2784
2848
|
// records all observe the same normalized value.
|
|
2785
2849
|
permissionContext.permissionMode = effectivePermissionMode;
|
|
2786
2850
|
permissionContext.executionPermission = fullAccessAuthorization;
|
|
2851
|
+
fullAccessMessagePromptContext = fullAccessAuthorization
|
|
2852
|
+
? FULL_ACCESS_MESSAGE_PROMPT_CONTEXT
|
|
2853
|
+
: undefined;
|
|
2787
2854
|
// 按 关系级 > agent级 > 全局 解析本次调用的模型/强度,作为 per-call 入参传入 runQuery。
|
|
2788
2855
|
// 不缓存、不绑会话——改关系级/agent级后该范围所有会话的下条消息即时生效;
|
|
2789
2856
|
// 多对端并发各自独立解析、各自传参,无共享状态可被污染。
|
|
@@ -2878,7 +2945,9 @@ export class ResponseEngine {
|
|
|
2878
2945
|
agentModel = (typeof agent.getModel === 'function') ? agent.getModel() : undefined;
|
|
2879
2946
|
primaryModel = modelOverride?.model || agentModel;
|
|
2880
2947
|
if (!hasExplicitTriggerModel && primaryModel) {
|
|
2881
|
-
const fallbackResolution = eligibleFallbackModels({
|
|
2948
|
+
const fallbackResolution = await eligibleFallbackModels({
|
|
2949
|
+
selfAid: selfAid || undefined,
|
|
2950
|
+
catalog: agent.getModelCatalog?.bind(agent),
|
|
2882
2951
|
baseagent: normalizedBaseagent.canonical,
|
|
2883
2952
|
currentModel: primaryModel,
|
|
2884
2953
|
resolveModelId: typeof agent.resolveModelId === 'function'
|
|
@@ -3067,6 +3136,7 @@ export class ResponseEngine {
|
|
|
3067
3136
|
sessionId: session.id,
|
|
3068
3137
|
sessionName: session.name || undefined,
|
|
3069
3138
|
sessionCreatedAt: session.createdAt ? new Date(session.createdAt).toISOString() : undefined,
|
|
3139
|
+
managedTempDir: sessionRuntimeDir,
|
|
3070
3140
|
// 时区(把 ISO 时间戳转本地时间用)+ OS 环境
|
|
3071
3141
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || undefined,
|
|
3072
3142
|
tzOffset: currentTzOffset(),
|
|
@@ -3115,7 +3185,10 @@ export class ResponseEngine {
|
|
|
3115
3185
|
try {
|
|
3116
3186
|
const estTokens = (s) => s ? Math.ceil(s.length / 4) : 0;
|
|
3117
3187
|
const cbModel = effectiveModel || agentModel || 'unknown';
|
|
3118
|
-
|
|
3188
|
+
// 从统一模型参数表获取模型的真实 context_window
|
|
3189
|
+
const { resolveModelSpec } = await import('../../stats/billing.js');
|
|
3190
|
+
const modelSpec = resolveModelSpec(resolveRoot(), cbModel);
|
|
3191
|
+
const cbMaxTokens = modelSpec.context_window;
|
|
3119
3192
|
const systemPromptTokens = estTokens(options?.systemPromptAppend);
|
|
3120
3193
|
const kitTokens = estTokens(kitContext);
|
|
3121
3194
|
const totalEst = estTokens(effectiveSystemPrompt);
|
|
@@ -3218,6 +3291,12 @@ export class ResponseEngine {
|
|
|
3218
3291
|
if (v2HandoffIds.length > 0 && renderResult?.body.trim())
|
|
3219
3292
|
handoffPromptRendered = true;
|
|
3220
3293
|
}
|
|
3294
|
+
// Keep this notice in the per-turn message prompt. Injecting it into
|
|
3295
|
+
// the system prompt would alter long-lived runner prompt caches and
|
|
3296
|
+
// could make a one-turn grant look persistent.
|
|
3297
|
+
if (fullAccessMessagePromptContext && effectivePrompt.trim()) {
|
|
3298
|
+
effectivePrompt = insertMessagePromptContext(effectivePrompt, fullAccessMessagePromptContext, message.content);
|
|
3299
|
+
}
|
|
3221
3300
|
// 空消息防护:在 agent 调用之前检查 prompt 是否为空
|
|
3222
3301
|
// 防止空消息(或纯空格消息)浪费 API 调用
|
|
3223
3302
|
if (!effectivePrompt.trim()) {
|
|
@@ -3282,6 +3361,8 @@ export class ResponseEngine {
|
|
|
3282
3361
|
selfAid,
|
|
3283
3362
|
peerKey,
|
|
3284
3363
|
issuedRole: peerRole,
|
|
3364
|
+
permissionMode: effectivePermissionMode,
|
|
3365
|
+
...(sessionRuntimeDir ? { sessionRuntimeDir } : {}),
|
|
3285
3366
|
...(fullAccessAuthorization ? { executionIdentity: fullAccessAuthorization } : {}),
|
|
3286
3367
|
});
|
|
3287
3368
|
runtimeEnv[AGENT_DELEGATION_TOKEN_ENV] = delegationToken;
|
|
@@ -3527,6 +3608,7 @@ export class ResponseEngine {
|
|
|
3527
3608
|
proactiveSelfAid: selfAid,
|
|
3528
3609
|
permissionMode: effectivePermissionMode,
|
|
3529
3610
|
turnLease,
|
|
3611
|
+
messagePromptContext: fullAccessMessagePromptContext,
|
|
3530
3612
|
beforeRunQuery: fullAccessAuthorization
|
|
3531
3613
|
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
3532
3614
|
: undefined,
|
|
@@ -3573,6 +3655,7 @@ export class ResponseEngine {
|
|
|
3573
3655
|
proactiveSelfAid: selfAid,
|
|
3574
3656
|
permissionMode: effectivePermissionMode,
|
|
3575
3657
|
turnLease,
|
|
3658
|
+
messagePromptContext: fullAccessMessagePromptContext,
|
|
3576
3659
|
beforeRunQuery: fullAccessAuthorization
|
|
3577
3660
|
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
3578
3661
|
: undefined,
|
|
@@ -4537,7 +4620,7 @@ export class ResponseEngine {
|
|
|
4537
4620
|
: undefined;
|
|
4538
4621
|
const owningAgent = this.agentRegistry?.resolveByChannel(message.channel);
|
|
4539
4622
|
const projectPath = owningAgent?.projectPath || process.cwd();
|
|
4540
|
-
const resolvedBaseagent = message.baseagent || owningAgent?.baseagent
|
|
4623
|
+
const resolvedBaseagent = message.baseagent || owningAgent?.baseagent;
|
|
4541
4624
|
// 优先复用 MessageBridge 入队前已解析的身份,避免二次独立解析产生角色漂移
|
|
4542
4625
|
// (曾观测到瞬时竞态下把 owner 误判为 none,导致合法用户被静默拒绝)。
|
|
4543
4626
|
// 仅当消息不经 MessageBridge.register()(如 trigger 直接投递)时才回退重新解析。
|
|
@@ -4727,6 +4810,10 @@ export class ResponseEngine {
|
|
|
4727
4810
|
// 每收到事件重置空闲超时
|
|
4728
4811
|
const toolName = event.type === 'tool_use' ? event.name : undefined;
|
|
4729
4812
|
resetTimer(event.type, toolName);
|
|
4813
|
+
// Provider reasoning/thinking is an internal liveness signal only.
|
|
4814
|
+
// It must never be projected as progress, activity, text, or transcript.
|
|
4815
|
+
if (event.type === 'heartbeat')
|
|
4816
|
+
continue;
|
|
4730
4817
|
if (event.type === 'input_accepted') {
|
|
4731
4818
|
inputAccepted = !turnLease || event.inputId === turnLease.inputId;
|
|
4732
4819
|
logger.debug(`[ResponseEngine] Input accepted: session=${session.id} input=${event.inputId}`);
|
|
@@ -5254,8 +5341,10 @@ export class ResponseEngine {
|
|
|
5254
5341
|
// 运行时错误(Codex: turn.failed / item error)
|
|
5255
5342
|
if (event.type === 'error') {
|
|
5256
5343
|
logger.warn(`[ResponseEngine] error event: ${event.errorType}: ${event.error}`);
|
|
5257
|
-
//
|
|
5258
|
-
|
|
5344
|
+
// Keep runtime diagnostics separate from assistant prose. Appending
|
|
5345
|
+
// an error to lastReplyText makes the terminal flush treat the error
|
|
5346
|
+
// as user-visible model output and can also make it look like a
|
|
5347
|
+
// duplicate of a pending text event.
|
|
5259
5348
|
completeResult = {
|
|
5260
5349
|
...completeResult,
|
|
5261
5350
|
isError: true,
|
|
@@ -5265,14 +5354,6 @@ export class ResponseEngine {
|
|
|
5265
5354
|
lastReplyText,
|
|
5266
5355
|
fullText: '',
|
|
5267
5356
|
};
|
|
5268
|
-
// 上下文过长的错误不在此处输出 notice,留给外层 isPromptTooLong 触发 auto-compact
|
|
5269
|
-
const isContextError = isContextTooLongText(event.error || '');
|
|
5270
|
-
const isRetryableRuntimeError = isRetryableError(new Error(event.error || ''));
|
|
5271
|
-
const isRecoverableModelError = classifyError(new Error(event.error || '')) === ErrorType.MODEL_UNAVAILABLE;
|
|
5272
|
-
if (!isContextError && !isRetryableRuntimeError && !isRecoverableModelError && !hasErrorResult && !shouldSuppress()) {
|
|
5273
|
-
hasErrorResult = true;
|
|
5274
|
-
renderer.addNotice(getRuntimeErrorMessage(event.error || '任务执行失败', false), 'warn', 'runtime-error', true);
|
|
5275
|
-
}
|
|
5276
5357
|
}
|
|
5277
5358
|
// 完成事件
|
|
5278
5359
|
// SDK 可能产生多个 complete 事件(如 subagent 或 auto-compact 二次查询),
|
|
@@ -5291,34 +5372,6 @@ export class ResponseEngine {
|
|
|
5291
5372
|
madeProgress = true;
|
|
5292
5373
|
// thought jsonl 写入已下沉到 aun.ts:sendThought 成功后,
|
|
5293
5374
|
// 由那里按 LLM 输出的每个 text item 单独写一条,此处不再写。
|
|
5294
|
-
// 失败且无前置错误输出:显示 errors 摘要
|
|
5295
|
-
// 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
|
|
5296
|
-
// 上下文过长的错误留给外层 isPromptTooLong 触发 auto-compact,不在此处输出
|
|
5297
|
-
const interruptReason = this.interruptedSessions.get(session.id);
|
|
5298
|
-
const isUserInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
5299
|
-
|| isExpectedTerminalInterrupt(event.terminalReason);
|
|
5300
|
-
const isContextTooLong = event.terminalReason === 'prompt_too_long'
|
|
5301
|
-
|| isContextTooLongText(event.errors?.join(' ') || '')
|
|
5302
|
-
|| isContextTooLongText(lastReplyText);
|
|
5303
|
-
const completeErrorText = getStreamErrorText(completeResult);
|
|
5304
|
-
const isRetryableCompleteError = event.isError && !isContextTooLong && completeErrorText
|
|
5305
|
-
? isRetryableError(new Error(completeErrorText))
|
|
5306
|
-
: false;
|
|
5307
|
-
const isRecoverableModelError = event.isError && !isContextTooLong && completeErrorText
|
|
5308
|
-
? classifyError(new Error(completeErrorText)) === ErrorType.MODEL_UNAVAILABLE
|
|
5309
|
-
: false;
|
|
5310
|
-
if (event.isError && completeErrorText && isPendingTextSameAsStreamError(renderer.getRemainingText(), completeErrorText)) {
|
|
5311
|
-
renderer.discardPendingText();
|
|
5312
|
-
}
|
|
5313
|
-
if (event.isError && !hasErrorResult && !shouldSuppress() && !isUserInterrupt && !isContextTooLong && !isRetryableCompleteError && !isRecoverableModelError) {
|
|
5314
|
-
// 使用 terminalReason 提供更友好的错误提示(不带 emoji,由 formatter 统一加)
|
|
5315
|
-
const userFriendlyMessage = event.terminalReason === 'prompt_too_long'
|
|
5316
|
-
? getContextTooLongHint(agent)
|
|
5317
|
-
: event.terminalReason === 'context_compact_failed'
|
|
5318
|
-
? getContextCompactFailedHint(agent)
|
|
5319
|
-
: getStreamErrorMessage(completeResult, false);
|
|
5320
|
-
renderer.addNotice(userFriendlyMessage, 'warn', 'task-error', true);
|
|
5321
|
-
}
|
|
5322
5375
|
// 中间 complete:flush 掉已有 activities(不带 isFinal),让中间结果及时显示
|
|
5323
5376
|
// 最终文本留给流结束后的统一 flush(true)
|
|
5324
5377
|
if (renderer.hasContent()) {
|