evolcore 0.0.25 → 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 +607 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +160 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +50 -12
- 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 +121 -38
- package/dist/agents/ecagent-runner.js +4 -9
- package/dist/agents/gemini-runner.js +2 -7
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +81 -8
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +1 -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/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +105 -84
- package/dist/cli/agent-create.js +112 -10
- package/dist/cli/agent.js +16 -5
- package/dist/cli/aun-commands.js +297 -296
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +47 -28
- package/dist/cli/contact.js +70 -13
- package/dist/cli/ctl-command.js +22 -22
- package/dist/cli/daemon-commands.js +105 -40
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +41 -9
- package/dist/cli/index.js +83 -77
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +46 -31
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +87 -40
- package/dist/cli/stats.js +51 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-operation-service.js +145 -0
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +27 -7
- 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 +25 -4
- package/dist/core/auth/operation-catalog.js +35 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/command-handler.js +83 -26
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +12 -3
- package/dist/core/command/menu-handler.js +47 -26
- package/dist/core/command/menu-protocol.js +5 -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 +86 -21
- 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 +47 -28
- 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 +2 -1
- package/dist/index.js +280 -163
- 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/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-dict.json +160 -160
- 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/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/welcome.js +22 -22
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +364 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -71
- package/kits/docs/evolcore/agent-create.md +170 -165
- package/kits/docs/evolcore/agent.md +66 -66
- package/kits/docs/evolcore/aid.md +60 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +202 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +42 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +166 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +37 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +190 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +96 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +85 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +31 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +100 -98
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import net from 'node:net';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
|
-
|
|
7
|
-
const startedAt = Date.now();
|
|
8
|
-
const runId = randomUUID();
|
|
9
|
-
let logPath;
|
|
10
|
-
let logFailed = false;
|
|
11
|
-
let context = {};
|
|
12
|
-
|
|
13
|
-
// This file is also copied into the Linux runtime; keep diagnostics self-contained.
|
|
14
|
-
function diagnostic(event, details = {}) {
|
|
15
|
-
if (process.env.EVOLCORE_CODEX_HOOK_LOG === '0' || logFailed) return;
|
|
16
|
-
try {
|
|
17
|
-
if (!logPath) {
|
|
18
|
-
logPath = path.resolve(process.env.EVOLCORE_CODEX_HOOK_LOG
|
|
19
|
-
|| path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'log', 'evolcore-hook.jsonl'));
|
|
20
|
-
fs.mkdirSync(path.dirname(logPath), { recursive: true, mode: 0o700 });
|
|
21
|
-
// Rotation is best-effort when concurrent tool calls start together.
|
|
22
|
-
try {
|
|
23
|
-
if (fs.statSync(logPath).size >= 2 * 1024 * 1024) fs.renameSync(logPath, `${logPath}.1`);
|
|
24
|
-
} catch {}
|
|
25
|
-
}
|
|
26
|
-
fs.appendFileSync(logPath, JSON.stringify({
|
|
27
|
-
time: new Date().toISOString(), runId, pid: process.pid,
|
|
28
|
-
elapsedMs: Date.now() - startedAt, event, ...context, ...details,
|
|
29
|
-
}) + '\n', { mode: 0o600 });
|
|
30
|
-
} catch (error) {
|
|
31
|
-
logFailed = true;
|
|
32
|
-
process.stderr.write(`[evolcore] Hook diagnostic log unavailable (${error?.code || 'unknown'}): ${logPath || '(unresolved)'}\n`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function errorDetails(error) {
|
|
37
|
-
// JSON parser messages and policy reasons can contain tool input. Omit them.
|
|
38
|
-
return {
|
|
39
|
-
errorName: error?.name,
|
|
40
|
-
errorCode: error?.code,
|
|
41
|
-
syscall: error?.syscall,
|
|
42
|
-
frames: typeof error?.stack === 'string'
|
|
43
|
-
? error.stack.split('\n').filter(line => /^\s+at /.test(line)).slice(0, 6) : undefined,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
process.on('uncaughtExceptionMonitor', error => diagnostic('uncaught_exception', errorDetails(error)));
|
|
48
|
-
process.on('exit', code => diagnostic('exit', { exitCode: code }));
|
|
49
|
-
diagnostic('start', {
|
|
50
|
-
nodeVersion: process.version, execPath: process.execPath, entrypoint: process.argv[1],
|
|
51
|
-
argumentCount: Math.max(0, process.argv.length - 2),
|
|
52
|
-
socketArgumentPresent: Boolean(process.argv[2]),
|
|
53
|
-
socketEnvironmentPresent: Boolean(process.env.EVOLCORE_INSTANCE_SOCKET),
|
|
54
|
-
socketArgumentUnexpanded: /^%EVOLCORE_INSTANCE_SOCKET%$/i.test(process.argv[2] || ''),
|
|
55
|
-
socketSource: process.argv[2] ? 'argument' : process.env.EVOLCORE_INSTANCE_SOCKET ? 'environment' : 'missing',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Only bound socket establishment. Once connected, the official Codex hook
|
|
59
|
-
// timeout owns the lifetime of this process; an application timeout here would
|
|
60
|
-
// turn /pause into a denial/allow race.
|
|
61
|
-
const connectTimeoutMs = 1500;
|
|
62
|
-
|
|
63
|
-
function deny(reason, category) {
|
|
64
|
-
diagnostic('decision', { decision: 'deny', category });
|
|
65
|
-
process.stdout.write(JSON.stringify({
|
|
66
|
-
hookSpecificOutput: {
|
|
67
|
-
hookEventName: 'PreToolUse',
|
|
68
|
-
permissionDecision: 'deny',
|
|
69
|
-
permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
|
|
70
|
-
},
|
|
71
|
-
}));
|
|
72
|
-
// Exit 2 is the documented blocking transport. Keep the structured JSON as
|
|
73
|
-
// well so Codex can surface the policy reason through the PreToolUse result.
|
|
74
|
-
process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
|
|
75
|
-
process.exitCode = 2;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function queryDaemon(socketPath, payload) {
|
|
79
|
-
diagnostic('connect_start', { timeoutMs: connectTimeoutMs });
|
|
80
|
-
return new Promise(resolve => {
|
|
81
|
-
const socket = net.createConnection(socketPath);
|
|
82
|
-
let buffer = '';
|
|
83
|
-
let settled = false;
|
|
84
|
-
let timer;
|
|
85
|
-
let connected = false;
|
|
86
|
-
const finish = value => {
|
|
87
|
-
if (settled) return;
|
|
88
|
-
settled = true;
|
|
89
|
-
clearTimeout(timer);
|
|
90
|
-
socket.destroy();
|
|
91
|
-
resolve(value);
|
|
92
|
-
};
|
|
93
|
-
timer = setTimeout(() => {
|
|
94
|
-
if (!connected) {
|
|
95
|
-
diagnostic('connect_timeout');
|
|
96
|
-
finish(null);
|
|
97
|
-
}
|
|
98
|
-
}, connectTimeoutMs);
|
|
99
|
-
timer.unref?.();
|
|
100
|
-
socket.once('error', error => {
|
|
101
|
-
diagnostic('socket_error', { connected, ...errorDetails(error) });
|
|
102
|
-
finish(null);
|
|
103
|
-
});
|
|
104
|
-
socket.once('close', () => {
|
|
105
|
-
if (!settled) diagnostic('socket_closed_without_response', { connected, responseBytes: Buffer.byteLength(buffer) });
|
|
106
|
-
});
|
|
107
|
-
socket.once('connect', () => {
|
|
108
|
-
connected = true;
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
diagnostic('connected');
|
|
111
|
-
socket.write(JSON.stringify(payload) + '\n');
|
|
112
|
-
});
|
|
113
|
-
socket.on('data', chunk => {
|
|
114
|
-
buffer += String(chunk);
|
|
115
|
-
const index = buffer.indexOf('\n');
|
|
116
|
-
if (index < 0) return;
|
|
117
|
-
try {
|
|
118
|
-
const result = JSON.parse(buffer.slice(0, index));
|
|
119
|
-
diagnostic('response', {
|
|
120
|
-
ok: result?.ok === true, applicable: result?.applicable === true,
|
|
121
|
-
decision: ['allow', 'deny'].includes(result?.decision) ? result.decision : 'other',
|
|
122
|
-
reasonPresent: typeof result?.reason === 'string',
|
|
123
|
-
});
|
|
124
|
-
finish(result);
|
|
125
|
-
} catch (error) {
|
|
126
|
-
diagnostic('invalid_response', { responseBytes: Buffer.byteLength(buffer), ...errorDetails(error) });
|
|
127
|
-
finish(null);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// The Windows managed requirements are machine-wide, so unmanaged Codex
|
|
134
|
-
// processes also reach this hook. EvolCore-owned threads carry a per-thread
|
|
135
|
-
// credential (EVOLCORE_CODEX_SESSION_TOKEN, injected through the thread's
|
|
136
|
-
// shell environment policy); unmanaged ones do not. Unmanaged calls are
|
|
137
|
-
// allowed outright and never contact the daemon — EvolCore being stopped must
|
|
138
|
-
// not break a standalone Codex. Credentialed calls always require an explicit
|
|
139
|
-
// daemon allow: a credential without its daemon is an orphaned managed thread
|
|
140
|
-
// and fails closed.
|
|
141
|
-
async function main() {
|
|
142
|
-
// Windows managed requirements use one machine-wide command. Resolve the
|
|
143
|
-
// per-daemon named pipe from the inherited environment so multiple EvolCore
|
|
144
|
-
// homes can share the installed hook without rewriting requirements.toml.
|
|
145
|
-
const socketPath = process.argv[2] || process.env.EVOLCORE_INSTANCE_SOCKET;
|
|
146
|
-
let input;
|
|
147
|
-
try {
|
|
148
|
-
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
149
|
-
} catch (error) {
|
|
150
|
-
diagnostic('invalid_input', errorDetails(error));
|
|
151
|
-
deny('Invalid Codex PreToolUse payload.', 'invalid_input');
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
const startedAt = Date.now();
|
|
8
|
+
const runId = randomUUID();
|
|
9
|
+
let logPath;
|
|
10
|
+
let logFailed = false;
|
|
11
|
+
let context = {};
|
|
12
|
+
|
|
13
|
+
// This file is also copied into the Linux runtime; keep diagnostics self-contained.
|
|
14
|
+
function diagnostic(event, details = {}) {
|
|
15
|
+
if (process.env.EVOLCORE_CODEX_HOOK_LOG === '0' || logFailed) return;
|
|
16
|
+
try {
|
|
17
|
+
if (!logPath) {
|
|
18
|
+
logPath = path.resolve(process.env.EVOLCORE_CODEX_HOOK_LOG
|
|
19
|
+
|| path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'log', 'evolcore-hook.jsonl'));
|
|
20
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true, mode: 0o700 });
|
|
21
|
+
// Rotation is best-effort when concurrent tool calls start together.
|
|
22
|
+
try {
|
|
23
|
+
if (fs.statSync(logPath).size >= 2 * 1024 * 1024) fs.renameSync(logPath, `${logPath}.1`);
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
fs.appendFileSync(logPath, JSON.stringify({
|
|
27
|
+
time: new Date().toISOString(), runId, pid: process.pid,
|
|
28
|
+
elapsedMs: Date.now() - startedAt, event, ...context, ...details,
|
|
29
|
+
}) + '\n', { mode: 0o600 });
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logFailed = true;
|
|
32
|
+
process.stderr.write(`[evolcore] Hook diagnostic log unavailable (${error?.code || 'unknown'}): ${logPath || '(unresolved)'}\n`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function errorDetails(error) {
|
|
37
|
+
// JSON parser messages and policy reasons can contain tool input. Omit them.
|
|
38
|
+
return {
|
|
39
|
+
errorName: error?.name,
|
|
40
|
+
errorCode: error?.code,
|
|
41
|
+
syscall: error?.syscall,
|
|
42
|
+
frames: typeof error?.stack === 'string'
|
|
43
|
+
? error.stack.split('\n').filter(line => /^\s+at /.test(line)).slice(0, 6) : undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.on('uncaughtExceptionMonitor', error => diagnostic('uncaught_exception', errorDetails(error)));
|
|
48
|
+
process.on('exit', code => diagnostic('exit', { exitCode: code }));
|
|
49
|
+
diagnostic('start', {
|
|
50
|
+
nodeVersion: process.version, execPath: process.execPath, entrypoint: process.argv[1],
|
|
51
|
+
argumentCount: Math.max(0, process.argv.length - 2),
|
|
52
|
+
socketArgumentPresent: Boolean(process.argv[2]),
|
|
53
|
+
socketEnvironmentPresent: Boolean(process.env.EVOLCORE_INSTANCE_SOCKET),
|
|
54
|
+
socketArgumentUnexpanded: /^%EVOLCORE_INSTANCE_SOCKET%$/i.test(process.argv[2] || ''),
|
|
55
|
+
socketSource: process.argv[2] ? 'argument' : process.env.EVOLCORE_INSTANCE_SOCKET ? 'environment' : 'missing',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Only bound socket establishment. Once connected, the official Codex hook
|
|
59
|
+
// timeout owns the lifetime of this process; an application timeout here would
|
|
60
|
+
// turn /pause into a denial/allow race.
|
|
61
|
+
const connectTimeoutMs = 1500;
|
|
62
|
+
|
|
63
|
+
function deny(reason, category) {
|
|
64
|
+
diagnostic('decision', { decision: 'deny', category });
|
|
65
|
+
process.stdout.write(JSON.stringify({
|
|
66
|
+
hookSpecificOutput: {
|
|
67
|
+
hookEventName: 'PreToolUse',
|
|
68
|
+
permissionDecision: 'deny',
|
|
69
|
+
permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
|
|
70
|
+
},
|
|
71
|
+
}));
|
|
72
|
+
// Exit 2 is the documented blocking transport. Keep the structured JSON as
|
|
73
|
+
// well so Codex can surface the policy reason through the PreToolUse result.
|
|
74
|
+
process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
|
|
75
|
+
process.exitCode = 2;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function queryDaemon(socketPath, payload) {
|
|
79
|
+
diagnostic('connect_start', { timeoutMs: connectTimeoutMs });
|
|
80
|
+
return new Promise(resolve => {
|
|
81
|
+
const socket = net.createConnection(socketPath);
|
|
82
|
+
let buffer = '';
|
|
83
|
+
let settled = false;
|
|
84
|
+
let timer;
|
|
85
|
+
let connected = false;
|
|
86
|
+
const finish = value => {
|
|
87
|
+
if (settled) return;
|
|
88
|
+
settled = true;
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
socket.destroy();
|
|
91
|
+
resolve(value);
|
|
92
|
+
};
|
|
93
|
+
timer = setTimeout(() => {
|
|
94
|
+
if (!connected) {
|
|
95
|
+
diagnostic('connect_timeout');
|
|
96
|
+
finish(null);
|
|
97
|
+
}
|
|
98
|
+
}, connectTimeoutMs);
|
|
99
|
+
timer.unref?.();
|
|
100
|
+
socket.once('error', error => {
|
|
101
|
+
diagnostic('socket_error', { connected, ...errorDetails(error) });
|
|
102
|
+
finish(null);
|
|
103
|
+
});
|
|
104
|
+
socket.once('close', () => {
|
|
105
|
+
if (!settled) diagnostic('socket_closed_without_response', { connected, responseBytes: Buffer.byteLength(buffer) });
|
|
106
|
+
});
|
|
107
|
+
socket.once('connect', () => {
|
|
108
|
+
connected = true;
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
diagnostic('connected');
|
|
111
|
+
socket.write(JSON.stringify(payload) + '\n');
|
|
112
|
+
});
|
|
113
|
+
socket.on('data', chunk => {
|
|
114
|
+
buffer += String(chunk);
|
|
115
|
+
const index = buffer.indexOf('\n');
|
|
116
|
+
if (index < 0) return;
|
|
117
|
+
try {
|
|
118
|
+
const result = JSON.parse(buffer.slice(0, index));
|
|
119
|
+
diagnostic('response', {
|
|
120
|
+
ok: result?.ok === true, applicable: result?.applicable === true,
|
|
121
|
+
decision: ['allow', 'deny'].includes(result?.decision) ? result.decision : 'other',
|
|
122
|
+
reasonPresent: typeof result?.reason === 'string',
|
|
123
|
+
});
|
|
124
|
+
finish(result);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
diagnostic('invalid_response', { responseBytes: Buffer.byteLength(buffer), ...errorDetails(error) });
|
|
127
|
+
finish(null);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The Windows managed requirements are machine-wide, so unmanaged Codex
|
|
134
|
+
// processes also reach this hook. EvolCore-owned threads carry a per-thread
|
|
135
|
+
// credential (EVOLCORE_CODEX_SESSION_TOKEN, injected through the thread's
|
|
136
|
+
// shell environment policy); unmanaged ones do not. Unmanaged calls are
|
|
137
|
+
// allowed outright and never contact the daemon — EvolCore being stopped must
|
|
138
|
+
// not break a standalone Codex. Credentialed calls always require an explicit
|
|
139
|
+
// daemon allow: a credential without its daemon is an orphaned managed thread
|
|
140
|
+
// and fails closed.
|
|
141
|
+
async function main() {
|
|
142
|
+
// Windows managed requirements use one machine-wide command. Resolve the
|
|
143
|
+
// per-daemon named pipe from the inherited environment so multiple EvolCore
|
|
144
|
+
// homes can share the installed hook without rewriting requirements.toml.
|
|
145
|
+
const socketPath = process.argv[2] || process.env.EVOLCORE_INSTANCE_SOCKET;
|
|
146
|
+
let input;
|
|
147
|
+
try {
|
|
148
|
+
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
diagnostic('invalid_input', errorDetails(error));
|
|
151
|
+
deny('Invalid Codex PreToolUse payload.', 'invalid_input');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
154
|
context = {
|
|
155
|
-
sessionId: typeof input?.session_id === 'string' ? input.session_id.slice(0, 128) : undefined,
|
|
156
|
-
toolName: typeof input?.tool_name === 'string' ? input.tool_name.slice(0, 128) : undefined,
|
|
157
|
-
callId: typeof (input?.tool_use_id ?? input?.toolUseId ?? input?.item_id ?? input?.itemId) === 'string'
|
|
158
|
-
? (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId).slice(0, 128) : undefined,
|
|
159
|
-
};
|
|
160
|
-
diagnostic('input', {
|
|
161
|
-
preToolUse: input?.hook_event_name === 'PreToolUse',
|
|
162
|
-
sessionIdPresent: typeof input?.session_id === 'string' && Boolean(input.session_id),
|
|
163
|
-
toolNamePresent: typeof input?.tool_name === 'string' && Boolean(input.tool_name),
|
|
164
|
-
toolInputPresent: input?.tool_input !== undefined,
|
|
165
|
-
});
|
|
155
|
+
sessionId: typeof input?.session_id === 'string' ? input.session_id.slice(0, 128) : undefined,
|
|
156
|
+
toolName: typeof input?.tool_name === 'string' ? input.tool_name.slice(0, 128) : undefined,
|
|
157
|
+
callId: typeof (input?.tool_use_id ?? input?.toolUseId ?? input?.item_id ?? input?.itemId) === 'string'
|
|
158
|
+
? (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId).slice(0, 128) : undefined,
|
|
159
|
+
};
|
|
160
|
+
diagnostic('input', {
|
|
161
|
+
preToolUse: input?.hook_event_name === 'PreToolUse',
|
|
162
|
+
sessionIdPresent: typeof input?.session_id === 'string' && Boolean(input.session_id),
|
|
163
|
+
toolNamePresent: typeof input?.tool_name === 'string' && Boolean(input.tool_name),
|
|
164
|
+
toolInputPresent: input?.tool_input !== undefined,
|
|
165
|
+
});
|
|
166
166
|
if (!socketPath
|
|
167
167
|
|| input?.hook_event_name !== 'PreToolUse'
|
|
168
168
|
|| typeof input?.session_id !== 'string'
|
|
169
|
-
|| !input.session_id
|
|
170
|
-
|| typeof input?.tool_name !== 'string'
|
|
171
|
-
|| !input.tool_name) {
|
|
172
|
-
deny('Incomplete Codex PreToolUse payload.', 'incomplete_input');
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const sessionToken = typeof process.env.EVOLCORE_CODEX_SESSION_TOKEN === 'string'
|
|
177
|
-
&& process.env.EVOLCORE_CODEX_SESSION_TOKEN
|
|
178
|
-
? process.env.EVOLCORE_CODEX_SESSION_TOKEN
|
|
179
|
-
: undefined;
|
|
180
|
-
|
|
181
|
-
if (!sessionToken) {
|
|
182
|
-
// Unmanaged process: nothing to enforce, and no reason to pay the daemon
|
|
183
|
-
// round-trip (or its outage) for a call we would allow anyway.
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (!socketPath) {
|
|
187
|
-
deny('EvolCore session credential is present but the policy service address is missing.');
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const result = await queryDaemon(socketPath, {
|
|
192
|
-
type: 'codex-pre-tool-use',
|
|
193
|
-
threadId: input.session_id,
|
|
194
|
-
toolName: input.tool_name,
|
|
195
|
-
toolInput: input.tool_input ?? null,
|
|
196
|
-
sessionToken,
|
|
197
|
-
...(typeof (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId) === 'string'
|
|
198
|
-
? { callId: input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId }
|
|
199
|
-
: {}),
|
|
200
|
-
});
|
|
201
|
-
if (!result) {
|
|
202
|
-
deny('EvolCore policy service is unavailable.', 'service_unavailable');
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (result.ok === true && result.applicable === true && result.decision === 'allow') {
|
|
206
|
-
diagnostic('decision', { decision: 'allow' });
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
deny(result.reason || 'EvolCore policy did not explicitly allow this tool call.', 'policy_denied');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
void main();
|
|
169
|
+
|| !input.session_id
|
|
170
|
+
|| typeof input?.tool_name !== 'string'
|
|
171
|
+
|| !input.tool_name) {
|
|
172
|
+
deny('Incomplete Codex PreToolUse payload.', 'incomplete_input');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const sessionToken = typeof process.env.EVOLCORE_CODEX_SESSION_TOKEN === 'string'
|
|
177
|
+
&& process.env.EVOLCORE_CODEX_SESSION_TOKEN
|
|
178
|
+
? process.env.EVOLCORE_CODEX_SESSION_TOKEN
|
|
179
|
+
: undefined;
|
|
180
|
+
|
|
181
|
+
if (!sessionToken) {
|
|
182
|
+
// Unmanaged process: nothing to enforce, and no reason to pay the daemon
|
|
183
|
+
// round-trip (or its outage) for a call we would allow anyway.
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!socketPath) {
|
|
187
|
+
deny('EvolCore session credential is present but the policy service address is missing.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await queryDaemon(socketPath, {
|
|
192
|
+
type: 'codex-pre-tool-use',
|
|
193
|
+
threadId: input.session_id,
|
|
194
|
+
toolName: input.tool_name,
|
|
195
|
+
toolInput: input.tool_input ?? null,
|
|
196
|
+
sessionToken,
|
|
197
|
+
...(typeof (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId) === 'string'
|
|
198
|
+
? { callId: input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId }
|
|
199
|
+
: {}),
|
|
200
|
+
});
|
|
201
|
+
if (!result) {
|
|
202
|
+
deny('EvolCore policy service is unavailable.', 'service_unavailable');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (result.ok === true && result.applicable === true && result.decision === 'allow') {
|
|
206
|
+
diagnostic('decision', { decision: 'allow' });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
deny(result.reason || 'EvolCore policy did not explicitly allow this tool call.', 'policy_denied');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
void main();
|