evolcore 0.0.17 → 0.0.19
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 +51 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +113 -24
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +43 -24
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/p2p.js +20 -8
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +82 -14
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/data-migration.js +10 -4
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +66 -8
- package/dist/core/message/response-engine.js +147 -10
- package/dist/core/permission/ec-command-parser.js +148 -22
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +19 -7
- package/dist/index.js +357 -48
- package/dist/ipc.js +81 -5
- package/dist/paths.js +0 -3
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/docs/path-registry.md +1 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/02-navigation.md +2 -2
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
|
@@ -1295,8 +1295,14 @@ function finalizationSourcePaths(root, manifest) {
|
|
|
1295
1295
|
function archiveHash(filePath) {
|
|
1296
1296
|
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
1297
1297
|
}
|
|
1298
|
-
function
|
|
1299
|
-
|
|
1298
|
+
function systemTar() {
|
|
1299
|
+
if (process.platform !== 'win32')
|
|
1300
|
+
return 'tar';
|
|
1301
|
+
// Git Bash puts GNU tar ahead of Windows bsdtar on PATH. GNU tar treats the
|
|
1302
|
+
// drive colon in an absolute archive path as host:path remote syntax.
|
|
1303
|
+
return path.win32.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe');
|
|
1304
|
+
}
|
|
1305
|
+
function verifyArchiveContains(archivePath, sourcePaths, tar) {
|
|
1300
1306
|
const normalizeArchivePath = (value) => value
|
|
1301
1307
|
.trim()
|
|
1302
1308
|
.replace(/\\/g, '/')
|
|
@@ -1402,14 +1408,14 @@ export function finalizeDataMigration(root, manifest) {
|
|
|
1402
1408
|
}
|
|
1403
1409
|
fs.mkdirSync(path.dirname(archivePath), { recursive: true });
|
|
1404
1410
|
const temporary = `${archivePath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
1405
|
-
const tar =
|
|
1411
|
+
const tar = systemTar();
|
|
1406
1412
|
try {
|
|
1407
1413
|
execFileSync(tar, ['-czf', temporary, ...sourcePaths], {
|
|
1408
1414
|
cwd: root,
|
|
1409
1415
|
stdio: 'pipe',
|
|
1410
1416
|
windowsHide: process.platform === 'win32',
|
|
1411
1417
|
});
|
|
1412
|
-
verifyArchiveContains(temporary, sourcePaths);
|
|
1418
|
+
verifyArchiveContains(temporary, sourcePaths, tar);
|
|
1413
1419
|
fs.renameSync(temporary, archivePath);
|
|
1414
1420
|
}
|
|
1415
1421
|
catch (error) {
|
|
@@ -313,10 +313,16 @@ const CATALOG = [
|
|
|
313
313
|
{ path: 'sessionId', type: 'string' },
|
|
314
314
|
{ path: 'toolName', type: 'string' },
|
|
315
315
|
{ path: 'input', type: 'object' },
|
|
316
|
+
{ path: 'eventKey', type: 'string' },
|
|
317
|
+
{ path: 'eventPhase', type: 'string' },
|
|
316
318
|
{ path: 'callId', type: 'string', optional: true },
|
|
317
319
|
{ path: 'correlationId', type: 'string', optional: true },
|
|
318
320
|
{ path: 'agentAid', type: 'string', optional: true },
|
|
319
321
|
{ path: 'permissionMode', type: 'string', optional: true },
|
|
322
|
+
{ path: 'decision', type: 'string' },
|
|
323
|
+
{ path: 'decisionSource', type: 'string' },
|
|
324
|
+
{ path: 'executed', type: 'boolean' },
|
|
325
|
+
{ path: 'executionState', type: 'string' },
|
|
320
326
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
321
327
|
],
|
|
322
328
|
},
|
|
@@ -328,6 +334,8 @@ const CATALOG = [
|
|
|
328
334
|
fields: [
|
|
329
335
|
{ path: 'sessionId', type: 'string' },
|
|
330
336
|
{ path: 'toolName', type: 'string' },
|
|
337
|
+
{ path: 'eventKey', type: 'string' },
|
|
338
|
+
{ path: 'eventPhase', type: 'string' },
|
|
331
339
|
{ path: 'isError', type: 'boolean', optional: true },
|
|
332
340
|
{ path: 'errorCode', type: 'string', optional: true },
|
|
333
341
|
{ path: 'agentName', type: 'string', optional: true },
|
|
@@ -335,6 +343,10 @@ const CATALOG = [
|
|
|
335
343
|
{ path: 'correlationId', type: 'string', optional: true },
|
|
336
344
|
{ path: 'agentAid', type: 'string', optional: true },
|
|
337
345
|
{ path: 'permissionMode', type: 'string', optional: true },
|
|
346
|
+
{ path: 'decision', type: 'string' },
|
|
347
|
+
{ path: 'decisionSource', type: 'string' },
|
|
348
|
+
{ path: 'executed', type: 'boolean' },
|
|
349
|
+
{ path: 'executionState', type: 'string' },
|
|
338
350
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
339
351
|
],
|
|
340
352
|
},
|
|
@@ -846,3 +858,9 @@ export function getEventCatalog(opts = {}) {
|
|
|
846
858
|
const namespaces = [...new Set(events.map(entry => entry.namespace))].sort();
|
|
847
859
|
return { namespaces, events };
|
|
848
860
|
}
|
|
861
|
+
/** Whether an event Trigger pattern can subscribe to daemon-internal events. */
|
|
862
|
+
export function eventPatternIncludesInternal(pattern) {
|
|
863
|
+
return CATALOG.some(entry => entry.internal === true && (pattern === '*'
|
|
864
|
+
|| pattern === entry.type
|
|
865
|
+
|| (pattern.endsWith(':*') && entry.namespace === pattern.slice(0, -2))));
|
|
866
|
+
}
|
|
@@ -17,7 +17,7 @@ import { handlePendingQQBotContactBindMessage, registerPendingQQBotContactBind }
|
|
|
17
17
|
import { handlePendingWecomContactBindMessage, registerPendingWecomContactBind } from '../../channels/wecom.js';
|
|
18
18
|
import { handlePendingWechatContactBindMessage, registerPendingWechatContactBind } from '../../channels/wechat.js';
|
|
19
19
|
import { authorizeAccess, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
20
|
-
import { MenuDiagnosticLimiter, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, menuSuccess, normalizeMenuError, parseMenuControl, validateMenuRequest, evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, isAunMenuTokenRequired, menuCommandForName, } from '../command/menu-protocol.js';
|
|
20
|
+
import { MenuDiagnosticLimiter, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, menuSuccess, normalizeMenuError, parseMenuControl, validateMenuRequest, evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, isAunMenuTokenRequired, menuCommandForName, normalizeMenuResponseTiming, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived, } from '../command/menu-protocol.js';
|
|
21
21
|
import { resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from '../command/menu-catalog.js';
|
|
22
22
|
import { MenuTokenStore } from '../command/menu-token-store.js';
|
|
23
23
|
import { SessionRenewService } from '../session/session-renew.js';
|
|
@@ -691,6 +691,7 @@ export class MessageBridge {
|
|
|
691
691
|
// that timestamp through session resolution and queue processing.
|
|
692
692
|
// The fallback is only for legacy adapters that do not provide it.
|
|
693
693
|
const receivedAt = msg.receivedAt ?? Date.now();
|
|
694
|
+
const menuTiming = normalizeMenuResponseTiming(msg);
|
|
694
695
|
const channelKey = adapter?.channelKey || channelName;
|
|
695
696
|
const owningAgent = this.agentRegistry?.resolveByChannel(channelKey)
|
|
696
697
|
?? this.agentRegistry?.resolveByChannel(channelName);
|
|
@@ -772,14 +773,24 @@ export class MessageBridge {
|
|
|
772
773
|
logger.info(`[MessageBridge] Inbound admission denied before business routing: self=${selfAid} actor=${actorId ?? '<none>'} channel=${channelKey} reason=${admissionDecision.reason}`);
|
|
773
774
|
return;
|
|
774
775
|
}
|
|
776
|
+
// Menu is a protocol control plane. Its parsing and response path must
|
|
777
|
+
// remain available throughout the agent lifecycle; lifecycle gates
|
|
778
|
+
// ordinary model execution, not protocol handling.
|
|
775
779
|
const menuControl = resolvedChannelType === 'aun'
|
|
776
780
|
? parseMenuControl(content)
|
|
777
781
|
: { isMenu: false };
|
|
778
782
|
let menuResponseWarning;
|
|
779
783
|
if (menuControl.isMenu) {
|
|
784
|
+
// AUN captures this mark at its adapter boundary. Legacy/test
|
|
785
|
+
// adapters fall back to the bridge entry mark captured above.
|
|
786
|
+
Object.assign(msg, menuTiming);
|
|
780
787
|
this.logMenuInbound(channelName, msg, menuControl);
|
|
781
788
|
if (!hasValidMenuId(menuControl)) {
|
|
782
789
|
this.logMenuDiagnostic('missing-id', channelName, msg, menuControl);
|
|
790
|
+
logMenuRequestCompleted(menuControl.request, undefined, this.menuFlowContext(channelName, msg), {
|
|
791
|
+
delivery: 'dropped',
|
|
792
|
+
reason: 'missing-id',
|
|
793
|
+
});
|
|
783
794
|
return;
|
|
784
795
|
}
|
|
785
796
|
const versionError = evaluateEvolMenuVersionGate({
|
|
@@ -916,7 +927,8 @@ export class MessageBridge {
|
|
|
916
927
|
// 2. 命令快速路径(去除引用前缀后检查,兼容话题中引用上文的情况)
|
|
917
928
|
const contentForCmd = content.replace(/^(>[^\n]*\n)+\n?/, '').trim();
|
|
918
929
|
const cmdContent = contentForCmd || content;
|
|
919
|
-
const isCmd = msg.source !== 'handoff'
|
|
930
|
+
const isCmd = msg.source !== 'handoff'
|
|
931
|
+
&& this.cmdHandler.isCommand(cmdContent);
|
|
920
932
|
if (isCmd) {
|
|
921
933
|
logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
|
|
922
934
|
// 命令也要记录入方向 jsonl(不创建 session,直接用 chatDirPath 计算路径)
|
|
@@ -953,11 +965,12 @@ export class MessageBridge {
|
|
|
953
965
|
logger.debug(`[MessageBridge] Failed to log inbound command: ${e}`);
|
|
954
966
|
}
|
|
955
967
|
}
|
|
956
|
-
if (msg.source !== 'handoff'
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
968
|
+
if (msg.source !== 'handoff'
|
|
969
|
+
&& await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => {
|
|
970
|
+
const taskId = `cmd-${msg.messageId || Date.now()}`;
|
|
971
|
+
logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId, correlationId: taskId, sessionId: msg.replyContext?.sessionId, agentAid: msg.selfAID, payload: { kind: 'command.result', text } });
|
|
972
|
+
return sendReply(msg.channelId, text, msg.replyContext);
|
|
973
|
+
}, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext, msg.messageId, msg.selfAID, authSubject))
|
|
961
974
|
return;
|
|
962
975
|
// 3. session 解析(使用 Channel 层填充的 chatType)
|
|
963
976
|
if (!(await this.canCreateThreadSession(channelName, msg, chatType))) {
|
|
@@ -1408,6 +1421,9 @@ export class MessageBridge {
|
|
|
1408
1421
|
}
|
|
1409
1422
|
}
|
|
1410
1423
|
async sendMenuResponse(adapter, channel, msg, response) {
|
|
1424
|
+
const request = this.menuRequestForLog(msg, response);
|
|
1425
|
+
const flowContext = this.menuFlowContext(channel, msg);
|
|
1426
|
+
const timedResponse = withMenuProcessingTime(response, msg);
|
|
1411
1427
|
if (!adapter?.send) {
|
|
1412
1428
|
this.logMenuDiagnostic('transport-unavailable', channel, msg, {
|
|
1413
1429
|
isMenu: true,
|
|
@@ -1417,6 +1433,10 @@ export class MessageBridge {
|
|
|
1417
1433
|
id: response.id,
|
|
1418
1434
|
name: response.name,
|
|
1419
1435
|
}, 'TEMPORARILY_UNAVAILABLE');
|
|
1436
|
+
logMenuRequestCompleted(request, timedResponse, flowContext, {
|
|
1437
|
+
delivery: 'dropped',
|
|
1438
|
+
reason: 'transport-unavailable',
|
|
1439
|
+
});
|
|
1420
1440
|
return;
|
|
1421
1441
|
}
|
|
1422
1442
|
const agentName = this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>';
|
|
@@ -1429,6 +1449,10 @@ export class MessageBridge {
|
|
|
1429
1449
|
};
|
|
1430
1450
|
if (msg.channelType === 'aun' && !replyContext.delivery) {
|
|
1431
1451
|
logger.warn(`[MessageBridge] dropping AUN menu response without trusted delivery route: channel=${channel} channelId=${msg.channelId}`);
|
|
1452
|
+
logMenuRequestCompleted(request, timedResponse, flowContext, {
|
|
1453
|
+
delivery: 'dropped',
|
|
1454
|
+
reason: 'missing-delivery-route',
|
|
1455
|
+
});
|
|
1432
1456
|
return;
|
|
1433
1457
|
}
|
|
1434
1458
|
const envelope = buildEnvelope({
|
|
@@ -1438,7 +1462,18 @@ export class MessageBridge {
|
|
|
1438
1462
|
agentName,
|
|
1439
1463
|
replyContext,
|
|
1440
1464
|
});
|
|
1441
|
-
|
|
1465
|
+
try {
|
|
1466
|
+
await adapter.send(envelope, { kind: 'custom', channelType: channel, payload: timedResponse });
|
|
1467
|
+
logMenuRequestCompleted(request, timedResponse, flowContext, { delivery: 'sent' });
|
|
1468
|
+
}
|
|
1469
|
+
catch (error) {
|
|
1470
|
+
logMenuRequestCompleted(request, timedResponse, flowContext, {
|
|
1471
|
+
delivery: 'failed',
|
|
1472
|
+
reason: 'transport-error',
|
|
1473
|
+
transportError: error,
|
|
1474
|
+
});
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1442
1477
|
}
|
|
1443
1478
|
menuTokenOwnerAid(channel, msg) {
|
|
1444
1479
|
return msg.selfAID || this.agentRegistry?.resolveByChannel(channel)?.aid;
|
|
@@ -1466,6 +1501,7 @@ export class MessageBridge {
|
|
|
1466
1501
|
return normalized.length > 0 && normalized.length <= 256 ? normalized : undefined;
|
|
1467
1502
|
}
|
|
1468
1503
|
logMenuInbound(channel, msg, parsed) {
|
|
1504
|
+
logMenuRequestReceived(parsed.request, this.menuFlowContext(channel, msg));
|
|
1469
1505
|
logger.channelIn({
|
|
1470
1506
|
channel,
|
|
1471
1507
|
channelId: this.shortHash(msg.channelId),
|
|
@@ -1482,6 +1518,21 @@ export class MessageBridge {
|
|
|
1482
1518
|
},
|
|
1483
1519
|
});
|
|
1484
1520
|
}
|
|
1521
|
+
menuRequestForLog(msg, response) {
|
|
1522
|
+
try {
|
|
1523
|
+
const parsed = JSON.parse(msg.content);
|
|
1524
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
1525
|
+
return parsed;
|
|
1526
|
+
}
|
|
1527
|
+
catch {
|
|
1528
|
+
// Fall through to a minimal correlation record.
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
type: 'menu.unknown',
|
|
1532
|
+
id: response.id,
|
|
1533
|
+
...(response.name ? { name: response.name } : {}),
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1485
1536
|
logMenuDiagnostic(category, channel, msg, parsed, code) {
|
|
1486
1537
|
const scope = msg.chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
|
|
1487
1538
|
const key = `${category}:${channel}:${this.shortHash(scope)}`;
|
|
@@ -1611,6 +1662,13 @@ export class MessageBridge {
|
|
|
1611
1662
|
* 撤回消息:先查 debounce 窗口,再查 message queue,最后查正在执行的任务。
|
|
1612
1663
|
* @returns true 如果找到并取消/中断
|
|
1613
1664
|
*/
|
|
1665
|
+
menuFlowContext(channel, msg) {
|
|
1666
|
+
return {
|
|
1667
|
+
source: 'agent-aun',
|
|
1668
|
+
selfAid: msg.selfAID ?? this.agentRegistry?.resolveByChannel(channel)?.aid,
|
|
1669
|
+
messageId: msg.messageId,
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1614
1672
|
cancel(messageId, selfAID) {
|
|
1615
1673
|
// 阶段 1: debounce 窗口(尚未入队)
|
|
1616
1674
|
for (const d of this.debouncers.values()) {
|
|
@@ -13,7 +13,7 @@ import { isHostChinese } from '../../utils/locale.js';
|
|
|
13
13
|
import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
14
14
|
import { isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
|
|
15
15
|
import { summarizeToolInput } from '../../utils/tool-summary.js';
|
|
16
|
-
import { getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
16
|
+
import { agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
17
17
|
import { renderKitSections } from '../../eck/kit-renderer.js';
|
|
18
18
|
import { renderMessageBody } from '../../eck/message-renderer.js';
|
|
19
19
|
import { syncGroupRulesContext } from '../../eck/group-rules-sync.js';
|
|
@@ -47,6 +47,59 @@ import { deriveSessionTitle, shouldAutoFillSessionTitle } from '../session/sessi
|
|
|
47
47
|
import { SessionTurnCoordinator } from '../session/session-turn-coordinator.js';
|
|
48
48
|
import { recordTriggerExecutionAnomaly } from '../../trigger/anomaly-store.js';
|
|
49
49
|
import { classifyToolErrorCode } from '../permission/tool-error-code.js';
|
|
50
|
+
import { buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
51
|
+
export class PauseController {
|
|
52
|
+
state = 'running';
|
|
53
|
+
waiters = new Set();
|
|
54
|
+
requestPause() {
|
|
55
|
+
if (this.state !== 'running')
|
|
56
|
+
return false;
|
|
57
|
+
this.state = 'pause_requested';
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
isPauseRequested() { return this.state === 'pause_requested'; }
|
|
61
|
+
isPaused() { return this.state === 'paused'; }
|
|
62
|
+
waitAtToolBoundary(signal) {
|
|
63
|
+
if (signal?.aborted)
|
|
64
|
+
return Promise.resolve('cancelled');
|
|
65
|
+
if (this.state === 'running')
|
|
66
|
+
return Promise.resolve('none');
|
|
67
|
+
this.state = 'paused';
|
|
68
|
+
return new Promise(resolve => {
|
|
69
|
+
const waiter = { resolve: () => { }, abort: undefined };
|
|
70
|
+
const finish = (result) => {
|
|
71
|
+
this.waiters.delete(waiter);
|
|
72
|
+
if (waiter.abort)
|
|
73
|
+
signal?.removeEventListener('abort', waiter.abort);
|
|
74
|
+
resolve(result);
|
|
75
|
+
};
|
|
76
|
+
waiter.resolve = finish;
|
|
77
|
+
if (signal) {
|
|
78
|
+
waiter.abort = () => finish('cancelled');
|
|
79
|
+
signal.addEventListener('abort', waiter.abort, { once: true });
|
|
80
|
+
}
|
|
81
|
+
this.waiters.add(waiter);
|
|
82
|
+
if (this.state === 'running')
|
|
83
|
+
finish('released');
|
|
84
|
+
else if (signal?.aborted)
|
|
85
|
+
finish('cancelled');
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
resume() {
|
|
89
|
+
if (this.state === 'running')
|
|
90
|
+
return false;
|
|
91
|
+
this.state = 'running';
|
|
92
|
+
for (const waiter of [...this.waiters])
|
|
93
|
+
waiter.resolve('released');
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
cancel() {
|
|
97
|
+
this.state = 'running';
|
|
98
|
+
for (const waiter of [...this.waiters])
|
|
99
|
+
waiter.resolve('cancelled');
|
|
100
|
+
}
|
|
101
|
+
clear() { this.cancel(); }
|
|
102
|
+
}
|
|
50
103
|
function isShowActivitiesMode(value) {
|
|
51
104
|
return value === 'all' || value === 'text' || value === 'none';
|
|
52
105
|
}
|
|
@@ -592,6 +645,28 @@ export class ResponseEngine {
|
|
|
592
645
|
registerBuiltinModes(registry);
|
|
593
646
|
this.responseCoordinator = new ResponseModeCoordinator(registry);
|
|
594
647
|
}
|
|
648
|
+
pauseControllers = new Map();
|
|
649
|
+
getPauseController(sessionId) {
|
|
650
|
+
let controller = this.pauseControllers.get(sessionId);
|
|
651
|
+
if (!controller) {
|
|
652
|
+
controller = new PauseController();
|
|
653
|
+
this.pauseControllers.set(sessionId, controller);
|
|
654
|
+
}
|
|
655
|
+
return controller;
|
|
656
|
+
}
|
|
657
|
+
pauseSession(sessionId) { return this.getPauseController(sessionId).requestPause(); }
|
|
658
|
+
resumeSession(sessionId) { return this.pauseControllers.get(sessionId)?.resume() ?? false; }
|
|
659
|
+
isPauseRequested(sessionId) {
|
|
660
|
+
const controller = this.pauseControllers.get(sessionId);
|
|
661
|
+
return !!controller && (controller.isPauseRequested() || controller.isPaused());
|
|
662
|
+
}
|
|
663
|
+
clearPauseSession(sessionId, expected) {
|
|
664
|
+
const controller = this.pauseControllers.get(sessionId);
|
|
665
|
+
if (expected && controller !== expected)
|
|
666
|
+
return;
|
|
667
|
+
controller?.clear();
|
|
668
|
+
this.pauseControllers.delete(sessionId);
|
|
669
|
+
}
|
|
595
670
|
setInteractionRouter(router) {
|
|
596
671
|
this.interactionRouter = router;
|
|
597
672
|
// 等待用户交互期间暂停 idle 监控,应答/取消/超时后恢复——
|
|
@@ -609,6 +684,7 @@ export class ResponseEngine {
|
|
|
609
684
|
this.messageQueue = queue;
|
|
610
685
|
}
|
|
611
686
|
async interruptSession(sessionId, reason) {
|
|
687
|
+
this.clearPauseSession(sessionId);
|
|
612
688
|
// A retry delay has no active runner stream to abort. Wake it before any
|
|
613
689
|
// session-store I/O so /stop and a newer message take effect immediately.
|
|
614
690
|
this.cancelRetryDelays(sessionId);
|
|
@@ -1165,7 +1241,7 @@ export class ResponseEngine {
|
|
|
1165
1241
|
static COMMAND_PREFIXES = [
|
|
1166
1242
|
'/new', '/pwd', '/help', '/status', '/restart',
|
|
1167
1243
|
'/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork',
|
|
1168
|
-
'/stop', '/clear', '/compact', '/del', '/perm', '/file', '/check',
|
|
1244
|
+
'/stop', '/pause', '/resume', '/clear', '/compact', '/del', '/perm', '/file', '/check',
|
|
1169
1245
|
'/s ', '/name ', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode',
|
|
1170
1246
|
'/aid', '/upgrade', '/evolagent',
|
|
1171
1247
|
];
|
|
@@ -1293,7 +1369,10 @@ export class ResponseEngine {
|
|
|
1293
1369
|
// 按 session.baseagent 选择 agent 后端(idle-kill 路径需要 interrupt)
|
|
1294
1370
|
let agent;
|
|
1295
1371
|
try {
|
|
1296
|
-
|
|
1372
|
+
const runnerSelfAid = this.agentRegistry?.resolveByChannel(channelKey)?.aid
|
|
1373
|
+
|| session.selfAID
|
|
1374
|
+
|| message.selfAID;
|
|
1375
|
+
agent = this.getAgent(channelKey, session.baseagent, runnerSelfAid);
|
|
1297
1376
|
}
|
|
1298
1377
|
catch (error) {
|
|
1299
1378
|
if (error instanceof BaseagentRunnerUnavailableError) {
|
|
@@ -1526,6 +1605,10 @@ export class ResponseEngine {
|
|
|
1526
1605
|
const taskAgentAid = message.selfAID || session.selfAID;
|
|
1527
1606
|
const owningAgentForTask = this.agentRegistry?.resolveByChannel(channelKey)
|
|
1528
1607
|
?? (taskAgentAid ? this.agentRegistry?.get(taskAgentAid) : null);
|
|
1608
|
+
const runnerSelfAid = owningAgentForTask?.aid || session.selfAID || message.selfAID;
|
|
1609
|
+
const lifecycle = owningAgentForTask?.config?.lifecycle;
|
|
1610
|
+
const isActive = lifecycle === 'active';
|
|
1611
|
+
const isBootstrapping = lifecycle === 'bootstrapping';
|
|
1529
1612
|
// Per-method agent name for stats bucketing (agent.name or '<unknown>')
|
|
1530
1613
|
const agentNameForStats = owningAgentForTask?.name ?? taskAgentAid ?? '<unknown>';
|
|
1531
1614
|
if (!channelInfo) {
|
|
@@ -1533,6 +1616,15 @@ export class ResponseEngine {
|
|
|
1533
1616
|
this.publishTriggerExecutionFailure(message, `unknown_channel:${channelKey}`);
|
|
1534
1617
|
return;
|
|
1535
1618
|
}
|
|
1619
|
+
// Agent-owned turns are runnable only in the two explicit execution
|
|
1620
|
+
// states. An untransitioned `created` agent or a malformed/missing
|
|
1621
|
+
// lifecycle must not fall through to a base runner's default prompt.
|
|
1622
|
+
if (owningAgentForTask && !isActive && !isBootstrapping) {
|
|
1623
|
+
const blockedLifecycle = lifecycle ?? 'missing';
|
|
1624
|
+
logger.error(`[ResponseEngine] Agent lifecycle is not runnable: agent=${owningAgentForTask.aid} lifecycle=${blockedLifecycle}`);
|
|
1625
|
+
this.publishTriggerExecutionFailure(message, `agent_lifecycle_not_runnable:${blockedLifecycle}`);
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1536
1628
|
// 二次拦截:如果命令消息绕过 MessageBridge 的 handleCommand 泄漏到这里,
|
|
1537
1629
|
// 静默丢弃而不是发送给 Agent(命令已在 MessageBridge 层处理过)
|
|
1538
1630
|
const rawContent = message.content.replace(/^(>[^\n]*\n)+\n?/, '').trim();
|
|
@@ -1546,7 +1638,7 @@ export class ResponseEngine {
|
|
|
1546
1638
|
const identityRole = session.identity?.role || 'none';
|
|
1547
1639
|
let agent;
|
|
1548
1640
|
try {
|
|
1549
|
-
agent = this.getAgent(channelKey, session.baseagent,
|
|
1641
|
+
agent = this.getAgent(channelKey, session.baseagent, runnerSelfAid);
|
|
1550
1642
|
}
|
|
1551
1643
|
catch (error) {
|
|
1552
1644
|
if (error instanceof BaseagentRunnerUnavailableError) {
|
|
@@ -1569,6 +1661,7 @@ export class ResponseEngine {
|
|
|
1569
1661
|
}
|
|
1570
1662
|
// 为本次任务处理生成唯一 task_id(客户端生成,格式 task-{10hex})
|
|
1571
1663
|
const taskId = `task-${crypto.randomUUID().replace(/-/g, '').slice(0, 10)}`;
|
|
1664
|
+
const taskPauseController = this.getPauseController(session.id);
|
|
1572
1665
|
let turnLease;
|
|
1573
1666
|
const inputCausation = normalizeCausation(message.causation) ?? createRootCausation();
|
|
1574
1667
|
const taskCausation = deriveCausation(inputCausation);
|
|
@@ -1604,7 +1697,12 @@ export class ResponseEngine {
|
|
|
1604
1697
|
const currentChannelType = options?.channelType || message.channel;
|
|
1605
1698
|
const adapterAny = channelInfo.adapter;
|
|
1606
1699
|
const adapterSelfAid = typeof adapterAny._selfAid === 'function' ? adapterAny._selfAid() : undefined;
|
|
1607
|
-
|
|
1700
|
+
// During bootstrap the lifecycle owner is the authoritative identity. Do
|
|
1701
|
+
// not let a stale/crafted message or session selfAID redirect agent.md
|
|
1702
|
+
// access or the two lifecycle commands to another local AID.
|
|
1703
|
+
const selfAid = isBootstrapping
|
|
1704
|
+
? owningAgentForTask?.aid
|
|
1705
|
+
: adapterSelfAid || message.selfAID || session.selfAID || undefined;
|
|
1608
1706
|
const registrySelfName = selfAid ? this.agentRegistry?.resolveDisplayName?.(selfAid) : undefined;
|
|
1609
1707
|
const adapterSelfName = typeof adapterAny._selfName === 'function' ? adapterAny._selfName() : undefined;
|
|
1610
1708
|
const selfName = registrySelfName ?? adapterSelfName;
|
|
@@ -1684,9 +1782,10 @@ export class ResponseEngine {
|
|
|
1684
1782
|
chatType,
|
|
1685
1783
|
peerType,
|
|
1686
1784
|
});
|
|
1785
|
+
const responseModeAgentDir = selfAid ? resolveAgentDir(selfAid) : undefined;
|
|
1687
1786
|
// mentionMode:顶层通用参数(先读出备用,投递/入队策略后续接入)
|
|
1688
1787
|
const mentionMode = effectiveAgentConfig?.mentionMode ?? 'disabled';
|
|
1689
|
-
const resolvedMode = triggerChatModeOverride || systemOrServicePeer
|
|
1788
|
+
const resolvedMode = triggerChatModeOverride || systemOrServicePeer || !responseModeAgentDir
|
|
1690
1789
|
? null // trigger 强制覆盖或 system/service 强制 interactive 时,不走插件解析
|
|
1691
1790
|
: this.responseCoordinator.resolveMode(effectiveAgentConfig?.responseMode, // 标量:关系级>agent级>注册表首选
|
|
1692
1791
|
chatModeFallback, effectiveAgentConfig?.responseModeParams, // 按模式分桶的参数字典
|
|
@@ -1706,7 +1805,7 @@ export class ResponseEngine {
|
|
|
1706
1805
|
send: async () => { }, // 引擎自行发送,插件 handleOutbound 只做决策
|
|
1707
1806
|
},
|
|
1708
1807
|
logger,
|
|
1709
|
-
agentDir:
|
|
1808
|
+
agentDir: responseModeAgentDir,
|
|
1710
1809
|
});
|
|
1711
1810
|
if (resolvedMode) {
|
|
1712
1811
|
logger.info('[ResponseSystem] selected mode=' + resolvedMode.mode.id + ' source=' + resolvedMode.source + ' chatType=' + chatType + ' peerKey=' + (peerKey ?? 'none') + ' chatMode=' + chatModeFallback);
|
|
@@ -2208,6 +2307,7 @@ export class ResponseEngine {
|
|
|
2208
2307
|
approvalRouting,
|
|
2209
2308
|
approvalInteractionPolicy: authorizationIdentity ? 'deny' : 'interactive',
|
|
2210
2309
|
recordExecutionAnomaly,
|
|
2310
|
+
pauseController: taskPauseController,
|
|
2211
2311
|
preToolUsePolicyHook: pureSessionPolicyHook,
|
|
2212
2312
|
armApprovedDelegationCommand: this.agentDelegationRegistry
|
|
2213
2313
|
? (carrierToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
|
|
@@ -2363,8 +2463,8 @@ export class ResponseEngine {
|
|
|
2363
2463
|
// Personal state is exposed to the manifest as vars. The manifest owns
|
|
2364
2464
|
// loading persona.md and working.md into the system prompt.
|
|
2365
2465
|
const owningAgent = owningAgentForTask;
|
|
2366
|
-
const persona = owningAgent?.getPersona?.() || undefined;
|
|
2367
|
-
const working = owningAgent?.getWorkingMemory?.() || undefined;
|
|
2466
|
+
const persona = isActive ? (owningAgent?.getPersona?.() || undefined) : undefined;
|
|
2467
|
+
const working = isActive ? (owningAgent?.getWorkingMemory?.() || undefined) : undefined;
|
|
2368
2468
|
// 计算 peerKey:群聊固定按 groupId/channelId,私聊按发送者 peerId。
|
|
2369
2469
|
// 这样单条和积压合并批次不会因队列状态不同而切换关系级配置。
|
|
2370
2470
|
const normalizedBaseagent = normalizeBaseagent(agent.name);
|
|
@@ -2521,6 +2621,12 @@ export class ResponseEngine {
|
|
|
2521
2621
|
if (shouldPassPermissionMode) {
|
|
2522
2622
|
modelOverride = { ...(modelOverride || {}), permissionMode: effectivePermissionMode };
|
|
2523
2623
|
}
|
|
2624
|
+
if (isBootstrapping && selfAid) {
|
|
2625
|
+
modelOverride = {
|
|
2626
|
+
...(modelOverride || {}),
|
|
2627
|
+
bootstrapAgentMdPath: agentMdPath(selfAid),
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2524
2630
|
if (normalizedBaseagent.canonical === 'claude') {
|
|
2525
2631
|
modelOverride = {
|
|
2526
2632
|
...(modelOverride || {}),
|
|
@@ -2567,6 +2673,9 @@ export class ResponseEngine {
|
|
|
2567
2673
|
VENUES_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'venues') : undefined,
|
|
2568
2674
|
selfAid: selfAid || undefined,
|
|
2569
2675
|
selfName: selfName || undefined,
|
|
2676
|
+
agentMdPath: selfAid ? agentMdPath(selfAid) : undefined,
|
|
2677
|
+
lifecycle,
|
|
2678
|
+
isBootstrapping,
|
|
2570
2679
|
hasPersona: !!persona,
|
|
2571
2680
|
hasWorkingMemory: !!working,
|
|
2572
2681
|
peerId: peerIdRaw || undefined,
|
|
@@ -2637,7 +2746,12 @@ export class ResponseEngine {
|
|
|
2637
2746
|
// 按会话原型(sessionType)选 manifest 文件:config.sessionManifests 映射,缺省回退主 manifest。
|
|
2638
2747
|
const sessionType = session.sessionType ?? 'main';
|
|
2639
2748
|
const sessionManifests = this.agentRegistry?.resolveByChannel(channelKey)?.config?.sessionManifests;
|
|
2640
|
-
|
|
2749
|
+
// Bootstrap always uses the lifecycle-gated main manifest. A custom
|
|
2750
|
+
// session manifest (for example the auxiliary manifest with `always`
|
|
2751
|
+
// sections) must not reintroduce normal rules or command context.
|
|
2752
|
+
const manifestFile = isBootstrapping
|
|
2753
|
+
? 'eck_manifest.json'
|
|
2754
|
+
: sessionManifests?.[sessionType] ?? 'eck_manifest.json';
|
|
2641
2755
|
const kitContext = renderKitSections(kitCtx, manifestFile);
|
|
2642
2756
|
effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
|
|
2643
2757
|
// ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
|
|
@@ -3834,6 +3948,7 @@ export class ResponseEngine {
|
|
|
3834
3948
|
if (this.activeRenderers.get(session.id)?.taskId === taskId) {
|
|
3835
3949
|
this.activeRenderers.delete(session.id);
|
|
3836
3950
|
}
|
|
3951
|
+
this.clearPauseSession(session.id, taskPauseController);
|
|
3837
3952
|
}
|
|
3838
3953
|
// [迁移探针] 任务收尾:记录工具提醒最终状态并落盘(防线 1)
|
|
3839
3954
|
if (snapshot.isEnabled()) {
|
|
@@ -4420,8 +4535,15 @@ export class ResponseEngine {
|
|
|
4420
4535
|
// 重置最后回复追踪
|
|
4421
4536
|
lastReplyText = '';
|
|
4422
4537
|
hasProjectedCurrentReplyText = false;
|
|
4538
|
+
const lifecycleEventKey = buildToolLifecycleEventKey({
|
|
4539
|
+
sessionId: session.id,
|
|
4540
|
+
callId: event.callId,
|
|
4541
|
+
correlationId: event.correlationId,
|
|
4542
|
+
});
|
|
4423
4543
|
this.eventBus.publish({
|
|
4424
4544
|
type: 'tool:use',
|
|
4545
|
+
eventKey: lifecycleEventKey,
|
|
4546
|
+
eventPhase: 'use',
|
|
4425
4547
|
sessionId: session.id,
|
|
4426
4548
|
toolName: event.name,
|
|
4427
4549
|
input: event.input,
|
|
@@ -4429,6 +4551,10 @@ export class ResponseEngine {
|
|
|
4429
4551
|
...(event.correlationId || event.callId ? { correlationId: event.correlationId ?? event.callId } : {}),
|
|
4430
4552
|
agentAid: session.selfAID ?? 'unknown',
|
|
4431
4553
|
permissionMode: permissionMode ?? 'unknown',
|
|
4554
|
+
decision: 'pending',
|
|
4555
|
+
decisionSource: 'runner',
|
|
4556
|
+
executed: false,
|
|
4557
|
+
executionState: 'requested',
|
|
4432
4558
|
timestamp: Date.now(),
|
|
4433
4559
|
causation,
|
|
4434
4560
|
});
|
|
@@ -4487,8 +4613,15 @@ export class ResponseEngine {
|
|
|
4487
4613
|
logger.warn('[ResponseEngine] auto clear queue after ec ctl queue failed:', error);
|
|
4488
4614
|
}
|
|
4489
4615
|
}
|
|
4616
|
+
const lifecycleEventKey = buildToolLifecycleEventKey({
|
|
4617
|
+
sessionId: session.id,
|
|
4618
|
+
callId: event.callId,
|
|
4619
|
+
correlationId: event.correlationId,
|
|
4620
|
+
});
|
|
4490
4621
|
this.eventBus.publish({
|
|
4491
4622
|
type: 'tool:result',
|
|
4623
|
+
eventKey: lifecycleEventKey,
|
|
4624
|
+
eventPhase: 'result',
|
|
4492
4625
|
sessionId: session.id,
|
|
4493
4626
|
toolName: event.name,
|
|
4494
4627
|
isError: event.isError,
|
|
@@ -4498,6 +4631,10 @@ export class ResponseEngine {
|
|
|
4498
4631
|
...(event.correlationId || event.callId ? { correlationId: event.correlationId ?? event.callId } : {}),
|
|
4499
4632
|
agentAid: session.selfAID ?? 'unknown',
|
|
4500
4633
|
permissionMode: permissionMode ?? 'unknown',
|
|
4634
|
+
decision: event.isError ? 'error' : 'allow',
|
|
4635
|
+
decisionSource: 'runner',
|
|
4636
|
+
executed: true,
|
|
4637
|
+
executionState: event.isError ? 'failed' : 'completed',
|
|
4501
4638
|
timestamp: Date.now(),
|
|
4502
4639
|
causation,
|
|
4503
4640
|
});
|