evolcore 0.0.10 → 0.0.12
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 +44 -0
- package/README.md +3 -3
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +158 -44
- package/dist/agents/codex-app-server-client.js +153 -23
- package/dist/agents/codex-runner.js +331 -43
- package/dist/agents/ecagent-runner.js +53 -6
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +418 -235
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +72 -17
- package/dist/cli/agent.js +249 -22
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +52 -33
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +106 -63
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/gateway-config.js +26 -10
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +62 -154
- package/dist/core/auth/operation-catalog.js +264 -22
- package/dist/core/bootstrap-messages.js +68 -0
- package/dist/core/bootstrap-service.js +144 -10
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +24 -7
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +213 -110
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +45 -25
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +12 -5
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +237 -79
- package/dist/core/message/message-log.js +23 -0
- package/dist/core/message/message-queue.js +20 -4
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +292 -111
- package/dist/core/protected-paths.js +17 -9
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +644 -180
- package/dist/ipc.js +246 -19
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/01-overview.md +9 -0
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +44 -0
- package/kits/templates/roles/visitor.json +40 -2
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +3 -2
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
- package/dist/core/command/evol-menu-version-gate.js +0 -39
package/dist/ipc.js
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import crypto from 'crypto';
|
|
3
4
|
import os from 'os';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { logger } from './utils/logger.js';
|
|
6
7
|
import { fileCache } from './core/daemon-file-cache.js';
|
|
7
8
|
import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
|
|
9
|
+
import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
|
|
8
10
|
import { resolvePaths } from './paths.js';
|
|
9
11
|
import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
|
|
10
12
|
import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
|
|
11
13
|
import { readSystemMemoryUsage } from './utils/system-memory.js';
|
|
12
14
|
import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
|
|
15
|
+
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
13
16
|
const isWindows = process.platform === 'win32';
|
|
14
17
|
const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
|
|
18
|
+
const LOCAL_CONTROL_COMMANDS = new Set([
|
|
19
|
+
'bind.begin',
|
|
20
|
+
'bind.status',
|
|
21
|
+
'bind.cancel',
|
|
22
|
+
'dingtalk.contact-bind.register',
|
|
23
|
+
'feishu.contact-bind.register',
|
|
24
|
+
'qqbot.contact-bind.register',
|
|
25
|
+
'wecom.contact-bind.register',
|
|
26
|
+
'wechat.contact-bind.register',
|
|
27
|
+
'queue-snapshot',
|
|
28
|
+
'aun-aid-stats-record-outbound',
|
|
29
|
+
'evolagent.reload',
|
|
30
|
+
'evolagent.load',
|
|
31
|
+
'evolagent.resync',
|
|
32
|
+
'menu.exec',
|
|
33
|
+
'monitor-snapshot',
|
|
34
|
+
]);
|
|
15
35
|
export function summarizeMonitorAgents(agents, aids) {
|
|
16
36
|
const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
|
|
17
37
|
const enabledAgents = agents.filter(agent => agent.status !== 'disabled');
|
|
@@ -38,8 +58,14 @@ export class IpcServer {
|
|
|
38
58
|
agentStatsProvider;
|
|
39
59
|
queueSnapshotProvider;
|
|
40
60
|
queueActionExecutor;
|
|
61
|
+
queueOperationExecutor;
|
|
62
|
+
statusAuthorizationExecutor;
|
|
63
|
+
storageAuthorizationExecutor;
|
|
41
64
|
triggerExecutor;
|
|
65
|
+
bootstrapCompleteExecutor;
|
|
66
|
+
agentReloadExecutor;
|
|
42
67
|
taskRuntimeContextProvider;
|
|
68
|
+
codexPreToolUseExecutor;
|
|
43
69
|
aunMsgSender;
|
|
44
70
|
handoffReturnExecutor;
|
|
45
71
|
handoffStatusExecutor;
|
|
@@ -48,11 +74,16 @@ export class IpcServer {
|
|
|
48
74
|
bindExecutor;
|
|
49
75
|
configOperationExecutor;
|
|
50
76
|
contactOperationExecutor;
|
|
77
|
+
aidOperationExecutor;
|
|
78
|
+
groupOperationExecutor;
|
|
79
|
+
msgOperationExecutor;
|
|
80
|
+
fsOperationExecutor;
|
|
51
81
|
dingtalkContactBindExecutor;
|
|
52
82
|
feishuContactBindExecutor;
|
|
53
83
|
qqbotContactBindExecutor;
|
|
54
84
|
wecomContactBindExecutor;
|
|
55
85
|
wechatContactBindExecutor;
|
|
86
|
+
controlToken;
|
|
56
87
|
// CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
|
|
57
88
|
// 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
|
|
58
89
|
// procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
|
|
@@ -75,6 +106,10 @@ export class IpcServer {
|
|
|
75
106
|
setAgentRegistry(registry) {
|
|
76
107
|
this.agentRegistry = registry;
|
|
77
108
|
}
|
|
109
|
+
/** Authenticate local control-plane callers that cannot use task delegation. */
|
|
110
|
+
setControlToken(token) {
|
|
111
|
+
this.controlToken = Buffer.from(token);
|
|
112
|
+
}
|
|
78
113
|
/** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
|
|
79
114
|
setMenuExecutor(executor) {
|
|
80
115
|
this.menuExecutor = executor;
|
|
@@ -85,6 +120,21 @@ export class IpcServer {
|
|
|
85
120
|
setContactOperationExecutor(executor) {
|
|
86
121
|
this.contactOperationExecutor = executor;
|
|
87
122
|
}
|
|
123
|
+
setAidOperationExecutor(executor) {
|
|
124
|
+
this.aidOperationExecutor = executor;
|
|
125
|
+
}
|
|
126
|
+
setGroupOperationExecutor(executor) {
|
|
127
|
+
this.groupOperationExecutor = executor;
|
|
128
|
+
}
|
|
129
|
+
setMsgOperationExecutor(executor) {
|
|
130
|
+
this.msgOperationExecutor = executor;
|
|
131
|
+
}
|
|
132
|
+
setFsOperationExecutor(executor) {
|
|
133
|
+
this.fsOperationExecutor = executor;
|
|
134
|
+
}
|
|
135
|
+
setAgentReloadExecutor(executor) {
|
|
136
|
+
this.agentReloadExecutor = executor;
|
|
137
|
+
}
|
|
88
138
|
/** Inject AUN AID state aggregator for aun-aids IPC handler */
|
|
89
139
|
setAunAidProvider(provider) {
|
|
90
140
|
this.aunAidProvider = provider;
|
|
@@ -113,14 +163,30 @@ export class IpcServer {
|
|
|
113
163
|
setQueueActionExecutor(executor) {
|
|
114
164
|
this.queueActionExecutor = executor;
|
|
115
165
|
}
|
|
166
|
+
setQueueOperationExecutor(executor) {
|
|
167
|
+
this.queueOperationExecutor = executor;
|
|
168
|
+
}
|
|
169
|
+
setStatusAuthorizationExecutor(executor) {
|
|
170
|
+
this.statusAuthorizationExecutor = executor;
|
|
171
|
+
}
|
|
172
|
+
setStorageAuthorizationExecutor(executor) {
|
|
173
|
+
this.storageAuthorizationExecutor = executor;
|
|
174
|
+
}
|
|
116
175
|
/** Inject daemon-level trigger executor for ec trigger. */
|
|
117
176
|
setTriggerExecutor(executor) {
|
|
118
177
|
this.triggerExecutor = executor;
|
|
119
178
|
}
|
|
179
|
+
/** Complete an agent bootstrap in the daemon so disk and runtime state change together. */
|
|
180
|
+
setBootstrapCompleteExecutor(executor) {
|
|
181
|
+
this.bootstrapCompleteExecutor = executor;
|
|
182
|
+
}
|
|
120
183
|
/** Inject active task runtime-context provider for in-task CLI routing. */
|
|
121
184
|
setTaskRuntimeContextProvider(provider) {
|
|
122
185
|
this.taskRuntimeContextProvider = provider;
|
|
123
186
|
}
|
|
187
|
+
setCodexPreToolUseExecutor(executor) {
|
|
188
|
+
this.codexPreToolUseExecutor = executor;
|
|
189
|
+
}
|
|
124
190
|
/** Inject daemon-backed AUN private message sender for in-task `ec msg send`. */
|
|
125
191
|
setAunMsgSender(sender) {
|
|
126
192
|
this.aunMsgSender = sender;
|
|
@@ -313,9 +379,31 @@ export class IpcServer {
|
|
|
313
379
|
}
|
|
314
380
|
}
|
|
315
381
|
async handleCommand(cmd) {
|
|
382
|
+
if (LOCAL_CONTROL_COMMANDS.has(cmd.type) && !this.hasValidControlToken(cmd.controlToken)) {
|
|
383
|
+
return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
|
|
384
|
+
}
|
|
316
385
|
switch (cmd.type) {
|
|
317
386
|
case 'status':
|
|
318
387
|
return this.getStatus();
|
|
388
|
+
case 'codex-pre-tool-use': {
|
|
389
|
+
if (!this.codexPreToolUseExecutor) {
|
|
390
|
+
return { ok: false, applicable: true, decision: 'deny', reason: 'EvolCore Codex policy executor is unavailable' };
|
|
391
|
+
}
|
|
392
|
+
if (typeof cmd.threadId !== 'string' || !cmd.threadId
|
|
393
|
+
|| typeof cmd.toolName !== 'string' || !cmd.toolName) {
|
|
394
|
+
return { ok: false, applicable: true, decision: 'deny', reason: 'invalid Codex hook request' };
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
return await this.codexPreToolUseExecutor({
|
|
398
|
+
threadId: cmd.threadId,
|
|
399
|
+
toolName: cmd.toolName,
|
|
400
|
+
toolInput: cmd.toolInput,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
return { ok: false, applicable: true, decision: 'deny', reason: `EvolCore policy unavailable: ${error instanceof Error ? error.message : String(error)}` };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
319
407
|
case 'ping':
|
|
320
408
|
return { pong: true, pid: process.pid, protocolVersion: 1 };
|
|
321
409
|
case 'bind.begin': {
|
|
@@ -325,7 +413,6 @@ export class IpcServer {
|
|
|
325
413
|
bindType: cmd.bindType,
|
|
326
414
|
targetAid: cmd.targetAid,
|
|
327
415
|
agentName: cmd.agentName,
|
|
328
|
-
ownerMode: cmd.ownerMode,
|
|
329
416
|
ttlMs: cmd.ttlMs,
|
|
330
417
|
});
|
|
331
418
|
}
|
|
@@ -445,6 +532,53 @@ export class IpcServer {
|
|
|
445
532
|
sessionKey: cmd.sessionKey,
|
|
446
533
|
});
|
|
447
534
|
}
|
|
535
|
+
case 'queue.op': {
|
|
536
|
+
if (!this.queueOperationExecutor)
|
|
537
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'queue.op not configured' };
|
|
538
|
+
if (typeof cmd.agent !== 'string' || !cmd.agent || typeof cmd.sessionId !== 'string' || !cmd.sessionId) {
|
|
539
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing agent or sessionId' };
|
|
540
|
+
}
|
|
541
|
+
if (cmd.action !== undefined && !['clear', 'cancel', 'interrupt'].includes(cmd.action)) {
|
|
542
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'invalid queue action' };
|
|
543
|
+
}
|
|
544
|
+
return await this.queueOperationExecutor({
|
|
545
|
+
agent: cmd.agent,
|
|
546
|
+
action: cmd.action,
|
|
547
|
+
messageId: typeof cmd.messageId === 'string' ? cmd.messageId : undefined,
|
|
548
|
+
sessionKey: typeof cmd.sessionKey === 'string' ? cmd.sessionKey : undefined,
|
|
549
|
+
sessionId: cmd.sessionId,
|
|
550
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
551
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
case 'status.auth': {
|
|
555
|
+
if (!this.statusAuthorizationExecutor)
|
|
556
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'status authorization not configured' };
|
|
557
|
+
if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
|
|
558
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId' };
|
|
559
|
+
return await this.statusAuthorizationExecutor({
|
|
560
|
+
sessionId: cmd.sessionId,
|
|
561
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
562
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
case 'storage.auth': {
|
|
566
|
+
if (!this.storageAuthorizationExecutor)
|
|
567
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'storage authorization not configured' };
|
|
568
|
+
if (typeof cmd.sessionId !== 'string' || !cmd.sessionId || typeof cmd.aid !== 'string' || !cmd.aid || typeof cmd.operation !== 'string') {
|
|
569
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId, aid, or operation' };
|
|
570
|
+
}
|
|
571
|
+
if (!['storage.list', 'storage.quota', 'storage.upload', 'storage.download', 'storage.delete'].includes(cmd.operation)) {
|
|
572
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'invalid storage operation' };
|
|
573
|
+
}
|
|
574
|
+
return await this.storageAuthorizationExecutor({
|
|
575
|
+
sessionId: cmd.sessionId,
|
|
576
|
+
aid: cmd.aid,
|
|
577
|
+
operation: cmd.operation,
|
|
578
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
579
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
448
582
|
case 'aun-aid-stats-record-outbound': {
|
|
449
583
|
if (!this.aunAidStatsRecorder)
|
|
450
584
|
return { ok: false, error: 'recorder not configured' };
|
|
@@ -510,14 +644,17 @@ export class IpcServer {
|
|
|
510
644
|
}
|
|
511
645
|
: undefined;
|
|
512
646
|
try {
|
|
647
|
+
const normalizedPayload = payload && Object.prototype.hasOwnProperty.call(payload, 'mentions')
|
|
648
|
+
? { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) }
|
|
649
|
+
: payload;
|
|
513
650
|
return await this.aunMsgSender({
|
|
514
651
|
aid: cmd.aid,
|
|
515
652
|
to: cmd.to,
|
|
516
653
|
scope: cmd.scope === 'group' ? 'group' : 'msg',
|
|
517
|
-
payload,
|
|
654
|
+
payload: normalizedPayload,
|
|
518
655
|
file,
|
|
519
|
-
mentions:
|
|
520
|
-
? cmd.mentions
|
|
656
|
+
mentions: Object.prototype.hasOwnProperty.call(cmd, 'mentions')
|
|
657
|
+
? normalizeAunMentionEntries(cmd.mentions)
|
|
521
658
|
: undefined,
|
|
522
659
|
encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
|
|
523
660
|
thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
|
|
@@ -530,7 +667,7 @@ export class IpcServer {
|
|
|
530
667
|
});
|
|
531
668
|
}
|
|
532
669
|
catch (e) {
|
|
533
|
-
return { ok: false, error: e?.message || String(e) };
|
|
670
|
+
return { ok: false, error: e?.message || String(e), code: e?.code };
|
|
534
671
|
}
|
|
535
672
|
}
|
|
536
673
|
case 'handoff-return': {
|
|
@@ -559,6 +696,8 @@ export class IpcServer {
|
|
|
559
696
|
return this.handoffStatusExecutor({
|
|
560
697
|
sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
|
|
561
698
|
handoffId: cmd.handoffId,
|
|
699
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
700
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
562
701
|
});
|
|
563
702
|
}
|
|
564
703
|
case 'handoff-list': {
|
|
@@ -578,6 +717,8 @@ export class IpcServer {
|
|
|
578
717
|
state: state,
|
|
579
718
|
filterSessionId: typeof cmd.filterSessionId === 'string' ? cmd.filterSessionId : undefined,
|
|
580
719
|
limit,
|
|
720
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
721
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
581
722
|
});
|
|
582
723
|
}
|
|
583
724
|
case 'handoff-trace': {
|
|
@@ -595,6 +736,8 @@ export class IpcServer {
|
|
|
595
736
|
agent: typeof cmd.agent === 'string' ? cmd.agent : undefined,
|
|
596
737
|
handoffId: cmd.handoffId,
|
|
597
738
|
limit,
|
|
739
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
740
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
598
741
|
});
|
|
599
742
|
}
|
|
600
743
|
case 'ctl': {
|
|
@@ -629,6 +772,54 @@ export class IpcServer {
|
|
|
629
772
|
}
|
|
630
773
|
return await this.contactOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
631
774
|
}
|
|
775
|
+
case 'aid.op': {
|
|
776
|
+
if (!this.aidOperationExecutor)
|
|
777
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'aid.op not configured' };
|
|
778
|
+
const { argv, sessionId, delegationToken } = cmd;
|
|
779
|
+
if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
|
|
780
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
781
|
+
}
|
|
782
|
+
if (delegationToken !== undefined && typeof delegationToken !== 'string') {
|
|
783
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
|
|
784
|
+
}
|
|
785
|
+
return await this.aidOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
786
|
+
}
|
|
787
|
+
case 'group.op': {
|
|
788
|
+
if (!this.groupOperationExecutor)
|
|
789
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'group.op not configured' };
|
|
790
|
+
const { argv, sessionId, delegationToken } = cmd;
|
|
791
|
+
if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
|
|
792
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
793
|
+
}
|
|
794
|
+
if (delegationToken !== undefined && typeof delegationToken !== 'string') {
|
|
795
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
|
|
796
|
+
}
|
|
797
|
+
return await this.groupOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
798
|
+
}
|
|
799
|
+
case 'msg.op': {
|
|
800
|
+
if (!this.msgOperationExecutor)
|
|
801
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'msg.op not configured' };
|
|
802
|
+
const { argv, sessionId, delegationToken } = cmd;
|
|
803
|
+
if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
|
|
804
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
805
|
+
}
|
|
806
|
+
if (delegationToken !== undefined && typeof delegationToken !== 'string') {
|
|
807
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
|
|
808
|
+
}
|
|
809
|
+
return await this.msgOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
810
|
+
}
|
|
811
|
+
case 'fs.op': {
|
|
812
|
+
if (!this.fsOperationExecutor)
|
|
813
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'fs.op not configured' };
|
|
814
|
+
const { argv, sessionId, delegationToken } = cmd;
|
|
815
|
+
if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
|
|
816
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
817
|
+
}
|
|
818
|
+
if (delegationToken !== undefined && typeof delegationToken !== 'string') {
|
|
819
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
|
|
820
|
+
}
|
|
821
|
+
return await this.fsOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
822
|
+
}
|
|
632
823
|
case 'trigger.list':
|
|
633
824
|
case 'trigger.show':
|
|
634
825
|
case 'trigger.history':
|
|
@@ -668,29 +859,43 @@ export class IpcServer {
|
|
|
668
859
|
return { ok: false, error: `Agent "${name}" found but info missing (race?)` };
|
|
669
860
|
return { ok: true, agent: info };
|
|
670
861
|
}
|
|
862
|
+
case 'evolagent.bootstrapComplete': {
|
|
863
|
+
const aid = cmd.aid;
|
|
864
|
+
if (!aid || typeof aid !== 'string')
|
|
865
|
+
return { ok: false, error: 'missing aid' };
|
|
866
|
+
if (!this.bootstrapCompleteExecutor)
|
|
867
|
+
return { ok: false, error: 'bootstrap complete executor not configured' };
|
|
868
|
+
try {
|
|
869
|
+
return await this.bootstrapCompleteExecutor(aid, {
|
|
870
|
+
sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
|
|
871
|
+
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
872
|
+
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
873
|
+
controlToken: typeof cmd.controlToken === 'string' ? cmd.controlToken : undefined,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
catch (e) {
|
|
877
|
+
return { ok: false, error: e?.message || String(e) };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
671
880
|
case 'evolagent.reload': {
|
|
672
881
|
if (!this.agentRegistry)
|
|
673
882
|
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
674
883
|
const name = cmd.name;
|
|
675
884
|
if (!name || typeof name !== 'string')
|
|
676
885
|
return { ok: false, error: 'missing name' };
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return { ok: false, error: 'Reload hooks not initialized' };
|
|
886
|
+
if (!this.agentReloadExecutor)
|
|
887
|
+
return { ok: false, error: 'Agent reload coordinator not initialized' };
|
|
680
888
|
try {
|
|
681
889
|
const a = this.agentRegistry.get(name);
|
|
682
890
|
if (!a)
|
|
683
891
|
return { ok: false, error: `Agent "${name}" not found` };
|
|
684
|
-
|
|
685
|
-
return { ok: false, error: 'EvolAgentRegistry.reload not available' };
|
|
686
|
-
// 'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload 策略,平时不查盘:
|
|
687
|
-
// reload 是它唯一的刷新时机,必须先清再 reload,否则重载出来的 agent 仍用旧模板。
|
|
688
|
-
const { invalidateKitCache } = await import('./eck/kit-renderer.js');
|
|
689
|
-
invalidateKitCache();
|
|
690
|
-
await this.agentRegistry.reload(name, hooks);
|
|
892
|
+
await this.agentReloadExecutor(name, { force: cmd.force === true });
|
|
691
893
|
return { ok: true, result: `Agent "${name}" reloaded` };
|
|
692
894
|
}
|
|
693
895
|
catch (e) {
|
|
896
|
+
if (e instanceof AgentReloadBusyError) {
|
|
897
|
+
return { ok: false, code: e.code, busyCount: e.busyCount, error: e.message };
|
|
898
|
+
}
|
|
694
899
|
return { ok: false, error: e?.message || String(e) };
|
|
695
900
|
}
|
|
696
901
|
}
|
|
@@ -729,7 +934,11 @@ export class IpcServer {
|
|
|
729
934
|
if (!this.menuExecutor)
|
|
730
935
|
return { ok: false, error: 'menu.exec not configured' };
|
|
731
936
|
try {
|
|
732
|
-
const response = await this.menuExecutor(cmd.payload,
|
|
937
|
+
const response = await this.menuExecutor(cmd.payload, {
|
|
938
|
+
source: 'ecweb',
|
|
939
|
+
actorAid: null,
|
|
940
|
+
localDirect: true,
|
|
941
|
+
});
|
|
733
942
|
return { ok: true, response };
|
|
734
943
|
}
|
|
735
944
|
catch (e) {
|
|
@@ -813,17 +1022,35 @@ export class IpcServer {
|
|
|
813
1022
|
return { error: `unknown command: ${cmd.type}` };
|
|
814
1023
|
}
|
|
815
1024
|
}
|
|
1025
|
+
hasValidControlToken(value) {
|
|
1026
|
+
if (!this.controlToken || typeof value !== 'string')
|
|
1027
|
+
return false;
|
|
1028
|
+
const provided = Buffer.from(value);
|
|
1029
|
+
return provided.length === this.controlToken.length
|
|
1030
|
+
&& crypto.timingSafeEqual(provided, this.controlToken);
|
|
1031
|
+
}
|
|
816
1032
|
}
|
|
817
1033
|
/**
|
|
818
1034
|
* Query the running EvolCore daemon via Unix socket.
|
|
819
1035
|
* Returns null if the service is not running or the socket is unreachable.
|
|
820
1036
|
*/
|
|
821
1037
|
export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
822
|
-
|
|
823
|
-
|
|
1038
|
+
let authenticatedCmd = cmd;
|
|
1039
|
+
if (!process.env.EVOLCORE_SESSION_ID
|
|
1040
|
+
&& LOCAL_CONTROL_COMMANDS.has(cmd.type)
|
|
1041
|
+
&& typeof cmd.controlToken !== 'string') {
|
|
1042
|
+
try {
|
|
1043
|
+
const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
|
|
1044
|
+
if (controlToken)
|
|
1045
|
+
authenticatedCmd = { ...cmd, controlToken };
|
|
1046
|
+
}
|
|
1047
|
+
catch { }
|
|
1048
|
+
}
|
|
1049
|
+
const delegationCommandHash = typeof authenticatedCmd.delegationToken === 'string'
|
|
1050
|
+
&& typeof authenticatedCmd.delegationCommandHash !== 'string'
|
|
824
1051
|
? hashCurrentDelegatedCommand()
|
|
825
1052
|
: undefined;
|
|
826
|
-
const request = delegationCommandHash ? { ...
|
|
1053
|
+
const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
|
|
827
1054
|
return new Promise((resolve) => {
|
|
828
1055
|
const conn = net.connect(socketPath);
|
|
829
1056
|
let buf = '';
|
|
@@ -41,7 +41,8 @@ export class ResponseModeCoordinator {
|
|
|
41
41
|
const { mode, modeConfig } = resolved;
|
|
42
42
|
contextDeps.logger.debug('[ResponseSystem] resolveMode mode=' + mode.id + ' source=' + resolved.source + ' chatmode=' + (modeConfig.chatmode ?? 'none'));
|
|
43
43
|
const context = this.contextBuilder.build(mode.id, { ...contextDeps, modeConfig });
|
|
44
|
-
|
|
44
|
+
const boundMode = mode.bind?.(context) ?? mode;
|
|
45
|
+
return { mode: boundMode, context, source: resolved.source };
|
|
45
46
|
}
|
|
46
47
|
catch (e) {
|
|
47
48
|
contextDeps.logger.warn(`[Coordinator] resolveMode failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -64,9 +65,10 @@ export class ResponseModeCoordinator {
|
|
|
64
65
|
...deps.contextDeps,
|
|
65
66
|
modeConfig,
|
|
66
67
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const boundMode = mode.bind?.(context) ?? mode;
|
|
69
|
+
await boundMode.initialize(context);
|
|
70
|
+
const decision = await boundMode.handleInbound(message);
|
|
71
|
+
return { modeId: boundMode.id, decision, mode: boundMode, context };
|
|
70
72
|
}
|
|
71
73
|
catch (e) {
|
|
72
74
|
deps.contextDeps.logger.warn(`[Coordinator] resolveInbound failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -65,29 +65,37 @@ export const proactiveFlow = {
|
|
|
65
65
|
const state = buildState(ctx.message, ctx.modeConfig ?? {});
|
|
66
66
|
ctx.state.set(STATE_KEY, state);
|
|
67
67
|
},
|
|
68
|
-
// ─── configureRun:policyHook
|
|
68
|
+
// ─── configureRun:policyHook 只判定,不推进状态 ───
|
|
69
69
|
configureRun(ctx) {
|
|
70
70
|
const state = ctx.state.get(STATE_KEY);
|
|
71
|
-
if (!state
|
|
71
|
+
if (!state)
|
|
72
72
|
return undefined;
|
|
73
73
|
return {
|
|
74
74
|
policyHook: (toolName, toolInput) => {
|
|
75
|
+
// Codex only invokes this hook for local tool paths. Hosted tools stay
|
|
76
|
+
// enabled and do not satisfy or clear the pending first-send gate.
|
|
75
77
|
const isSendCommand = ctx.isSendCommand(toolName, toolInput);
|
|
76
78
|
if (state.firstSendRequired && !state.firstToolDone) {
|
|
77
79
|
if (!isSendCommand) {
|
|
78
80
|
const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
|
|
79
81
|
const target = state.chatType === 'group' ? '群里' : '对方';
|
|
80
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
block: true,
|
|
84
|
+
policyCode: 'proactive_first_send_required',
|
|
85
|
+
reason: `请先用 ${cmdHint} 向${target}说明你的意图,再执行其他工具`,
|
|
86
|
+
};
|
|
81
87
|
}
|
|
82
|
-
state.firstToolDone = true;
|
|
83
88
|
}
|
|
84
89
|
if (state.toolReportPending) {
|
|
85
90
|
if (!isSendCommand) {
|
|
86
91
|
const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
|
|
87
92
|
const target = state.chatType === 'group' ? '群里' : '对方';
|
|
88
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
block: true,
|
|
95
|
+
policyCode: 'proactive_tool_report_required',
|
|
96
|
+
reason: `工具调用已达到 ${state.toolCount} 次,请先用 ${cmdHint} 向${target}汇报当前情况和下一步意图,再执行其他工具`,
|
|
97
|
+
};
|
|
89
98
|
}
|
|
90
|
-
state.toolReportPending = false;
|
|
91
99
|
}
|
|
92
100
|
return undefined;
|
|
93
101
|
},
|
|
@@ -96,7 +104,18 @@ export const proactiveFlow = {
|
|
|
96
104
|
// ─── onToolUse:队列未读提醒 + 工具汇报提醒 ───
|
|
97
105
|
onToolUse(ctx) {
|
|
98
106
|
const state = ctx.state.get(STATE_KEY);
|
|
99
|
-
if (!state
|
|
107
|
+
if (!state)
|
|
108
|
+
return;
|
|
109
|
+
// PreToolUse 可能被工具子进程直接调用,因此审批阶段不能推进门禁状态。
|
|
110
|
+
// 只有 runner 发布真实工具事件后,精确匹配的发送命令才提交状态。
|
|
111
|
+
const isAllowedTool = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
|
|
112
|
+
if (isAllowedTool) {
|
|
113
|
+
if (state.firstSendRequired && !state.firstToolDone)
|
|
114
|
+
state.firstToolDone = true;
|
|
115
|
+
if (state.toolReportPending)
|
|
116
|
+
state.toolReportPending = false;
|
|
117
|
+
}
|
|
118
|
+
if (!state.toolUseReminder)
|
|
100
119
|
return;
|
|
101
120
|
// 队列未读变化提醒
|
|
102
121
|
const queueLen = ctx.getQueueLength();
|
|
@@ -110,7 +129,6 @@ export const proactiveFlow = {
|
|
|
110
129
|
}
|
|
111
130
|
}
|
|
112
131
|
// 工具计数(排除表态命令白名单)
|
|
113
|
-
const isAllowedTool = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
|
|
114
132
|
if (state.toolReportRequired && !isAllowedTool)
|
|
115
133
|
state.toolCount++;
|
|
116
134
|
// 每 N 次工具调用警告
|
|
@@ -19,6 +19,7 @@ import { loadSchema } from '../../../config/schema-registry.js';
|
|
|
19
19
|
* 解析后注入,见 implementation-plan.md §三;无 auto 值)。
|
|
20
20
|
*/
|
|
21
21
|
export class SingleSessionMode {
|
|
22
|
+
context;
|
|
22
23
|
id = 'single-session';
|
|
23
24
|
displayName = '单会话模式';
|
|
24
25
|
description = '单会话直接响应,按 chatMode 决定投递方式。';
|
|
@@ -26,8 +27,20 @@ export class SingleSessionMode {
|
|
|
26
27
|
applicableScenes = ['private', 'group'];
|
|
27
28
|
get configSchema() { return loadSchema('single-session').raw; }
|
|
28
29
|
queue = new FIFOQueue();
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Registry 中的实例只承担模式定义的职责。每个实际会话都由 bind() 创建
|
|
32
|
+
* 独立实例,以免并发会话覆盖彼此的 modeConfig 和流程上下文。
|
|
33
|
+
*/
|
|
34
|
+
constructor(context) {
|
|
35
|
+
this.context = context;
|
|
36
|
+
}
|
|
37
|
+
bind(context) {
|
|
38
|
+
return new SingleSessionMode(context);
|
|
39
|
+
}
|
|
30
40
|
async initialize(context) {
|
|
41
|
+
if (this.context && this.context !== context) {
|
|
42
|
+
throw new Error('[single-session] bound instance initialized with a different context');
|
|
43
|
+
}
|
|
31
44
|
this.context = context;
|
|
32
45
|
context.logger.debug('[ResponseSystem] single-session initialized chatMode=' + this.chatMode());
|
|
33
46
|
}
|
|
@@ -12,12 +12,13 @@ export class ResponseModeSelector {
|
|
|
12
12
|
if (!mode) {
|
|
13
13
|
throw new Error(`[ResponseModeSelector] mode not found: ${modeId}`);
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const boundMode = mode.bind?.(context) ?? mode;
|
|
16
|
+
await boundMode.initialize(context);
|
|
17
|
+
context.logger.debug('[ResponseSystem] selector selected mode=' + boundMode.id + ' engine=' + (boundMode.engineType ?? 'v1'));
|
|
17
18
|
return {
|
|
18
|
-
id:
|
|
19
|
-
engineType:
|
|
20
|
-
mode,
|
|
19
|
+
id: boundMode.id,
|
|
20
|
+
engineType: boundMode.engineType ?? 'v1',
|
|
21
|
+
mode: boundMode,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -251,7 +251,11 @@ function anomalyClassKey(anomaly) {
|
|
|
251
251
|
anomaly.severity,
|
|
252
252
|
anomaly.phase,
|
|
253
253
|
anomaly.toolName,
|
|
254
|
+
anomaly.policyCode,
|
|
254
255
|
anomaly.policy,
|
|
256
|
+
anomaly.agentAid,
|
|
257
|
+
anomaly.sessionId,
|
|
258
|
+
anomaly.permissionMode,
|
|
255
259
|
anomaly.summary,
|
|
256
260
|
anomaly.effect,
|
|
257
261
|
]);
|
package/dist/utils/aid-bind.js
CHANGED
|
@@ -45,7 +45,6 @@ export class BindService {
|
|
|
45
45
|
}
|
|
46
46
|
const ttlMs = clampTtl(req.ttlMs);
|
|
47
47
|
const expiresAt = Date.now() + ttlMs;
|
|
48
|
-
const ownerMode = req.ownerMode === 'replace' ? 'replace' : 'append';
|
|
49
48
|
const task = {
|
|
50
49
|
taskId,
|
|
51
50
|
bindType: req.bindType,
|
|
@@ -56,7 +55,6 @@ export class BindService {
|
|
|
56
55
|
createdAt: Date.now(),
|
|
57
56
|
expiresAt,
|
|
58
57
|
status: 'pending',
|
|
59
|
-
ownerMode,
|
|
60
58
|
};
|
|
61
59
|
this.tasks.set(taskId, task);
|
|
62
60
|
this.taskIdsByTokenHash.set(tokenHash, taskId);
|
|
@@ -209,9 +207,7 @@ export class BindService {
|
|
|
209
207
|
if (task.bindType === 'daemon') {
|
|
210
208
|
const cfg = loadDaemonConfig();
|
|
211
209
|
const current = cfg.owners ?? [];
|
|
212
|
-
const owners =
|
|
213
|
-
? [ownerAid]
|
|
214
|
-
: [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
210
|
+
const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
215
211
|
saveDaemonConfig({ ...cfg, $schema_version: cfg.$schema_version ?? 1, owners });
|
|
216
212
|
this.opts.onDaemonOwnersUpdated?.(owners);
|
|
217
213
|
return;
|
|
@@ -220,10 +216,9 @@ export class BindService {
|
|
|
220
216
|
if (!agent)
|
|
221
217
|
throw new Error(`agent not found: ${task.targetAid}`);
|
|
222
218
|
const current = agent.owners ?? [];
|
|
223
|
-
const owners =
|
|
224
|
-
? [ownerAid]
|
|
225
|
-
: [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
219
|
+
const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
226
220
|
saveAgent({ ...agent, owners });
|
|
221
|
+
this.opts.onAgentOwnerUpdated?.(task.targetAid, ownerAid);
|
|
227
222
|
}
|
|
228
223
|
cleanup() {
|
|
229
224
|
const now = Date.now();
|