evolcore 0.0.13 → 0.0.15
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 +27 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +53 -5
- package/dist/agents/codex-app-server-client.js +123 -2
- package/dist/agents/codex-runner.js +152 -30
- package/dist/agents/ecagent-runner.js +17 -1
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/msg/managed-operation.js +63 -3
- package/dist/channels/aun.js +144 -15
- package/dist/channels/daemon.js +2 -0
- package/dist/channels/feishu.js +6 -1
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/fs-command.js +46 -9
- package/dist/cli/task-context.js +176 -0
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +6 -2
- package/dist/config/contact-book-store.js +7 -2
- package/dist/core/auth/auth-gateway.js +1 -0
- package/dist/core/auth/authorization-audit.js +32 -0
- package/dist/core/auth/operation-catalog.js +3 -3
- package/dist/core/bootstrap-service.js +7 -1
- package/dist/core/command/command-handler.js +3 -0
- package/dist/core/command/slash-handler.js +1 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/message/im-renderer.js +15 -1
- package/dist/core/message/message-bridge.js +5 -2
- package/dist/core/message/response-engine.js +138 -10
- package/dist/core/permission/approval-gateway.js +99 -16
- package/dist/core/permission/ec-command-parser.js +556 -4
- package/dist/core/permission/tool-policy.js +17 -29
- package/dist/core/runtime-lock.js +101 -0
- package/dist/index.js +30 -3
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/history.js +42 -7
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +37 -4
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/member.json +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const RUNTIME_LOCK_DIR_ENV = 'EVOLCORE_RUNTIME_LOCK_DIR';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a task-provided lock location without accepting relative or escaped
|
|
7
|
+
* paths. When no managed task context is present, callers retain their legacy
|
|
8
|
+
* on-disk lock path for daemon-side compatibility and migration tooling.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveManagedLockPath(namespace, identity, legacyPath) {
|
|
11
|
+
const configured = process.env[RUNTIME_LOCK_DIR_ENV]?.trim();
|
|
12
|
+
if (!configured || !path.isAbsolute(configured) || !namespace || !identity || !isTrustedRuntimeLockDir(configured)) {
|
|
13
|
+
return legacyPath;
|
|
14
|
+
}
|
|
15
|
+
const digest = crypto.createHash('sha256').update(identity).digest('hex').slice(0, 24);
|
|
16
|
+
return path.join(path.resolve(configured), namespace, `${digest}.lock`);
|
|
17
|
+
}
|
|
18
|
+
function isTrustedRuntimeLockDir(candidate) {
|
|
19
|
+
const resolved = path.resolve(candidate);
|
|
20
|
+
if (path.basename(resolved) !== 'evolcore-locks')
|
|
21
|
+
return false;
|
|
22
|
+
const tmpRoot = process.env.TMPDIR?.trim();
|
|
23
|
+
if (!tmpRoot || !path.isAbsolute(tmpRoot))
|
|
24
|
+
return false;
|
|
25
|
+
const resolvedTmpRoot = path.resolve(tmpRoot);
|
|
26
|
+
const relative = path.relative(resolvedTmpRoot, resolved);
|
|
27
|
+
const isWithinTmp = relative !== '..'
|
|
28
|
+
&& !relative.startsWith(`..${path.sep}`)
|
|
29
|
+
&& !path.isAbsolute(relative);
|
|
30
|
+
// The daemon supplies each task either a generic evolcore-runtime-* child
|
|
31
|
+
// or Codex's private digest directory. Both share a sibling evolcore-locks
|
|
32
|
+
// directory so mutations from separate sessions still serialize. Do not
|
|
33
|
+
// accept arbitrary TMPDIR siblings supplied by a child process.
|
|
34
|
+
const tmpBase = path.basename(resolvedTmpRoot);
|
|
35
|
+
const tmpParent = path.dirname(resolvedTmpRoot);
|
|
36
|
+
const isGenericSessionRuntime = /^evolcore-runtime-[a-f0-9]{24}$/.test(tmpBase);
|
|
37
|
+
const isCodexSessionRuntime = /^[a-f0-9]{24}$/.test(tmpBase)
|
|
38
|
+
&& /^evolcore-codex-(?:\d+|user)$/.test(path.basename(tmpParent));
|
|
39
|
+
const isManagedSessionSibling = (isGenericSessionRuntime || isCodexSessionRuntime)
|
|
40
|
+
&& path.dirname(resolved) === tmpParent;
|
|
41
|
+
if (!isWithinTmp && !isManagedSessionSibling)
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
const tmpStat = fs.lstatSync(resolvedTmpRoot);
|
|
45
|
+
if (!tmpStat.isDirectory() || tmpStat.isSymbolicLink())
|
|
46
|
+
return false;
|
|
47
|
+
if (typeof process.getuid === 'function' && tmpStat.uid !== process.getuid())
|
|
48
|
+
return false;
|
|
49
|
+
if ((tmpStat.mode & 0o077) !== 0)
|
|
50
|
+
return false;
|
|
51
|
+
if (isManagedSessionSibling) {
|
|
52
|
+
const parentStat = fs.lstatSync(tmpParent);
|
|
53
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink())
|
|
54
|
+
return false;
|
|
55
|
+
if (typeof process.getuid === 'function' && parentStat.uid !== process.getuid())
|
|
56
|
+
return false;
|
|
57
|
+
if ((parentStat.mode & 0o077) !== 0)
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const stat = fs.lstatSync(resolved);
|
|
61
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
62
|
+
return false;
|
|
63
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
64
|
+
return false;
|
|
65
|
+
return (stat.mode & 0o077) === 0;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function ensureManagedLockParent(lockPath) {
|
|
72
|
+
const configured = process.env[RUNTIME_LOCK_DIR_ENV]?.trim();
|
|
73
|
+
if (!configured || !isTrustedRuntimeLockDir(configured)) {
|
|
74
|
+
throw new Error('managed runtime lock directory is not trusted');
|
|
75
|
+
}
|
|
76
|
+
const root = path.resolve(configured);
|
|
77
|
+
const parent = path.resolve(path.dirname(lockPath));
|
|
78
|
+
const relative = path.relative(root, parent);
|
|
79
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
80
|
+
throw new Error('managed runtime lock parent escapes its runtime directory');
|
|
81
|
+
}
|
|
82
|
+
// Create each component individually. mkdir({recursive:true}) would follow a
|
|
83
|
+
// pre-existing symlink in a namespace component before it can be checked.
|
|
84
|
+
let current = root;
|
|
85
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
86
|
+
current = path.join(current, segment);
|
|
87
|
+
try {
|
|
88
|
+
fs.mkdirSync(current, { mode: 0o700 });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error?.code !== 'EEXIST')
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const stat = fs.lstatSync(current);
|
|
95
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()
|
|
96
|
+
|| (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
97
|
+
|| (stat.mode & 0o077) !== 0) {
|
|
98
|
+
throw new Error(`managed runtime lock path is not a private real directory: ${current}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -87,6 +87,7 @@ import { applyTriggerPatch } from './trigger/patch.js';
|
|
|
87
87
|
import { validateModelSelectionForRole } from './core/model/model-permission.js';
|
|
88
88
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
|
|
89
89
|
import { atomicWriteJson } from './core/session/session-fs-store.js';
|
|
90
|
+
import { ensureProcessManagedTempDir } from './cli/task-context.js';
|
|
90
91
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
|
|
91
92
|
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
92
93
|
import { MAIN_PACKAGE_NAME } from './product.js';
|
|
@@ -456,6 +457,20 @@ export async function sendSystemPayload(adapter, envelope, payload) {
|
|
|
456
457
|
}
|
|
457
458
|
async function runBindBootstrapDaemon(daemonCfg) {
|
|
458
459
|
logger.warn('[bind-bootstrap] starting control AID + IPC only');
|
|
460
|
+
// Bootstrap exits before the normal trigger scheduler is constructed. Keep
|
|
461
|
+
// the daemon-owned upgrade check present during a fresh installation too;
|
|
462
|
+
// the regular startup path will reseed this definition idempotently.
|
|
463
|
+
const bootstrapControlAid = daemonCfg.aid || '__daemon__';
|
|
464
|
+
try {
|
|
465
|
+
seedUpgradeCheckTrigger(new TriggerDefinitionManager(bootstrapControlAid), {
|
|
466
|
+
aid: bootstrapControlAid,
|
|
467
|
+
originPeerId: bootstrapControlAid,
|
|
468
|
+
baseagent: 'codex',
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
logger.warn(`[bind-bootstrap] failed to seed daemon upgrade trigger: ${error instanceof Error ? error.message : String(error)}`);
|
|
473
|
+
}
|
|
459
474
|
const bindService = new BindService({
|
|
460
475
|
receiverAid: daemonCfg.aid,
|
|
461
476
|
getAvailableBaseagents: detectAvailableBaseagentsForBind,
|
|
@@ -649,6 +664,9 @@ function readFastaunVersion() {
|
|
|
649
664
|
}
|
|
650
665
|
}
|
|
651
666
|
async function main() {
|
|
667
|
+
// Service managers do not necessarily export TMPDIR. Establish the private
|
|
668
|
+
// managed root before any runner or task runtime is initialized.
|
|
669
|
+
ensureProcessManagedTempDir();
|
|
652
670
|
// 启动信息:目录类型 + 版本号 + 代码最新时间戳
|
|
653
671
|
{
|
|
654
672
|
const pkgRoot = getPackageRoot();
|
|
@@ -1726,11 +1744,20 @@ async function main() {
|
|
|
1726
1744
|
// 0. 包装 adapter.send,记录所有出站到 channel-out.log
|
|
1727
1745
|
const originalSend = inst.adapter.send.bind(inst.adapter);
|
|
1728
1746
|
inst.adapter.send = async (envelope, payload) => {
|
|
1729
|
-
|
|
1747
|
+
const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey)
|
|
1748
|
+
?? agentRegistry.resolveByChannel(inst.adapter.channelName);
|
|
1749
|
+
logger.channelOut({
|
|
1750
|
+
channel: inst.adapter.channelName,
|
|
1751
|
+
channelId: envelope.channelId,
|
|
1752
|
+
taskId: envelope.taskId,
|
|
1753
|
+
correlationId: envelope.operationId ?? envelope.taskId,
|
|
1754
|
+
sessionId: envelope.sessionId,
|
|
1755
|
+
agentAid: owningAgent?.aid,
|
|
1756
|
+
agentName: envelope.agentName,
|
|
1757
|
+
payload: summarizeOutboundPayload(payload),
|
|
1758
|
+
});
|
|
1730
1759
|
const result = await originalSend(envelope, payload);
|
|
1731
1760
|
if (shouldCountSentPayload(payload)) {
|
|
1732
|
-
const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey)
|
|
1733
|
-
?? agentRegistry.resolveByChannel(inst.adapter.channelName);
|
|
1734
1761
|
if ((inst.channelType || inst.adapter.channelName) !== 'aun') {
|
|
1735
1762
|
try {
|
|
1736
1763
|
const logPayload = outboundPayloadToLogText(payload);
|
|
@@ -10,6 +10,84 @@
|
|
|
10
10
|
*/
|
|
11
11
|
const STATE_KEY = 'proactive';
|
|
12
12
|
const TOOL_REPORT_INTERVAL = 10;
|
|
13
|
+
const SEND_RECEIPT_RE = /["']?(?:message[_ -]?id|handoff[_ -]?id)["']?\s*[:=]\s*["']?([A-Za-z0-9][A-Za-z0-9._:-]*)/gi;
|
|
14
|
+
const HANDOFF_TEXT_RECEIPT_RE = /\bhandoff\s+(h-[A-Za-z0-9._-]+)/i;
|
|
15
|
+
function hasSuccessfulSendReceipt(result, isError) {
|
|
16
|
+
if (isError === true)
|
|
17
|
+
return false;
|
|
18
|
+
const hasReceiptValue = (value) => {
|
|
19
|
+
if (typeof value !== 'string')
|
|
20
|
+
return false;
|
|
21
|
+
const normalized = value.trim().toLowerCase();
|
|
22
|
+
return normalized.length > 0 && normalized !== '-' && normalized !== 'undefined' && normalized !== 'null';
|
|
23
|
+
};
|
|
24
|
+
const hasExplicitFailure = (value, seen) => {
|
|
25
|
+
if (!value || typeof value !== 'object')
|
|
26
|
+
return false;
|
|
27
|
+
if (seen.has(value))
|
|
28
|
+
return false;
|
|
29
|
+
seen.add(value);
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return value.some(item => hasExplicitFailure(item, seen));
|
|
32
|
+
const data = value;
|
|
33
|
+
if (Object.prototype.hasOwnProperty.call(data, 'ok') && data.ok !== true)
|
|
34
|
+
return true;
|
|
35
|
+
if (typeof data.error === 'string' && data.error.trim())
|
|
36
|
+
return true;
|
|
37
|
+
if (typeof data.status === 'string' && /^(failed|error|rejected|denied)$/i.test(data.status.trim()))
|
|
38
|
+
return true;
|
|
39
|
+
return Object.values(data).some(item => hasExplicitFailure(item, seen));
|
|
40
|
+
};
|
|
41
|
+
if (hasExplicitFailure(result, new Set()))
|
|
42
|
+
return false;
|
|
43
|
+
const inspect = (value, seen) => {
|
|
44
|
+
if (!value || typeof value !== 'object')
|
|
45
|
+
return false;
|
|
46
|
+
if (seen.has(value))
|
|
47
|
+
return false;
|
|
48
|
+
seen.add(value);
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value.some(item => inspect(item, seen));
|
|
51
|
+
for (const [key, item] of Object.entries(value)) {
|
|
52
|
+
if ((key === 'message_id' || key === 'handoff_id' || key === 'messageId' || key === 'handoffId')
|
|
53
|
+
&& hasReceiptValue(item))
|
|
54
|
+
return true;
|
|
55
|
+
if (inspect(item, seen))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
if (inspect(result, new Set()))
|
|
61
|
+
return true;
|
|
62
|
+
let text = '';
|
|
63
|
+
if (typeof result === 'string')
|
|
64
|
+
text = result;
|
|
65
|
+
else {
|
|
66
|
+
try {
|
|
67
|
+
text = JSON.stringify(result) ?? '';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (/(?:^|\n)\s*(?:script failed|❌)|returned no (?:message|group message)/i.test(text))
|
|
74
|
+
return false;
|
|
75
|
+
if (/(?:^|[\s,{])["']?ok["']?\s*[:=]\s*(?:false|0|no)\b/i.test(text))
|
|
76
|
+
return false;
|
|
77
|
+
if (/(?:^|[\s,{])["']?status["']?\s*[:=]\s*["']?(?:failed|error|rejected|denied)\b/i.test(text))
|
|
78
|
+
return false;
|
|
79
|
+
if (/\b(?:failed|failure|error|rejected|denied)\b/i.test(text))
|
|
80
|
+
return false;
|
|
81
|
+
SEND_RECEIPT_RE.lastIndex = 0;
|
|
82
|
+
for (const match of text.matchAll(SEND_RECEIPT_RE)) {
|
|
83
|
+
if (hasReceiptValue(match[1]))
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const handoffText = text.match(HANDOFF_TEXT_RECEIPT_RE)?.[1];
|
|
87
|
+
if (hasReceiptValue(handoffText))
|
|
88
|
+
return true;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
13
91
|
function isHumanFacingMessage(message) {
|
|
14
92
|
if (message.source === 'trigger')
|
|
15
93
|
return false;
|
|
@@ -106,15 +184,9 @@ export const proactiveFlow = {
|
|
|
106
184
|
const state = ctx.state.get(STATE_KEY);
|
|
107
185
|
if (!state)
|
|
108
186
|
return;
|
|
109
|
-
// PreToolUse
|
|
110
|
-
//
|
|
187
|
+
// PreToolUse 和 tool_use 都发生在命令执行前,不能推进发送门禁。
|
|
188
|
+
// 只有成功的 tool_result 携带明确回执时才提交状态。
|
|
111
189
|
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
190
|
if (!state.toolUseReminder)
|
|
119
191
|
return;
|
|
120
192
|
// 队列未读变化提醒
|
|
@@ -139,6 +211,18 @@ export const proactiveFlow = {
|
|
|
139
211
|
ctx.injectToModel(`⚠️ 工具调用已达到 ${state.toolCount} 次,请立即用 ${cmdHint} 向${target}汇报当前情况和下一步意图。`);
|
|
140
212
|
}
|
|
141
213
|
},
|
|
214
|
+
// ─── onToolResult:成功发送回执后解除门禁 ───
|
|
215
|
+
onToolResult(ctx) {
|
|
216
|
+
const state = ctx.state.get(STATE_KEY);
|
|
217
|
+
if (!state || !ctx.isSendCommand(ctx.toolName, ctx.toolInput))
|
|
218
|
+
return;
|
|
219
|
+
if (!hasSuccessfulSendReceipt(ctx.result, ctx.isError || !!ctx.error))
|
|
220
|
+
return;
|
|
221
|
+
if (state.firstSendRequired && !state.firstToolDone)
|
|
222
|
+
state.firstToolDone = true;
|
|
223
|
+
if (state.toolReportPending)
|
|
224
|
+
state.toolReportPending = false;
|
|
225
|
+
},
|
|
142
226
|
// ─── onComplete:标志位检查 ───
|
|
143
227
|
async onComplete(ctx) {
|
|
144
228
|
if (/\[PROACTIVE:REPLY_CONFIRMED_(SENT|NONE)\]/.test(ctx.lastReplyText)) {
|
package/dist/trigger/history.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { isManagedSessionRuntimeDir, SESSION_RUNTIME_DIR_ENV } from '../cli/task-context.js';
|
|
4
5
|
import { definitionRevision } from './validation.js';
|
|
5
6
|
export class TriggerHistoryStore {
|
|
6
7
|
rootDir;
|
|
7
8
|
agentAid;
|
|
8
9
|
file;
|
|
10
|
+
fallbackFile;
|
|
9
11
|
constructor(rootDir, agentAid) {
|
|
10
12
|
this.rootDir = rootDir;
|
|
11
13
|
this.agentAid = agentAid;
|
|
12
|
-
|
|
14
|
+
try {
|
|
15
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (!['EACCES', 'EPERM', 'EROFS'].includes(error?.code))
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
13
21
|
this.file = path.join(rootDir, 'history.jsonl');
|
|
22
|
+
const runtimeDir = process.env[SESSION_RUNTIME_DIR_ENV];
|
|
23
|
+
if (isManagedSessionRuntimeDir(runtimeDir)) {
|
|
24
|
+
const digest = crypto.createHash('sha256').update(this.agentAid).digest('hex').slice(0, 24);
|
|
25
|
+
this.fallbackFile = path.join(path.resolve(runtimeDir), 'trigger-history', digest, 'history.jsonl');
|
|
26
|
+
}
|
|
14
27
|
}
|
|
15
28
|
recordDefinition(type, definition, opts = {}) {
|
|
16
29
|
const event = {
|
|
@@ -191,15 +204,37 @@ export class TriggerHistoryStore {
|
|
|
191
204
|
return imported;
|
|
192
205
|
}
|
|
193
206
|
append(event) {
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
readLines() {
|
|
207
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
197
208
|
try {
|
|
198
|
-
|
|
209
|
+
fs.appendFileSync(this.file, line, { encoding: 'utf8', mode: 0o600 });
|
|
210
|
+
return;
|
|
199
211
|
}
|
|
200
|
-
catch {
|
|
201
|
-
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (!this.fallbackFile || !['EACCES', 'EPERM', 'EROFS'].includes(error?.code))
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
fs.mkdirSync(path.dirname(this.fallbackFile), { recursive: true, mode: 0o700 });
|
|
218
|
+
fs.appendFileSync(this.fallbackFile, line, { encoding: 'utf8', mode: 0o600 });
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
// A readonly task may legitimately have no writable runtime. Preserve
|
|
222
|
+
// the command result while making the loss visible to the daemon log.
|
|
223
|
+
if (!['EACCES', 'EPERM', 'EROFS'].includes(error?.code))
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
readLines() {
|
|
228
|
+
const lines = [];
|
|
229
|
+
for (const file of [this.file, this.fallbackFile].filter((value) => !!value)) {
|
|
230
|
+
try {
|
|
231
|
+
lines.push(...fs.readFileSync(file, 'utf8').split('\n').filter(Boolean));
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Missing or inaccessible history is treated as an empty source.
|
|
235
|
+
}
|
|
202
236
|
}
|
|
237
|
+
return lines;
|
|
203
238
|
}
|
|
204
239
|
mergeRunStats(legacy, current) {
|
|
205
240
|
if (!legacy)
|
|
@@ -358,6 +358,13 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
358
358
|
if (msg.includes('401') || msg.includes('authentication_error') || msg.toLowerCase().includes('unauthorized')) {
|
|
359
359
|
return `${errPrefix}API Key 无效,请检查密钥配置。使用 /status 查看当前配置`;
|
|
360
360
|
}
|
|
361
|
+
// Managed PreToolUse availability is a deterministic local configuration
|
|
362
|
+
// failure. Preserve the actionable setup guidance instead of telling the
|
|
363
|
+
// user to retry a condition that cannot change by itself.
|
|
364
|
+
if (msg.includes('Codex Windows proactive requires an installed and loaded EvolCore managed PreToolUse hook')
|
|
365
|
+
|| msg.includes('Codex proactive managed PreToolUse enforcement requires Linux or an installed Windows managed hook')) {
|
|
366
|
+
return `${errPrefix}${msg}`;
|
|
367
|
+
}
|
|
361
368
|
if (hasRetryableHttpStatus(msg)) {
|
|
362
369
|
return `${warnPrefix}API 服务暂时不可用,请稍后重试`;
|
|
363
370
|
}
|
package/dist/utils/logger.js
CHANGED
|
@@ -39,6 +39,39 @@ function getWriters() {
|
|
|
39
39
|
function shouldLog(level) {
|
|
40
40
|
return (LEVELS[level] ?? 1) >= (LEVELS[currentLevel] ?? 1);
|
|
41
41
|
}
|
|
42
|
+
/** Keep structured log streams joinable even when a producer only knows one
|
|
43
|
+
* of the lifecycle identifiers. Existing fields remain source-of-truth; this
|
|
44
|
+
* only supplies aliases used by daily interception analysis. */
|
|
45
|
+
export function normalizeStructuredLog(data) {
|
|
46
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
47
|
+
return data;
|
|
48
|
+
const nested = data.event && typeof data.event === 'object' ? data.event : undefined;
|
|
49
|
+
const correlationId = data.correlationId
|
|
50
|
+
?? data.callId
|
|
51
|
+
?? data.requestId
|
|
52
|
+
?? data.operationId
|
|
53
|
+
?? data.msgId
|
|
54
|
+
?? nested?.correlationId
|
|
55
|
+
?? nested?.callId;
|
|
56
|
+
const sessionId = data.sessionId ?? nested?.sessionId;
|
|
57
|
+
const agentAid = data.agentAid ?? data.selfAid ?? nested?.agentAid;
|
|
58
|
+
const toolName = data.toolName ?? data.tool ?? nested?.toolName ?? nested?.name;
|
|
59
|
+
const isToolResult = data.type === 'tool:result' || nested?.type === 'tool_result';
|
|
60
|
+
const decision = data.decision
|
|
61
|
+
?? data.status
|
|
62
|
+
?? (isToolResult && (data.isError === true || nested?.isError === true) ? 'error' : undefined)
|
|
63
|
+
?? (isToolResult && (data.isError === false || nested?.isError === false) ? 'allow' : undefined)
|
|
64
|
+
?? (nested?.type === 'tool_use' ? 'started' : undefined)
|
|
65
|
+
?? nested?.decision;
|
|
66
|
+
return {
|
|
67
|
+
...data,
|
|
68
|
+
...(correlationId ? { correlationId } : {}),
|
|
69
|
+
...(sessionId ? { sessionId } : {}),
|
|
70
|
+
...(agentAid ? { agentAid } : {}),
|
|
71
|
+
...(toolName ? { toolName } : {}),
|
|
72
|
+
...(decision ? { decision } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
42
75
|
export function localTimestamp() {
|
|
43
76
|
const d = new Date();
|
|
44
77
|
const pad = (n) => String(n).padStart(2, '0');
|
|
@@ -72,18 +105,18 @@ export const logger = {
|
|
|
72
105
|
const writer = getWriters().message;
|
|
73
106
|
if (!writer)
|
|
74
107
|
return;
|
|
75
|
-
writer.write(JSON.stringify({ ts: localTimestamp(), ...data }));
|
|
108
|
+
writer.write(JSON.stringify({ ts: localTimestamp(), ...normalizeStructuredLog(data) }));
|
|
76
109
|
},
|
|
77
110
|
event: (data) => {
|
|
78
111
|
const writer = getWriters().event;
|
|
79
112
|
if (!writer)
|
|
80
113
|
return;
|
|
81
|
-
writer.write(JSON.stringify({ ts: localTimestamp(), ...data }));
|
|
114
|
+
writer.write(JSON.stringify({ ts: localTimestamp(), ...normalizeStructuredLog(data) }));
|
|
82
115
|
},
|
|
83
116
|
channelIn: (data) => {
|
|
84
|
-
getWriters().channelIn.write(JSON.stringify({ ts: localTimestamp(), ...data }));
|
|
117
|
+
getWriters().channelIn.write(JSON.stringify({ ts: localTimestamp(), ...normalizeStructuredLog(data) }));
|
|
85
118
|
},
|
|
86
119
|
channelOut: (data) => {
|
|
87
|
-
getWriters().channelOut.write(JSON.stringify({ ts: localTimestamp(), ...data }));
|
|
120
|
+
getWriters().channelOut.write(JSON.stringify({ ts: localTimestamp(), ...normalizeStructuredLog(data) }));
|
|
88
121
|
}
|
|
89
122
|
};
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"ec.fs.copy": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
69
69
|
"ec.fs.move": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
70
70
|
"ec.fs.remove": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
71
|
+
"ec.fs.getfacl": { "allow": true, "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
72
|
+
"ec.fs.setfacl": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
71
73
|
"*": { "allow": true },
|
|
72
74
|
"agent.reload": {
|
|
73
75
|
"allow": true,
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"ec.fs.cat": { "allow": true, "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
67
67
|
"ec.fs.find": { "allow": true, "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
68
68
|
"ec.fs.df": { "allow": true, "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
69
|
+
"ec.fs.getfacl": { "allow": true, "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
69
70
|
"ec.fs.mkdir": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
70
71
|
"ec.fs.upload": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
71
72
|
"ec.fs.copy": { "allow": true, "permissionMode": "auto", "scopes": ["relation"], "constraints": { "groupOnly": true, "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolcore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "AI Agent gateway connecting Claude, Codex, Gemini, and the bundled ecagent runner to messaging channels with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|