evolcore 0.0.13 → 0.0.14

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/bin/codex-managed-hook.mjs +4 -1
  3. package/bin/install-codex-managed-hooks.mjs +201 -0
  4. package/dist/agents/claude-runner.js +53 -5
  5. package/dist/agents/codex-app-server-client.js +123 -2
  6. package/dist/agents/codex-runner.js +149 -30
  7. package/dist/agents/ecagent-runner.js +17 -1
  8. package/dist/agents/gemini-runner.js +9 -4
  9. package/dist/aun/msg/managed-operation.js +63 -3
  10. package/dist/channels/aun.js +144 -15
  11. package/dist/channels/daemon.js +2 -0
  12. package/dist/cli/aun-commands.js +1 -1
  13. package/dist/cli/fs-command.js +46 -9
  14. package/dist/cli/task-context.js +172 -0
  15. package/dist/config/builtin-roles.js +2 -0
  16. package/dist/config/config-manager.js +6 -2
  17. package/dist/config/contact-book-store.js +7 -2
  18. package/dist/core/auth/auth-gateway.js +1 -0
  19. package/dist/core/auth/authorization-audit.js +32 -0
  20. package/dist/core/auth/operation-catalog.js +3 -3
  21. package/dist/core/bootstrap-service.js +7 -1
  22. package/dist/core/command/command-handler.js +3 -0
  23. package/dist/core/event-catalog.js +2 -0
  24. package/dist/core/message/im-renderer.js +15 -1
  25. package/dist/core/message/message-bridge.js +5 -2
  26. package/dist/core/message/response-engine.js +138 -10
  27. package/dist/core/permission/ec-command-parser.js +556 -4
  28. package/dist/core/permission/tool-policy.js +17 -29
  29. package/dist/core/runtime-lock.js +101 -0
  30. package/dist/index.js +30 -3
  31. package/dist/response-system/engines/v1/proactive-flow.js +92 -8
  32. package/dist/response-system/modes/single-session/index.js +3 -0
  33. package/dist/trigger/history.js +42 -7
  34. package/dist/utils/error-utils.js +7 -0
  35. package/dist/utils/logger.js +37 -4
  36. package/kits/templates/roles/admin.json +2 -0
  37. package/kits/templates/roles/member.json +1 -0
  38. 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
- logger.channelOut({ channel: inst.adapter.channelName, channelId: envelope.channelId, taskId: envelope.taskId, payload: summarizeOutboundPayload(payload) });
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
- // 只有 runner 发布真实工具事件后,精确匹配的发送命令才提交状态。
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)) {
@@ -79,6 +79,9 @@ export class SingleSessionMode {
79
79
  onToolUse(ctx) {
80
80
  return this.flow().onToolUse?.(ctx);
81
81
  }
82
+ onToolResult(ctx) {
83
+ return this.flow().onToolResult?.(ctx);
84
+ }
82
85
  onComplete(ctx) {
83
86
  return this.flow().onComplete?.(ctx);
84
87
  }
@@ -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
- fs.mkdirSync(rootDir, { recursive: true });
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
- fs.appendFileSync(this.file, `${JSON.stringify(event)}\n`, { encoding: 'utf8', mode: 0o600 });
195
- }
196
- readLines() {
207
+ const line = `${JSON.stringify(event)}\n`;
197
208
  try {
198
- return fs.readFileSync(this.file, 'utf8').split('\n').filter(Boolean);
209
+ fs.appendFileSync(this.file, line, { encoding: 'utf8', mode: 0o600 });
210
+ return;
199
211
  }
200
- catch {
201
- return [];
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
  }
@@ -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.13",
3
+ "version": "0.0.14",
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",