evolcore 0.0.24 → 0.0.26
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 +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ import { MessageQueue } from './core/message/message-queue.js';
|
|
|
46
46
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
47
47
|
import { evaluateOutboundContactAdmission } from './core/message/message-admission.js';
|
|
48
48
|
import { enforceMenuResponseSize, evaluateMenuClientCompatibility, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
|
|
49
|
-
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
49
|
+
import { computeBuildId, readBuildIdentity, readEvolcoreRuntimeFlavor, readEvolcoreStartedAtIso, readInstalledEvolcoreVersion, tagErrorTextWithEvolcoreVersion, } from './utils/evolcore-version.js';
|
|
50
50
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
51
51
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
52
52
|
import { BootstrapService, completeBootstrapWithWelcome } from './core/bootstrap-service.js';
|
|
@@ -75,6 +75,7 @@ import { buildReloadHooks } from './core/channel-loader.js';
|
|
|
75
75
|
import { IpcServer } from './ipc.js';
|
|
76
76
|
import { logger, setLogLevel } from './utils/logger.js';
|
|
77
77
|
import { remoteAccessService } from './core/auth/remote-access-store.js';
|
|
78
|
+
import { DiagService, executeDiagIpc } from './core/command/diag-service.js';
|
|
78
79
|
import { createEcwebSupervisor } from './utils/ecweb-supervisor.js';
|
|
79
80
|
import { writeMain, removeAll, isMainWinner, scanInstances } from './utils/instance-registry.js';
|
|
80
81
|
import { detectDuplicates } from './core/evolagent-registry.js';
|
|
@@ -147,6 +148,45 @@ function summarizeOutboundPayload(payload) {
|
|
|
147
148
|
}
|
|
148
149
|
return s;
|
|
149
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* 给用户可见的错误文案打上 EvolCore 版本标记,便于从对端截图直接判断版本与运行形态。
|
|
153
|
+
*
|
|
154
|
+
* 打标范围只含"出错了"语义的 payload:
|
|
155
|
+
* - `result.error` / `system.error`:任务级与系统级失败(含沙箱、权限、超时)
|
|
156
|
+
* - `status.error`:终态元数据,附带结构化版本字段供前端不受截断影响地展示
|
|
157
|
+
* - `command.error`:仅当带 `reasonCode`(授权/权限拒绝)时才打。命令参数写错、
|
|
158
|
+
* 未知命令这类输入校验反馈不打——它们与版本无关,逐条加前缀只会变成噪音。
|
|
159
|
+
*
|
|
160
|
+
* 返回新对象,不改调用方持有的 payload(同一 payload 可能被重复发送到多个渠道)。
|
|
161
|
+
*/
|
|
162
|
+
function tagErrorPayloadWithEvolcoreVersion(payload) {
|
|
163
|
+
switch (payload.kind) {
|
|
164
|
+
case 'result.error':
|
|
165
|
+
case 'system.error':
|
|
166
|
+
return { ...payload, text: tagErrorTextWithEvolcoreVersion(payload.text) };
|
|
167
|
+
case 'command.error':
|
|
168
|
+
if (!payload.reasonCode)
|
|
169
|
+
return payload;
|
|
170
|
+
return { ...payload, text: tagErrorTextWithEvolcoreVersion(payload.text) };
|
|
171
|
+
case 'status.error': {
|
|
172
|
+
const metadata = payload.metadata;
|
|
173
|
+
return {
|
|
174
|
+
...payload,
|
|
175
|
+
metadata: {
|
|
176
|
+
...metadata,
|
|
177
|
+
...(typeof metadata?.message === 'string'
|
|
178
|
+
? { message: tagErrorTextWithEvolcoreVersion(metadata.message) }
|
|
179
|
+
: {}),
|
|
180
|
+
evolcoreVersion: readInstalledEvolcoreVersion(),
|
|
181
|
+
evolcoreFlavor: readEvolcoreRuntimeFlavor(),
|
|
182
|
+
evolcoreStartedAt: readEvolcoreStartedAtIso(),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
return payload;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
150
190
|
function shouldCountSentPayload(payload) {
|
|
151
191
|
return [
|
|
152
192
|
'result.text',
|
|
@@ -610,6 +650,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
610
650
|
}));
|
|
611
651
|
ipcServer.setControlToken(controlToken);
|
|
612
652
|
ipcServer.setRemoteAccessService(remoteAccessService);
|
|
653
|
+
ipcServer.setRemoteAccessOwnerProvider(() => daemonCfg.owners?.[0]);
|
|
613
654
|
ipcServer.setShutdownExecutor(async (reason) => {
|
|
614
655
|
if (!shutdown) {
|
|
615
656
|
pendingShutdownReason = reason;
|
|
@@ -757,6 +798,12 @@ async function main() {
|
|
|
757
798
|
// Service managers do not necessarily export TMPDIR. Establish the private
|
|
758
799
|
// managed root before any runner or task runtime is initialized.
|
|
759
800
|
ensureProcessManagedTempDir();
|
|
801
|
+
const runtimeEntry = fileURLToPath(import.meta.url);
|
|
802
|
+
const diskDistDir = path.join(getPackageRoot(), 'dist');
|
|
803
|
+
const runtimeIsDist = path.resolve(path.dirname(runtimeEntry)) === path.resolve(diskDistDir);
|
|
804
|
+
const runtimeBuildIdentity = readBuildIdentity(runtimeIsDist ? path.dirname(runtimeEntry) : diskDistDir, readEvolcoreVersion());
|
|
805
|
+
if (!runtimeIsDist)
|
|
806
|
+
runtimeBuildIdentity.id = null;
|
|
760
807
|
// 启动信息:目录类型 + 版本号 + 代码最新时间戳
|
|
761
808
|
{
|
|
762
809
|
const pkgRoot = getPackageRoot();
|
|
@@ -798,6 +845,10 @@ async function main() {
|
|
|
798
845
|
console.error(`[EvolCore] 代码时间: ${latestMtime ? fmtTime(latestMtime) : '?'}`);
|
|
799
846
|
}
|
|
800
847
|
console.error('[EvolCore] ✓ Version info printed');
|
|
848
|
+
const diskBuildId = computeBuildId(diskDistDir);
|
|
849
|
+
console.error(`[EvolCore] 运行 build ID: ${runtimeIsDist ? (runtimeBuildIdentity.id ?? '?') : 'source'}`);
|
|
850
|
+
console.error(`[EvolCore] 磁盘 dist build ID: ${diskBuildId ?? '?'}`);
|
|
851
|
+
logger.info(`[Build] runtimeMode=${runtimeIsDist ? 'dist' : 'source'} runtimeBuildId=${runtimeBuildIdentity.id ?? 'source'} diskDistBuildId=${diskBuildId ?? 'unknown'} match=${runtimeIsDist && runtimeBuildIdentity.id !== null && runtimeBuildIdentity.id === diskBuildId}`);
|
|
801
852
|
// 过滤飞书 SDK 的 info 日志
|
|
802
853
|
const originalLog = console.log;
|
|
803
854
|
const originalInfo = console.info;
|
|
@@ -975,13 +1026,13 @@ async function main() {
|
|
|
975
1026
|
port: ecwebConfig.port ?? DEFAULT_ECWEB_PORT,
|
|
976
1027
|
logger,
|
|
977
1028
|
});
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1029
|
+
// ECWeb is optional and has its own health checks/restart loop. Starting
|
|
1030
|
+
// it must not hold up the daemon's agent/IPC initialization or ready
|
|
1031
|
+
// signal; status will report it as starting until the first healthy probe.
|
|
1032
|
+
onShutdown(() => ecwebSupervisor?.stop());
|
|
1033
|
+
void ecwebSupervisor.start().catch((error) => {
|
|
983
1034
|
logger.warn(`ECWeb supervisor 启动失败(不影响 daemon 主流程): ${error?.message || error}`);
|
|
984
|
-
}
|
|
1035
|
+
});
|
|
985
1036
|
}
|
|
986
1037
|
if (bindBootstrapMode && daemonCfg.aid && loadAllAgents().agents.length === 0) {
|
|
987
1038
|
await runBindBootstrapDaemon(daemonCfg);
|
|
@@ -1918,7 +1969,9 @@ async function main() {
|
|
|
1918
1969
|
function registerChannelInstance(inst) {
|
|
1919
1970
|
// 0. 包装 adapter.send,记录所有出站到 channel-out.log
|
|
1920
1971
|
const originalSend = inst.adapter.send.bind(inst.adapter);
|
|
1921
|
-
inst.adapter.send = async (envelope,
|
|
1972
|
+
inst.adapter.send = async (envelope, rawPayload) => {
|
|
1973
|
+
// 版本标记在记账之前打,让 channel-out.log 与对端看到的文案一致。
|
|
1974
|
+
const payload = rawPayload ? tagErrorPayloadWithEvolcoreVersion(rawPayload) : rawPayload;
|
|
1922
1975
|
const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey)
|
|
1923
1976
|
?? agentRegistry.resolveByChannel(inst.adapter.channelName);
|
|
1924
1977
|
logger.channelOut({
|
|
@@ -2316,6 +2369,14 @@ async function main() {
|
|
|
2316
2369
|
// A failed bind (for example, a stale Windows named pipe) must abort startup
|
|
2317
2370
|
// without creating a second AUN session that can kick the old daemon.
|
|
2318
2371
|
let controlChannel;
|
|
2372
|
+
const diagService = new DiagService(() => ({ controlAid: loadDaemonConfig().aid, owners: loadDaemonConfig().owners ?? [] }), async (to, text) => {
|
|
2373
|
+
if (!controlChannel)
|
|
2374
|
+
throw new Error('Control AID channel is unavailable');
|
|
2375
|
+
const result = await controlChannel.sendDaemonMsg({ to, payload: { type: 'text', text }, encrypt: true });
|
|
2376
|
+
if (!result.ok)
|
|
2377
|
+
throw new Error(result.error ?? 'Invitation send failed');
|
|
2378
|
+
});
|
|
2379
|
+
cmdHandler.setDiagService(diagService);
|
|
2319
2380
|
let shutdown;
|
|
2320
2381
|
let pendingShutdownReason;
|
|
2321
2382
|
const ipcServer = new IpcServer(resolvePaths().socket, () => {
|
|
@@ -2335,6 +2396,13 @@ async function main() {
|
|
|
2335
2396
|
return {
|
|
2336
2397
|
pid: process.pid,
|
|
2337
2398
|
uptime: snap.uptimeMs,
|
|
2399
|
+
build: {
|
|
2400
|
+
runtimeMode: runtimeIsDist ? 'dist' : 'source',
|
|
2401
|
+
runtimeId: runtimeBuildIdentity.id,
|
|
2402
|
+
diskDistId: computeBuildId(diskDistDir),
|
|
2403
|
+
entry: runtimeBuildIdentity.entry,
|
|
2404
|
+
version: runtimeBuildIdentity.version,
|
|
2405
|
+
},
|
|
2338
2406
|
controlPlane: {
|
|
2339
2407
|
ready: true,
|
|
2340
2408
|
owned: processLevelOwners.length > 0,
|
|
@@ -2384,6 +2452,7 @@ async function main() {
|
|
|
2384
2452
|
return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
|
|
2385
2453
|
});
|
|
2386
2454
|
ipcServer.setDaemonRuntimeEpoch(agentDelegationRegistry.getRuntimeEpoch());
|
|
2455
|
+
ipcServer.setRemoteAccessOwnerProvider(() => processLevelOwners[0]);
|
|
2387
2456
|
cmdHandler.setDaemonMonitorProvider(() => ipcServer.getMonitorSnapshot().snapshot);
|
|
2388
2457
|
// Register every IPC executor/provider before exposing the endpoint. The
|
|
2389
2458
|
// function declaration is hoisted, while its invocation remains here so a
|
|
@@ -2446,14 +2515,16 @@ async function main() {
|
|
|
2446
2515
|
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
2447
2516
|
});
|
|
2448
2517
|
// connect() 失败不置空实例:AUNChannel 内部有无限重连(SDK auto_reconnect +
|
|
2449
|
-
// scheduleReconnect
|
|
2450
|
-
|
|
2451
|
-
|
|
2518
|
+
// scheduleReconnect),首连失败会自愈;启动连接放到后台,避免 ready signal
|
|
2519
|
+
// 被远端网关/TLS 首连延迟阻塞。Attach the handlers below before the
|
|
2520
|
+
// promise can complete so an early control message is still accepted.
|
|
2521
|
+
const controlConnectPromise = controlChannel.connect()
|
|
2522
|
+
.then(() => {
|
|
2452
2523
|
logger.info(`✓ 控制 AID 已连接: ${daemonCfg.aid}`);
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2524
|
+
})
|
|
2525
|
+
.catch((e) => {
|
|
2455
2526
|
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
2456
|
-
}
|
|
2527
|
+
});
|
|
2457
2528
|
// 控制 AID 接收 daemon-owner 或有效远程访问 token 授权的指令:
|
|
2458
2529
|
// 1. /pair — daemon 远程控制配对码(文本快路径)
|
|
2459
2530
|
// 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
|
|
@@ -2522,6 +2593,13 @@ async function main() {
|
|
|
2522
2593
|
const isRoleMenu = menuControl.isMenu
|
|
2523
2594
|
&& (menuControl.name === 'role' || menuControl.request.cmd === '/role');
|
|
2524
2595
|
const currentProcessOwners = loadDaemonConfig().owners ?? [];
|
|
2596
|
+
if (/^\/diag(?:\s|$)/i.test(text)) {
|
|
2597
|
+
const command = text.slice(5).trim();
|
|
2598
|
+
const result = await diagService.execute(command.toLowerCase() === 'stop'
|
|
2599
|
+
? { action: 'stop' } : { action: 'start', problem: command }, opts.peerId);
|
|
2600
|
+
await controlChannel.sendMessage(opts.channelId, result.message, controlReplyContext(opts));
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2525
2603
|
if (menuControl.isMenu && !hasValidMenuId(menuControl)) {
|
|
2526
2604
|
logger.warn(`[ControlMenu] dropped malformed request without id type=${menuControl.type}`);
|
|
2527
2605
|
logMenuRequestCompleted(menuControl.request, undefined, menuFlowContext, {
|
|
@@ -2595,7 +2673,7 @@ async function main() {
|
|
|
2595
2673
|
return;
|
|
2596
2674
|
}
|
|
2597
2675
|
// owner 发的其他内容:提示可用指令
|
|
2598
|
-
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair
|
|
2676
|
+
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取远程控制配对码)、/diag <问题描述>、/diag stop', controlReplyContext(opts));
|
|
2599
2677
|
}
|
|
2600
2678
|
catch (e) {
|
|
2601
2679
|
if (activeMenuRequest && !menuCompletionLogged) {
|
|
@@ -2608,6 +2686,7 @@ async function main() {
|
|
|
2608
2686
|
logger.warn(`控制 AID 消息处理失败: ${e?.message || e}`);
|
|
2609
2687
|
}
|
|
2610
2688
|
});
|
|
2689
|
+
void controlConnectPromise;
|
|
2611
2690
|
// ── Service Proxy:把本地服务(ecweb 等)通过控制 AID 暴露到 AUN 网络 ──
|
|
2612
2691
|
// 挂在控制 AUNChannel 上,动态解引用其 client(规避重连换 client)。
|
|
2613
2692
|
// 失败只 warn,不影响 daemon 主流程。
|
|
@@ -2859,6 +2938,17 @@ async function main() {
|
|
|
2859
2938
|
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2860
2939
|
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2861
2940
|
ipcServer.setAidOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleAidOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2941
|
+
ipcServer.setDiagExecutor(cmd => executeDiagIpc(diagService, agentDelegationRegistry, loadDaemonConfig().owners ?? [], cmd));
|
|
2942
|
+
ipcServer.setMenuClientExecutor(async (cmd) => {
|
|
2943
|
+
if (!Array.isArray(cmd.argv) || cmd.argv.some((v) => typeof v !== 'string') || typeof cmd.sessionId !== 'string')
|
|
2944
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'A managed session and Menu argv are required' };
|
|
2945
|
+
return cmdHandler.handleMenuClientOperation(cmd.argv, cmd.sessionId, cmd.delegationToken, cmd.delegationCommandHash, async (options) => {
|
|
2946
|
+
const instance = channelInstances.find(item => item.channelType === 'aun' && item.channel.getAidState()?.aid === options.fromAid);
|
|
2947
|
+
if (!instance)
|
|
2948
|
+
throw new Error('Current Agent AUN channel is unavailable');
|
|
2949
|
+
return instance.channel.requestRemoteMenu(options);
|
|
2950
|
+
});
|
|
2951
|
+
});
|
|
2862
2952
|
ipcServer.setGroupOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleGroupOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2863
2953
|
ipcServer.setMsgOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2864
2954
|
ipcServer.setFsOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleFsOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
@@ -3005,7 +3095,14 @@ async function main() {
|
|
|
3005
3095
|
error: decision.reason,
|
|
3006
3096
|
};
|
|
3007
3097
|
}
|
|
3008
|
-
return {
|
|
3098
|
+
return {
|
|
3099
|
+
ok: true,
|
|
3100
|
+
aid: selfAid,
|
|
3101
|
+
currentSessionId: params.sessionId,
|
|
3102
|
+
managementRole,
|
|
3103
|
+
role: subject.role,
|
|
3104
|
+
actorId: delegation.grant.actorId,
|
|
3105
|
+
};
|
|
3009
3106
|
};
|
|
3010
3107
|
const resolveHandoffQueryScope = async (params, operation) => {
|
|
3011
3108
|
if (params.sessionId) {
|
|
@@ -3027,6 +3124,22 @@ async function main() {
|
|
|
3027
3124
|
return scope;
|
|
3028
3125
|
return responseEngine.returnAuthorizedHandoffResult({ sessionId: params.sessionId, handoffId: params.handoffId, content: params.content }, scope.aid);
|
|
3029
3126
|
});
|
|
3127
|
+
ipcServer.setHandoffRecoverReturnExecutor(async (params) => {
|
|
3128
|
+
if (!params.sessionId)
|
|
3129
|
+
return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'current session is required' };
|
|
3130
|
+
const scope = await resolveManagedHandoffScope({ ...params, sessionId: params.sessionId }, 'ec.handoff.recover');
|
|
3131
|
+
if (!scope.ok)
|
|
3132
|
+
return scope;
|
|
3133
|
+
if (scope.role !== 'owner') {
|
|
3134
|
+
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: 'handoff recovery requires the current Agent owner' };
|
|
3135
|
+
}
|
|
3136
|
+
return responseEngine.recoverAuthorizedHandoffResult({
|
|
3137
|
+
sessionId: params.sessionId,
|
|
3138
|
+
handoffId: params.handoffId,
|
|
3139
|
+
content: params.content,
|
|
3140
|
+
actorId: scope.actorId,
|
|
3141
|
+
}, scope.aid);
|
|
3142
|
+
});
|
|
3030
3143
|
ipcServer.setHandoffStatusExecutor(async (params) => {
|
|
3031
3144
|
const scope = await resolveHandoffQueryScope(params, 'ec.handoff.status');
|
|
3032
3145
|
if (!scope.ok)
|
|
@@ -3177,6 +3290,7 @@ async function main() {
|
|
|
3177
3290
|
thread: params.thread,
|
|
3178
3291
|
targetChatType: params.scope === 'group' ? 'group' : 'private',
|
|
3179
3292
|
explicitReturnPolicy: params.returnPolicy,
|
|
3293
|
+
handoffContext: params.handoffContext,
|
|
3180
3294
|
originAuthorization: {
|
|
3181
3295
|
actorId: delegation.grant.actorId,
|
|
3182
3296
|
channelKey: delegation.grant.channel,
|
|
@@ -3663,7 +3777,8 @@ async function main() {
|
|
|
3663
3777
|
});
|
|
3664
3778
|
if (params.targetAid
|
|
3665
3779
|
&& params.targetAid !== delegation.grant.selfAid
|
|
3666
|
-
&& subject.dataScope !== 'daemon'
|
|
3780
|
+
&& subject.dataScope !== 'daemon'
|
|
3781
|
+
&& !(operation === 'agent.show' && isManagementRole(subject.role))) {
|
|
3667
3782
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3668
3783
|
}
|
|
3669
3784
|
const decision = authorizeOperation({
|
|
@@ -3710,18 +3825,21 @@ async function main() {
|
|
|
3710
3825
|
const taskAid = delegation.grant.selfAid;
|
|
3711
3826
|
const targetAid = sub === 'list'
|
|
3712
3827
|
? undefined
|
|
3713
|
-
: sub === '
|
|
3714
|
-
?
|
|
3715
|
-
: sub === '
|
|
3716
|
-
? (argv[2] && !argv[2].startsWith('--') ? argv[2] :
|
|
3717
|
-
:
|
|
3828
|
+
: sub === 'persona'
|
|
3829
|
+
? taskAid
|
|
3830
|
+
: sub === 'show'
|
|
3831
|
+
? (argv[2] && !argv[2].startsWith('--') ? argv[2] : taskAid)
|
|
3832
|
+
: sub === 'reload'
|
|
3833
|
+
? (argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined)
|
|
3834
|
+
: (argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined);
|
|
3718
3835
|
const operation = sub === 'list' ? 'agent.list'
|
|
3719
3836
|
: sub === 'show' ? 'agent.show'
|
|
3720
|
-
: sub === '
|
|
3721
|
-
: sub === '
|
|
3722
|
-
: sub === '
|
|
3723
|
-
: sub === '
|
|
3724
|
-
: sub === '
|
|
3837
|
+
: sub === 'persona' ? 'agent.persona'
|
|
3838
|
+
: sub === 'new' ? 'agent.create'
|
|
3839
|
+
: sub === 'enable' ? 'agent.enable'
|
|
3840
|
+
: sub === 'disable' ? 'agent.disable'
|
|
3841
|
+
: sub === 'reload' ? 'agent.reload'
|
|
3842
|
+
: sub === 'delete' ? 'agent.delete' : undefined;
|
|
3725
3843
|
if (!operation)
|
|
3726
3844
|
return fail('INVALID_ARGS', `unsupported managed agent command: ${sub}`);
|
|
3727
3845
|
if (sub === 'new' && !argv.includes('--non-interactive')) {
|
|
@@ -3753,7 +3871,7 @@ async function main() {
|
|
|
3753
3871
|
subject,
|
|
3754
3872
|
intent: {
|
|
3755
3873
|
operation,
|
|
3756
|
-
scope: operation === 'agent.show'
|
|
3874
|
+
scope: operation === 'agent.show' || operation === 'agent.persona'
|
|
3757
3875
|
? 'agent'
|
|
3758
3876
|
: operation === 'agent.reload' && targetAid
|
|
3759
3877
|
? 'agent'
|
|
@@ -3799,6 +3917,7 @@ async function main() {
|
|
|
3799
3917
|
ok: true,
|
|
3800
3918
|
aid: agent.aid,
|
|
3801
3919
|
status: agent.status,
|
|
3920
|
+
lifecycle: agent.lifecycle ?? null,
|
|
3802
3921
|
identity: agent.identity ?? { name: null, description: null },
|
|
3803
3922
|
config: {
|
|
3804
3923
|
baseagent: agent.config?.baseagent ?? null,
|
|
@@ -3809,16 +3928,50 @@ async function main() {
|
|
|
3809
3928
|
},
|
|
3810
3929
|
connection: agent.connection ?? null,
|
|
3811
3930
|
sessions: agent.sessions ?? { active: 0, last_activity: null },
|
|
3812
|
-
paths:
|
|
3813
|
-
|
|
3931
|
+
paths: subject.dataScope === 'daemon'
|
|
3932
|
+
? (agent.paths ?? { config: null, agent_md: null, project: null, data: null })
|
|
3933
|
+
: { config: null, agent_md: null, project: null, data: null },
|
|
3934
|
+
...(subject.dataScope === 'daemon' ? {} : { redacted: true }),
|
|
3814
3935
|
...(agent.createProgress ? { createProgress: agent.createProgress } : {}),
|
|
3815
3936
|
} };
|
|
3816
3937
|
}
|
|
3938
|
+
if (operation === 'agent.persona') {
|
|
3939
|
+
const rawPersonaArgs = argv.slice(2);
|
|
3940
|
+
const hasFormatSuffix = rawPersonaArgs.length >= 2
|
|
3941
|
+
&& rawPersonaArgs.at(-2) === '--format'
|
|
3942
|
+
&& rawPersonaArgs.at(-1) === 'json';
|
|
3943
|
+
const personaArgs = hasFormatSuffix ? rawPersonaArgs.slice(0, -2) : rawPersonaArgs;
|
|
3944
|
+
if (!hasFormatSuffix && rawPersonaArgs.includes('--format')) {
|
|
3945
|
+
return fail('INVALID_ARGS', 'persona 仅支持末尾的 --format json');
|
|
3946
|
+
}
|
|
3947
|
+
if (personaArgs.length === 2 && personaArgs[0] === '--file') {
|
|
3948
|
+
return fail('INVALID_ARGS', '托管 persona --file 必须由 CLI 读取后作为文本提交');
|
|
3949
|
+
}
|
|
3950
|
+
const wantsShow = personaArgs.length === 1 && personaArgs[0] === 'show';
|
|
3951
|
+
const text = personaArgs.length === 2 && personaArgs[0] === '--text'
|
|
3952
|
+
? personaArgs[1]
|
|
3953
|
+
: undefined;
|
|
3954
|
+
if (!wantsShow && !(personaArgs.length === 2 && personaArgs[0] === '--text')) {
|
|
3955
|
+
return fail('INVALID_ARGS', '用法: ec agent persona show | --text <text>');
|
|
3956
|
+
}
|
|
3957
|
+
if (text === undefined && !wantsShow)
|
|
3958
|
+
return fail('INVALID_ARGS', 'persona --text 缺少参数');
|
|
3959
|
+
const { agentPersona } = await import('./cli/agent.js');
|
|
3960
|
+
const result = await agentPersona(taskAid, wantsShow ? { trustedLocal: true } : { text, trustedLocal: true });
|
|
3961
|
+
if (!result.ok)
|
|
3962
|
+
return fail(result.code ?? 'INTERNAL', result.error);
|
|
3963
|
+
return { ok: true, result: {
|
|
3964
|
+
ok: true,
|
|
3965
|
+
aid: taskAid,
|
|
3966
|
+
content: result.content,
|
|
3967
|
+
...(result.saved ? { saved: true } : {}),
|
|
3968
|
+
} };
|
|
3969
|
+
}
|
|
3817
3970
|
if (operation === 'agent.create') {
|
|
3818
3971
|
const aid = argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined;
|
|
3819
3972
|
if (!aid)
|
|
3820
3973
|
return fail('INVALID_ARGS', 'ec agent new requires an AID');
|
|
3821
|
-
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan, parseAgentOwnerArgs } = await import('./cli/agent-create.js');
|
|
3974
|
+
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan, parseAgentOwnerArgs, parseAgentTags } = await import('./cli/agent-create.js');
|
|
3822
3975
|
let ownerOptions;
|
|
3823
3976
|
try {
|
|
3824
3977
|
ownerOptions = parseAgentOwnerArgs(argv);
|
|
@@ -3826,6 +3979,13 @@ async function main() {
|
|
|
3826
3979
|
catch (error) {
|
|
3827
3980
|
return fail('INVALID_ARGS', error instanceof Error ? error.message : String(error));
|
|
3828
3981
|
}
|
|
3982
|
+
let tags;
|
|
3983
|
+
try {
|
|
3984
|
+
tags = parseAgentTags(value('--tags'));
|
|
3985
|
+
}
|
|
3986
|
+
catch (error) {
|
|
3987
|
+
return fail('INVALID_ARGS', error instanceof Error ? error.message : String(error));
|
|
3988
|
+
}
|
|
3829
3989
|
const plan = resolveAgentCreatePlan({
|
|
3830
3990
|
aid,
|
|
3831
3991
|
project: value('--project'),
|
|
@@ -3834,6 +3994,8 @@ async function main() {
|
|
|
3834
3994
|
handoff: value('--handoff'),
|
|
3835
3995
|
name: value('--name'),
|
|
3836
3996
|
description: value('--description'),
|
|
3997
|
+
type: value('--type'),
|
|
3998
|
+
tags,
|
|
3837
3999
|
force: argv.includes('--force'),
|
|
3838
4000
|
});
|
|
3839
4001
|
const validationError = validateAgentCreatePlan(plan);
|
|
@@ -3890,6 +4052,8 @@ async function main() {
|
|
|
3890
4052
|
owners: plan.owners,
|
|
3891
4053
|
name: plan.name,
|
|
3892
4054
|
description: plan.description,
|
|
4055
|
+
type: plan.type,
|
|
4056
|
+
tags: plan.tags,
|
|
3893
4057
|
force: argv.includes('--force'),
|
|
3894
4058
|
};
|
|
3895
4059
|
try {
|
package/dist/ipc.js
CHANGED
|
@@ -76,7 +76,7 @@ function requiresLocalControlToken(cmd) {
|
|
|
76
76
|
// Managed bootstrap completion is authenticated by the session delegation
|
|
77
77
|
// ticket. A standalone CLI invocation still needs the daemon control token.
|
|
78
78
|
return LOCAL_CONTROL_COMMANDS.has(String(cmd.type ?? ''))
|
|
79
|
-
|| (cmd.type === 'evolagent.bootstrapComplete' && typeof cmd.sessionId !== 'string');
|
|
79
|
+
|| ((cmd.type === 'evolagent.bootstrapComplete' || cmd.type === 'diag') && typeof cmd.sessionId !== 'string');
|
|
80
80
|
}
|
|
81
81
|
export function summarizeMonitorAgents(agents, aids) {
|
|
82
82
|
const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
|
|
@@ -116,7 +116,10 @@ export class IpcServer {
|
|
|
116
116
|
taskRuntimeContextProvider;
|
|
117
117
|
codexPreToolUseExecutor;
|
|
118
118
|
aunMsgSender;
|
|
119
|
+
diagExecutor;
|
|
120
|
+
menuClientExecutor;
|
|
119
121
|
handoffReturnExecutor;
|
|
122
|
+
handoffRecoverReturnExecutor;
|
|
120
123
|
handoffStatusExecutor;
|
|
121
124
|
handoffListExecutor;
|
|
122
125
|
handoffTraceExecutor;
|
|
@@ -135,6 +138,8 @@ export class IpcServer {
|
|
|
135
138
|
controlToken;
|
|
136
139
|
daemonRuntimeEpoch;
|
|
137
140
|
remoteAccessService = remoteAccessService;
|
|
141
|
+
/** Current daemon owner used to bind locally issued ECWeb pairing codes. */
|
|
142
|
+
remoteAccessOwnerProvider;
|
|
138
143
|
// CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
|
|
139
144
|
// 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
|
|
140
145
|
// procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
|
|
@@ -189,6 +194,15 @@ export class IpcServer {
|
|
|
189
194
|
setRemoteAccessService(service) {
|
|
190
195
|
this.remoteAccessService = service;
|
|
191
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Supply the live process-level owner for pairing requests that do not carry
|
|
199
|
+
* an explicit owner (for example `ec watch web` and the ECWeb supervisor).
|
|
200
|
+
* The provider is evaluated for every request so owner updates take effect
|
|
201
|
+
* without restarting ECWeb.
|
|
202
|
+
*/
|
|
203
|
+
setRemoteAccessOwnerProvider(provider) {
|
|
204
|
+
this.remoteAccessOwnerProvider = provider;
|
|
205
|
+
}
|
|
192
206
|
/** Set the process-lifetime marker used to reject stale managed children. */
|
|
193
207
|
setDaemonRuntimeEpoch(epoch) {
|
|
194
208
|
this.daemonRuntimeEpoch = epoch;
|
|
@@ -362,10 +376,15 @@ export class IpcServer {
|
|
|
362
376
|
setAunMsgSender(sender) {
|
|
363
377
|
this.aunMsgSender = sender;
|
|
364
378
|
}
|
|
379
|
+
setDiagExecutor(executor) { this.diagExecutor = executor; }
|
|
380
|
+
setMenuClientExecutor(executor) { this.menuClientExecutor = executor; }
|
|
365
381
|
/** Inject internal handoff result return executor for `ec handoff return`. */
|
|
366
382
|
setHandoffReturnExecutor(executor) {
|
|
367
383
|
this.handoffReturnExecutor = executor;
|
|
368
384
|
}
|
|
385
|
+
setHandoffRecoverReturnExecutor(executor) {
|
|
386
|
+
this.handoffRecoverReturnExecutor = executor;
|
|
387
|
+
}
|
|
369
388
|
setHandoffStatusExecutor(executor) {
|
|
370
389
|
this.handoffStatusExecutor = executor;
|
|
371
390
|
}
|
|
@@ -807,6 +826,10 @@ export class IpcServer {
|
|
|
807
826
|
};
|
|
808
827
|
}
|
|
809
828
|
switch (cmd.type) {
|
|
829
|
+
case 'diag':
|
|
830
|
+
return this.diagExecutor ? this.diagExecutor(cmd) : { ok: false, code: 'NOT_CONFIGURED', message: '诊断入口未配置' };
|
|
831
|
+
case 'menu-client.request':
|
|
832
|
+
return this.menuClientExecutor ? this.menuClientExecutor(cmd) : { ok: false, code: 'NOT_CONFIGURED', error: 'Managed Menu client is not configured' };
|
|
810
833
|
case 'model.catalog': {
|
|
811
834
|
if ((cmd.self !== undefined && typeof cmd.self !== 'string')
|
|
812
835
|
|| (cmd.baseagent !== undefined && !['claude', 'codex', 'ecagent', 'gemini'].includes(cmd.baseagent))) {
|
|
@@ -887,7 +910,9 @@ export class IpcServer {
|
|
|
887
910
|
});
|
|
888
911
|
}
|
|
889
912
|
case 'remote-auth.pair-code': {
|
|
890
|
-
const
|
|
913
|
+
const requestedOwner = typeof cmd.authorizedBy === 'string' ? cmd.authorizedBy.trim() : undefined;
|
|
914
|
+
const authorizedBy = requestedOwner || this.remoteAccessOwnerProvider?.();
|
|
915
|
+
const pair = this.remoteAccessService.getPairingCode(authorizedBy);
|
|
891
916
|
return { ok: true, ...pair };
|
|
892
917
|
}
|
|
893
918
|
case 'remote-auth.issue-local': {
|
|
@@ -1094,6 +1119,10 @@ export class IpcServer {
|
|
|
1094
1119
|
return { ok: true, context };
|
|
1095
1120
|
}
|
|
1096
1121
|
case 'aun-msg-send': {
|
|
1122
|
+
if (cmd.handoffContext !== undefined && (typeof cmd.handoffContext !== 'string'
|
|
1123
|
+
|| !cmd.handoffContext.trim() || cmd.handoffContext.length > 64 * 1024 || cmd.scope === 'group')) {
|
|
1124
|
+
return { ok: false, code: 'INVALID_HANDOFF_CONTEXT', error: 'Private handoff context must contain 1–65536 characters' };
|
|
1125
|
+
}
|
|
1097
1126
|
if (!this.aunMsgSender)
|
|
1098
1127
|
return { ok: false, error: 'aun msg sender not configured' };
|
|
1099
1128
|
if (!cmd.aid || typeof cmd.aid !== 'string')
|
|
@@ -1148,6 +1177,7 @@ export class IpcServer {
|
|
|
1148
1177
|
encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
|
|
1149
1178
|
thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
|
|
1150
1179
|
returnPolicy: cmd.returnPolicy === 'required' || cmd.returnPolicy === 'none' ? cmd.returnPolicy : undefined,
|
|
1180
|
+
handoffContext: cmd.handoffContext,
|
|
1151
1181
|
originSessionId: typeof cmd.originSessionId === 'string' ? cmd.originSessionId : undefined,
|
|
1152
1182
|
originMessageId: typeof cmd.originMessageId === 'string' ? cmd.originMessageId : undefined,
|
|
1153
1183
|
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
@@ -1177,6 +1207,28 @@ export class IpcServer {
|
|
|
1177
1207
|
return { ok: false, error: e?.message || String(e) };
|
|
1178
1208
|
}
|
|
1179
1209
|
}
|
|
1210
|
+
case 'handoff-recover-return': {
|
|
1211
|
+
if (!this.handoffRecoverReturnExecutor)
|
|
1212
|
+
return { ok: false, error: 'handoff recovery not configured' };
|
|
1213
|
+
if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
|
|
1214
|
+
return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'missing sessionId' };
|
|
1215
|
+
if (typeof cmd.handoffId !== 'string' || !cmd.handoffId)
|
|
1216
|
+
return { ok: false, code: 'INVALID_HANDOFF_ID', error: 'missing handoff_id' };
|
|
1217
|
+
if (typeof cmd.content !== 'string')
|
|
1218
|
+
return { ok: false, code: 'HANDOFF_RETURN_CONTENT_REQUIRED', error: 'missing content' };
|
|
1219
|
+
try {
|
|
1220
|
+
return await this.handoffRecoverReturnExecutor({
|
|
1221
|
+
sessionId: cmd.sessionId,
|
|
1222
|
+
handoffId: cmd.handoffId,
|
|
1223
|
+
content: cmd.content,
|
|
1224
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
1225
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
catch (e) {
|
|
1229
|
+
return { ok: false, error: e?.message || String(e) };
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1180
1232
|
case 'handoff-status': {
|
|
1181
1233
|
if (!this.handoffStatusExecutor)
|
|
1182
1234
|
return { ok: false, code: 'HANDOFF_STATUS_UNAVAILABLE', error: 'handoff status not configured' };
|
|
@@ -12,7 +12,18 @@ const STATE_KEY = 'proactive';
|
|
|
12
12
|
const TOOL_REPORT_INTERVAL = 10;
|
|
13
13
|
const SEND_RECEIPT_RE = /["']?(?:message[_ -]?id|handoff[_ -]?id)["']?\s*[:=]\s*["']?([A-Za-z0-9][A-Za-z0-9._:-]*)/gi;
|
|
14
14
|
const HANDOFF_TEXT_RECEIPT_RE = /\bhandoff\s+(h-[A-Za-z0-9._-]+)/i;
|
|
15
|
-
|
|
15
|
+
const RETURN_CONTENT_RE = /["']?(?:return[_ -]?content|returnContent)["']?\s*[:=]\s*["']?([^}\n,]*)/i;
|
|
16
|
+
function hasRequiredReturnFlag(toolInput) {
|
|
17
|
+
if (!toolInput || typeof toolInput !== 'object')
|
|
18
|
+
return false;
|
|
19
|
+
const input = toolInput;
|
|
20
|
+
const commandValues = ['command', 'cmd', 'script'].flatMap(key => {
|
|
21
|
+
const value = input[key];
|
|
22
|
+
return typeof value === 'string' ? [value] : [];
|
|
23
|
+
});
|
|
24
|
+
return commandValues.some(command => /(?:^|\s)--return(?:=|\s+)['"]?required(?:['"]?)(?=\s|$)/i.test(command));
|
|
25
|
+
}
|
|
26
|
+
function hasSuccessfulSendReceipt(result, isError, requiresBusinessReturn = false) {
|
|
16
27
|
if (isError === true)
|
|
17
28
|
return false;
|
|
18
29
|
const hasReceiptValue = (value) => {
|
|
@@ -40,6 +51,41 @@ function hasSuccessfulSendReceipt(result, isError) {
|
|
|
40
51
|
};
|
|
41
52
|
if (hasExplicitFailure(result, new Set()))
|
|
42
53
|
return false;
|
|
54
|
+
const isBusinessReturn = (value) => {
|
|
55
|
+
if (typeof value !== 'string')
|
|
56
|
+
return false;
|
|
57
|
+
const normalized = value.trim();
|
|
58
|
+
if (!normalized || /^(?:null|undefined|-)$/.test(normalized))
|
|
59
|
+
return false;
|
|
60
|
+
// Contact admission/refusal is a real reply, but it is not task completion.
|
|
61
|
+
if (/(?:当前不接受陌生人消息|可发送\s*\/request|contact\s+(?:required|request)|stranger message)/i.test(normalized))
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
65
|
+
const inspectBusinessReturn = (value, seen) => {
|
|
66
|
+
if (!value || typeof value !== 'object')
|
|
67
|
+
return false;
|
|
68
|
+
if (seen.has(value))
|
|
69
|
+
return false;
|
|
70
|
+
seen.add(value);
|
|
71
|
+
if (Array.isArray(value))
|
|
72
|
+
return value.some(item => inspectBusinessReturn(item, seen));
|
|
73
|
+
for (const [key, item] of Object.entries(value)) {
|
|
74
|
+
if ((key === 'return_content' || key === 'returnContent') && isBusinessReturn(item))
|
|
75
|
+
return true;
|
|
76
|
+
if (inspectBusinessReturn(item, seen))
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
81
|
+
if (requiresBusinessReturn) {
|
|
82
|
+
if (inspectBusinessReturn(result, new Set()))
|
|
83
|
+
return true;
|
|
84
|
+
const returnText = typeof result === 'string'
|
|
85
|
+
? result.match(RETURN_CONTENT_RE)?.[1]
|
|
86
|
+
: undefined;
|
|
87
|
+
return isBusinessReturn(returnText);
|
|
88
|
+
}
|
|
43
89
|
const inspect = (value, seen) => {
|
|
44
90
|
if (!value || typeof value !== 'object')
|
|
45
91
|
return false;
|
|
@@ -153,6 +199,11 @@ export const proactiveFlow = {
|
|
|
153
199
|
// Codex only invokes this hook for local tool paths. Hosted tools stay
|
|
154
200
|
// enabled and do not satisfy or clear the pending first-send gate.
|
|
155
201
|
const isSendCommand = ctx.isSendCommand(toolName, toolInput);
|
|
202
|
+
if (!isSendCommand && ((state.firstSendRequired && !state.firstToolDone) || state.toolReportPending)) {
|
|
203
|
+
const sendFailure = ctx.describeSendCommandFailure?.(toolName, toolInput);
|
|
204
|
+
if (sendFailure)
|
|
205
|
+
return { block: true, policyCode: 'proactive_send_invalid', reason: sendFailure };
|
|
206
|
+
}
|
|
156
207
|
if (state.firstSendRequired && !state.firstToolDone) {
|
|
157
208
|
if (!isSendCommand) {
|
|
158
209
|
const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
|
|
@@ -221,7 +272,13 @@ export const proactiveFlow = {
|
|
|
221
272
|
return;
|
|
222
273
|
if (!ctx.isSendCommand(ctx.toolName, ctx.toolInput))
|
|
223
274
|
return;
|
|
224
|
-
|
|
275
|
+
const requiresBusinessReturn = hasRequiredReturnFlag(ctx.toolInput)
|
|
276
|
+
&& (typeof ctx.result === 'string'
|
|
277
|
+
? HANDOFF_TEXT_RECEIPT_RE.test(ctx.result)
|
|
278
|
+
: !!ctx.result && typeof ctx.result === 'object'
|
|
279
|
+
&& ('handoff_id' in ctx.result || 'handoffId' in ctx.result));
|
|
280
|
+
HANDOFF_TEXT_RECEIPT_RE.lastIndex = 0;
|
|
281
|
+
if (!hasSuccessfulSendReceipt(ctx.result, ctx.isError || !!ctx.error, requiresBusinessReturn))
|
|
225
282
|
return;
|
|
226
283
|
if (state.firstSendRequired && !state.firstToolDone)
|
|
227
284
|
state.firstToolDone = true;
|