evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +30 -6
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -75,6 +75,33 @@ export function atomicWrite(filePath, content) {
|
|
|
75
75
|
export function atomicWriteJson(filePath, value) {
|
|
76
76
|
atomicWrite(filePath, JSON.stringify(value, null, 2) + '\n');
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Update a small protected file without replacing its directory entry.
|
|
80
|
+
*
|
|
81
|
+
* H-class sandbox mounts bind individual files by pathname. Replacing such a
|
|
82
|
+
* file with rename(2) changes the inode while bubblewrap is constructing its
|
|
83
|
+
* mount table and can make the bind/remount operation fail. This helper keeps
|
|
84
|
+
* the inode stable; callers should use it only for best-effort, daemon-owned
|
|
85
|
+
* state where a crash during the write may leave incomplete content.
|
|
86
|
+
*/
|
|
87
|
+
export function stableWrite(filePath, content) {
|
|
88
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
89
|
+
const noFollow = fs.constants.O_NOFOLLOW ?? 0;
|
|
90
|
+
const fd = fs.openSync(filePath, fs.constants.O_RDWR | fs.constants.O_CREAT | noFollow, 0o600);
|
|
91
|
+
try {
|
|
92
|
+
const stat = fs.fstatSync(fd);
|
|
93
|
+
if (!stat.isFile())
|
|
94
|
+
throw new Error(`stable write target is not a regular file: ${filePath}`);
|
|
95
|
+
fs.ftruncateSync(fd, 0);
|
|
96
|
+
fs.writeFileSync(fd, content, 'utf8');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
fs.closeSync(fd);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function stableWriteJson(filePath, value) {
|
|
103
|
+
stableWrite(filePath, JSON.stringify(value, null, 2) + '\n');
|
|
104
|
+
}
|
|
78
105
|
/**
|
|
79
106
|
* 原子读取——自动按状态恢复。文件不存在时返回 null。
|
|
80
107
|
*
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { compareVersions, resolveGlobalPkg } from './npm-ops.js';
|
|
4
4
|
import { isWindows as platformIsWindows, resolveCommandPath } from './cross-platform.js';
|
|
5
5
|
import { MIN_ECWEB_VERSION, WEB_CLI_BIN, WEB_PACKAGE_NAME } from '../product.js';
|
|
6
|
+
import { resolvePaths } from '../paths.js';
|
|
6
7
|
/** EC Web owns the session-log reader, so older builds cannot safely serve a newer daemon. */
|
|
7
8
|
export function isEcwebVersionSupported(version) {
|
|
8
9
|
const normalized = version?.trim();
|
|
@@ -70,10 +71,23 @@ export function resolveEcwebLaunchCommand(ecwebArgs, opts = {}) {
|
|
|
70
71
|
* 配对码是 ecweb 进程自己生成并持有的内部状态。daemon/CLI 通过 ecweb 的
|
|
71
72
|
* localhost-only HTTP 接口 GET /api/pair-code 取当前码(远程访问被 ecweb 403 拒绝)。
|
|
72
73
|
*/
|
|
73
|
-
/** 经 localhost 拉取 ecweb 当前配对码(仅本机可取)。失败返回 null。
|
|
74
|
-
|
|
74
|
+
/** 经 localhost 拉取 ecweb 当前配对码(仅本机可取)。失败返回 null。
|
|
75
|
+
* authorizedBy 仅由 daemon 的 AUN 控制通道传入,用于把配对码换出的 token
|
|
76
|
+
* 绑定到实际批准的 daemon-owner;普通 CLI 取码不携带该字段。 */
|
|
77
|
+
export async function fetchEcwebPairCode(port, authorizedBy) {
|
|
75
78
|
try {
|
|
79
|
+
const headers = {};
|
|
80
|
+
const owner = authorizedBy?.trim();
|
|
81
|
+
if (owner)
|
|
82
|
+
headers['x-evolcore-pair-authorized-by'] = owner;
|
|
83
|
+
try {
|
|
84
|
+
const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
|
|
85
|
+
if (controlToken)
|
|
86
|
+
headers['x-evolcore-control-token'] = controlToken;
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
76
89
|
const resp = await fetch(`http://127.0.0.1:${port}/api/pair-code`, {
|
|
90
|
+
headers,
|
|
77
91
|
signal: AbortSignal.timeout(2000),
|
|
78
92
|
});
|
|
79
93
|
if (!resp.ok)
|
|
@@ -388,7 +388,10 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
388
388
|
const warnPrefix = includeEmoji ? '⚠️ ' : '';
|
|
389
389
|
const errPrefix = includeEmoji ? '❌ ' : '';
|
|
390
390
|
if (msg.includes('CONTEXT_COMPACT_FAILED') || isContextTooLongText(msg)) {
|
|
391
|
-
|
|
391
|
+
const detail = msg.match(/^CONTEXT_COMPACT_FAILED:\s*(.+)$/)?.[1]?.trim();
|
|
392
|
+
return detail
|
|
393
|
+
? `${warnPrefix}上下文过长,自动压缩失败:${detail}`
|
|
394
|
+
: `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
|
|
392
395
|
}
|
|
393
396
|
if (isSandboxInitializationFailure(error)) {
|
|
394
397
|
return `${warnPrefix}只读执行沙箱启动失败(sandbox_initialization_failed),当前任务未执行;请检查 user namespace/seccomp 配置后重试`;
|
package/dist/utils/logger.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { resolvePaths } from '../paths.js';
|
|
3
3
|
import { LogWriter } from './log-writer.js';
|
|
4
|
-
import { classifyToolErrorCode } from '../core/permission/tool-error-code.js';
|
|
4
|
+
import { classifyToolErrorCode, normalizeToolErrorCode } from '../core/permission/tool-error-code.js';
|
|
5
|
+
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
5
6
|
import { buildToolLifecycleEventKey } from '../core/audit/event-key.js';
|
|
6
7
|
let currentLevel = process.env.LOG_LEVEL || 'INFO';
|
|
7
8
|
const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
|
|
@@ -70,15 +71,32 @@ export function normalizeStructuredLog(data) {
|
|
|
70
71
|
?? nested?.toolUseId
|
|
71
72
|
?? nested?.tool_use_id;
|
|
72
73
|
const sessionId = data.sessionId ?? data.session_id ?? nested?.sessionId ?? nested?.session_id;
|
|
73
|
-
const agentAid = data.agentAid ?? data.agent_aid ?? data.selfAid ?? nested?.agentAid ?? nested?.agent_aid;
|
|
74
|
-
const
|
|
74
|
+
const agentAid = data.agentAid ?? data.agent_aid ?? data.selfAid ?? nested?.agentAid ?? nested?.agent_aid ?? nested?.selfAid;
|
|
75
|
+
const rawAgentName = data.agentName ?? data.agent_name ?? nested?.agentName ?? nested?.agent_name;
|
|
76
|
+
// Lifecycle streams should carry both the stable AID and a display label.
|
|
77
|
+
// When a producer only knows the AID, use that same value as the label so
|
|
78
|
+
// daily joins never invent a second identity for one session.
|
|
79
|
+
const normalizedAgentName = typeof rawAgentName === 'string' ? rawAgentName.trim() : '';
|
|
80
|
+
const agentName = normalizedAgentName
|
|
81
|
+
&& !['unknown', '<unknown>', '<none>', 'undefined', 'null'].includes(normalizedAgentName)
|
|
82
|
+
? normalizedAgentName
|
|
83
|
+
: typeof agentAid === 'string' && agentAid !== 'unknown'
|
|
84
|
+
? agentAid
|
|
85
|
+
: undefined;
|
|
86
|
+
const rawPermissionMode = data.permissionMode ?? data.permission_mode ?? nested?.permissionMode ?? nested?.permission_mode;
|
|
87
|
+
// Normalize legacy values at the log boundary. Preserve an explicit
|
|
88
|
+
// `unknown` marker for records that genuinely arrived without context.
|
|
89
|
+
const permissionMode = typeof rawPermissionMode === 'string'
|
|
90
|
+
&& rawPermissionMode !== 'unknown'
|
|
91
|
+
? normalizePermissionMode(rawPermissionMode).mode
|
|
92
|
+
: rawPermissionMode;
|
|
75
93
|
const toolName = data.toolName ?? data.tool ?? data.name ?? nested?.toolName ?? nested?.name;
|
|
76
94
|
const isToolResult = eventType === 'tool:result' || eventType === 'tool_result';
|
|
77
95
|
const isToolUse = eventType === 'tool:use' || eventType === 'tool_use';
|
|
78
96
|
const isError = data.isError ?? data.is_error ?? (data.ok === false ? true : undefined)
|
|
79
97
|
?? nested?.isError ?? nested?.is_error ?? (nested?.ok === false ? true : undefined);
|
|
80
|
-
const
|
|
81
|
-
|
|
98
|
+
const decisionSource = data.decisionSource ?? data.decision_source
|
|
99
|
+
?? nested?.decisionSource ?? nested?.decision_source;
|
|
82
100
|
const decision = data.decision
|
|
83
101
|
?? data.status
|
|
84
102
|
?? (isToolResult && isError === true ? 'error' : undefined)
|
|
@@ -95,8 +113,11 @@ export function normalizeStructuredLog(data) {
|
|
|
95
113
|
?? nested?.errorCode
|
|
96
114
|
?? nested?.error_code;
|
|
97
115
|
const errorCode = isToolResult && isError === true
|
|
98
|
-
? classifyToolErrorCode({ errorCode: explicitErrorCode,
|
|
116
|
+
? classifyToolErrorCode({ errorCode: explicitErrorCode, decisionSource })
|
|
99
117
|
: explicitErrorCode;
|
|
118
|
+
const classificationMissing = isToolResult && isError === true
|
|
119
|
+
&& !normalizeToolErrorCode(explicitErrorCode)
|
|
120
|
+
&& decisionSource !== 'policy' && decisionSource !== 'approval';
|
|
100
121
|
const lifecycleRecord = isToolUse || isToolResult;
|
|
101
122
|
const lifecycleEventKey = lifecycleRecord
|
|
102
123
|
? (eventKey ?? buildToolLifecycleEventKey({
|
|
@@ -116,12 +137,15 @@ export function normalizeStructuredLog(data) {
|
|
|
116
137
|
...(correlationId ? { correlationId } : {}),
|
|
117
138
|
...(sessionId ? { sessionId } : lifecycleRecord ? { sessionId: 'unknown' } : {}),
|
|
118
139
|
...(agentAid ? { agentAid } : lifecycleRecord ? { agentAid: 'unknown' } : {}),
|
|
140
|
+
...(agentName ? { agentName } : lifecycleRecord ? { agentName: 'unknown' } : {}),
|
|
119
141
|
...(permissionMode ? { permissionMode } : lifecycleRecord ? { permissionMode: 'unknown' } : {}),
|
|
120
142
|
...(toolName ? { toolName } : {}),
|
|
143
|
+
...(decisionSource ? { decisionSource } : {}),
|
|
121
144
|
...(decision ? { decision } : {}),
|
|
122
145
|
...(executed !== undefined ? { executed } : {}),
|
|
123
146
|
...(executionState ? { executionState } : {}),
|
|
124
147
|
...(errorCode ? { errorCode } : {}),
|
|
148
|
+
...(classificationMissing ? { classificationMissing: true } : {}),
|
|
125
149
|
...(missingContextFields.length > 0 ? { contextMissing: missingContextFields } : {}),
|
|
126
150
|
};
|
|
127
151
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { spawnSync } from 'child_process';
|
|
3
|
+
import { parseCimDate } from './process-introspect.js';
|
|
3
4
|
function runnerKind(name) {
|
|
4
5
|
const normalized = name.toLowerCase();
|
|
5
6
|
if (normalized.includes('codex'))
|
|
@@ -164,7 +165,7 @@ function readUnixPsMetrics() {
|
|
|
164
165
|
function readWindowsMetrics() {
|
|
165
166
|
const command = [
|
|
166
167
|
'Get-CimInstance Win32_Process',
|
|
167
|
-
|
|
168
|
+
"Select-Object ProcessId,ParentProcessId,Name,WorkingSetSize,KernelModeTime,UserModeTime,@{Name='CreationDate';Expression={if ($_.CreationDate) {$_.CreationDate.ToUniversalTime().ToString('o')}}}",
|
|
168
169
|
'ConvertTo-Json -Compress',
|
|
169
170
|
].join(' | ');
|
|
170
171
|
const result = spawnSync('powershell', ['-NoProfile', '-Command', command], {
|
|
@@ -184,13 +185,32 @@ function readWindowsMetrics() {
|
|
|
184
185
|
name: String(row.Name || 'unknown'),
|
|
185
186
|
rss: Number(row.WorkingSetSize) || 0,
|
|
186
187
|
cumulativeCpuMs: ((Number(row.KernelModeTime) || 0) + (Number(row.UserModeTime) || 0)) / 10_000,
|
|
187
|
-
|
|
188
|
+
// Keep a comparable numeric start time when PowerShell serializes CIM
|
|
189
|
+
// DateTime as either CIM text or an ISO date. The raw value remains as
|
|
190
|
+
// a fallback so PID reuse protection in ProcessTreeSampler still works
|
|
191
|
+
// when a platform returns an unfamiliar representation.
|
|
192
|
+
startId: parseWindowsCreationDate(row.CreationDate),
|
|
188
193
|
})).filter((row) => row.pid > 0);
|
|
189
194
|
}
|
|
190
195
|
catch {
|
|
191
196
|
return [];
|
|
192
197
|
}
|
|
193
198
|
}
|
|
199
|
+
export function parseWindowsCreationDate(value) {
|
|
200
|
+
if (value === undefined || value === null)
|
|
201
|
+
return undefined;
|
|
202
|
+
const raw = String(value).trim();
|
|
203
|
+
if (!raw)
|
|
204
|
+
return undefined;
|
|
205
|
+
const cim = parseCimDate(raw);
|
|
206
|
+
if (cim !== null)
|
|
207
|
+
return cim;
|
|
208
|
+
const serializedDate = raw.match(/^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/);
|
|
209
|
+
if (serializedDate)
|
|
210
|
+
return Number(serializedDate[1]);
|
|
211
|
+
const parsed = Date.parse(raw);
|
|
212
|
+
return Number.isFinite(parsed) ? parsed : raw;
|
|
213
|
+
}
|
|
194
214
|
export function readProcessMetrics() {
|
|
195
215
|
if (process.platform === 'linux')
|
|
196
216
|
return readLinuxMetrics();
|
|
@@ -209,9 +229,9 @@ export class ProcessTreeSampler {
|
|
|
209
229
|
this.readMetrics = readMetrics;
|
|
210
230
|
this.now = now;
|
|
211
231
|
}
|
|
212
|
-
sample(associatedRootPids = []) {
|
|
232
|
+
sample(associatedRootPids = [], metricsOverride) {
|
|
213
233
|
const timestamp = this.now();
|
|
214
|
-
const metrics = this.readMetrics();
|
|
234
|
+
const metrics = metricsOverride ?? this.readMetrics();
|
|
215
235
|
const byPid = new Map(metrics.map(metric => [metric.pid, metric]));
|
|
216
236
|
const children = new Map();
|
|
217
237
|
for (const metric of metrics) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { readProcessMetrics } from './process-tree-stats.js';
|
|
4
|
+
const port = parentPort;
|
|
5
|
+
if (!port)
|
|
6
|
+
throw new Error('process-tree worker requires a parent port');
|
|
7
|
+
port.on('message', (message) => {
|
|
8
|
+
if (message?.type !== 'sample')
|
|
9
|
+
return;
|
|
10
|
+
const startedAt = performance.now();
|
|
11
|
+
try {
|
|
12
|
+
const metrics = readProcessMetrics();
|
|
13
|
+
if (metrics.length === 0)
|
|
14
|
+
throw new Error('process enumeration returned no metrics');
|
|
15
|
+
const response = {
|
|
16
|
+
type: 'sample',
|
|
17
|
+
requestId: message.requestId,
|
|
18
|
+
metrics,
|
|
19
|
+
durationMs: Math.round((performance.now() - startedAt) * 10) / 10,
|
|
20
|
+
};
|
|
21
|
+
port.postMessage(response);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
port.postMessage({
|
|
25
|
+
type: 'sample-error',
|
|
26
|
+
requestId: message.requestId,
|
|
27
|
+
error: error instanceof Error ? error.message : String(error),
|
|
28
|
+
durationMs: Math.round((performance.now() - startedAt) * 10) / 10,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
@@ -53,8 +53,7 @@ export function defaultProjectsRoot(evolcoreRoot, options = {}) {
|
|
|
53
53
|
export function agentProjectRootFromDefaults(defaults, evolcoreRoot, options = {}) {
|
|
54
54
|
const platform = options.platform ?? process.platform;
|
|
55
55
|
const pathImpl = pathForPlatform(platform);
|
|
56
|
-
const configuredRoot = defaults?.projects?.
|
|
57
|
-
|| (defaults?.projects?.defaultPath && pathImpl.dirname(defaults.projects.defaultPath))
|
|
56
|
+
const configuredRoot = defaults?.projects?.defaultPath
|
|
58
57
|
|| defaultProjectsRoot(evolcoreRoot, options);
|
|
59
58
|
return sanitizeProjectRoot(configuredRoot, options);
|
|
60
59
|
}
|
|
@@ -5,6 +5,65 @@
|
|
|
5
5
|
* Edit 工具的摘要为 diff 风格预览(支持 old/new_string 与 unified diff 两种输入)。
|
|
6
6
|
*/
|
|
7
7
|
import fs from 'fs';
|
|
8
|
+
import { parseLiteralShellArgv, resolveCodexShellCarrierString, } from '../core/permission/ec-command-parser.js';
|
|
9
|
+
const DISPLAY_POSIX_SHELLS = new Set(['bash', 'sh']);
|
|
10
|
+
const DISPLAY_POWERSHELLS = new Set(['pwsh', 'pwsh.exe', 'powershell', 'powershell.exe']);
|
|
11
|
+
const DISPLAY_CMD_SHELLS = new Set(['cmd', 'cmd.exe']);
|
|
12
|
+
const DISPLAY_CMD_INERT_SWITCHES = new Set(['/d', '/s', '/q', '/a', '/u']);
|
|
13
|
+
function executableBasename(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return '';
|
|
16
|
+
return value.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase() ?? '';
|
|
17
|
+
}
|
|
18
|
+
/** Display-only fallback for valid Codex carriers installed outside standard paths. */
|
|
19
|
+
function summarizeDisplayCarrierArgv(argv) {
|
|
20
|
+
if (argv.length === 3) {
|
|
21
|
+
const executable = executableBasename(argv[0]);
|
|
22
|
+
const switchName = argv[1]?.toLowerCase();
|
|
23
|
+
if (DISPLAY_POSIX_SHELLS.has(executable) && switchName === '-lc')
|
|
24
|
+
return argv[2];
|
|
25
|
+
if (DISPLAY_POWERSHELLS.has(executable) && switchName === '-command')
|
|
26
|
+
return argv[2];
|
|
27
|
+
}
|
|
28
|
+
if (argv.length >= 3 && DISPLAY_CMD_SHELLS.has(executableBasename(argv[0]))) {
|
|
29
|
+
const executeIndex = argv.slice(1, -1).findIndex(value => value.toLowerCase() === '/c');
|
|
30
|
+
if (executeIndex >= 0 && executeIndex + 1 === argv.length - 2) {
|
|
31
|
+
const switches = argv.slice(1, executeIndex + 1);
|
|
32
|
+
if (switches.every(value => DISPLAY_CMD_INERT_SWITCHES.has(value.toLowerCase()))) {
|
|
33
|
+
return argv.at(-1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Remove Codex app-server's fixed shell carrier from display-only arguments. */
|
|
40
|
+
function displayShellCommand(command) {
|
|
41
|
+
if (typeof command !== 'string' || !command)
|
|
42
|
+
return undefined;
|
|
43
|
+
const parsedArgv = parseLiteralShellArgv(command);
|
|
44
|
+
const display = parsedArgv ? summarizeDisplayCarrierArgv(parsedArgv) : undefined;
|
|
45
|
+
if (display !== undefined)
|
|
46
|
+
return display;
|
|
47
|
+
// The nested PowerShell body is not POSIX shell syntax and can contain
|
|
48
|
+
// quotes, variables, redirects, and statement composition. Use the strict
|
|
49
|
+
// carrier parser only for PowerShell commands that the argv projection
|
|
50
|
+
// cannot parse; ordinary `bash -c` commands must remain unchanged.
|
|
51
|
+
const carrier = resolveCodexShellCarrierString(command);
|
|
52
|
+
return carrier?.dialect === 'powershell' ? carrier.command : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the tool arguments projected into activity/thought payloads.
|
|
56
|
+
* Execution, permission checks, and audit logging keep using the original
|
|
57
|
+
* input; only the human-facing Shell command is de-wrapped here.
|
|
58
|
+
*/
|
|
59
|
+
export function toolInputForDisplay(toolName, input) {
|
|
60
|
+
if (!input || toolName !== 'Shell')
|
|
61
|
+
return input;
|
|
62
|
+
const command = displayShellCommand(input.command);
|
|
63
|
+
if (!command || command === input.command)
|
|
64
|
+
return input;
|
|
65
|
+
return { ...input, command };
|
|
66
|
+
}
|
|
8
67
|
/**
|
|
9
68
|
* 工具输入摘要(提取工具调用的可读描述,供权限审批和消息展示使用)
|
|
10
69
|
*/
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
const MAX_CACHE_ENTRIES = 128;
|
|
5
|
+
const signatureCache = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* The signer name is checked only after Authenticode reports a trusted chain.
|
|
8
|
+
* Keep this list deliberately small: an unsigned or unknown Bash distribution
|
|
9
|
+
* must not enter the privileged EC command path.
|
|
10
|
+
*/
|
|
11
|
+
const MICROSOFT_PUBLISHER_RE = /(?:^|[,;]\s*)CN=Microsoft (?:Corporation|Windows(?: [^,;]+)?)(?:[,;]|$)/i;
|
|
12
|
+
const MICROSOFT_ORG_RE = /(?:^|[,;]\s*)O=Microsoft Corporation(?:[,;]|$)/i;
|
|
13
|
+
const BASH_PUBLISHER_RES = [
|
|
14
|
+
/(?:^|[,;]\s*)(?:CN|O)=Git for Windows(?:[,;]|$)/i,
|
|
15
|
+
/(?:^|[,;]\s*)(?:CN|O)=The Git Development Community(?:[,;]|$)/i,
|
|
16
|
+
// Git for Windows binaries are currently signed by project maintainer
|
|
17
|
+
// Johannes Schindelin rather than a certificate named after the project.
|
|
18
|
+
/(?:^|[,;]\s*)(?:CN|O)=Johannes Schindelin(?:[,;]|$)/i,
|
|
19
|
+
/(?:^|[,;]\s*)(?:CN|O)=Red Hat,? Inc\.(?:[,;]|$)/i,
|
|
20
|
+
/(?:^|[,;]\s*)(?:CN|O)=MSYS2(?:[,;]|$)/i,
|
|
21
|
+
];
|
|
22
|
+
function isValidStatus(status) {
|
|
23
|
+
return typeof status === 'string' && status.trim().toLowerCase() === 'valid';
|
|
24
|
+
}
|
|
25
|
+
/** Pure signature policy, separately testable without a Windows host. */
|
|
26
|
+
export function isTrustedShellSignature(kind, evidence) {
|
|
27
|
+
if (!isValidStatus(evidence.status) || typeof evidence.subject !== 'string')
|
|
28
|
+
return false;
|
|
29
|
+
const subject = evidence.subject.trim();
|
|
30
|
+
if (kind === 'cmd' || kind === 'powershell') {
|
|
31
|
+
return MICROSOFT_PUBLISHER_RE.test(subject) && MICROSOFT_ORG_RE.test(subject);
|
|
32
|
+
}
|
|
33
|
+
return (MICROSOFT_PUBLISHER_RE.test(subject) && MICROSOFT_ORG_RE.test(subject))
|
|
34
|
+
|| BASH_PUBLISHER_RES.some(pattern => pattern.test(subject));
|
|
35
|
+
}
|
|
36
|
+
function windowsSystemRoot() {
|
|
37
|
+
return process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows';
|
|
38
|
+
}
|
|
39
|
+
function systemPowerShellPath() {
|
|
40
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
41
|
+
}
|
|
42
|
+
function systemPowerShellModulePath() {
|
|
43
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'WindowsPowerShell', 'v1.0', 'Modules');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Windows PowerShell 5.1 must load its own Security module. A caller's
|
|
47
|
+
* PSModulePath can put PowerShell 7's module ahead of it; that module is not
|
|
48
|
+
* compatible with 5.1 and makes Get-AuthenticodeSignature fail to load.
|
|
49
|
+
*/
|
|
50
|
+
function systemPowerShellEnvironment() {
|
|
51
|
+
const env = { ...process.env };
|
|
52
|
+
for (const key of Object.keys(env)) {
|
|
53
|
+
if (key.toLowerCase() === 'psmodulepath')
|
|
54
|
+
delete env[key];
|
|
55
|
+
}
|
|
56
|
+
env.PSModulePath = systemPowerShellModulePath();
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
function systemWherePath() {
|
|
60
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'where.exe');
|
|
61
|
+
}
|
|
62
|
+
function isAbsoluteWindowsPath(value) {
|
|
63
|
+
return path.win32.isAbsolute(value) || /^\\\\[^\\/]+[\\/][^\\/]+/.test(value);
|
|
64
|
+
}
|
|
65
|
+
function resolveBareShellExecutable(executable, kind) {
|
|
66
|
+
const name = executable.toLowerCase().replace(/\.exe$/i, '');
|
|
67
|
+
if (kind === 'cmd' && name === 'cmd') {
|
|
68
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'cmd.exe');
|
|
69
|
+
}
|
|
70
|
+
if (kind === 'powershell' && name === 'powershell') {
|
|
71
|
+
return systemPowerShellPath();
|
|
72
|
+
}
|
|
73
|
+
const result = spawnSync(systemWherePath(), [`${name}.exe`], {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
timeout: 3000,
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
78
|
+
});
|
|
79
|
+
if (result.status !== 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
const first = String(result.stdout || '')
|
|
82
|
+
.split(/\r?\n/)
|
|
83
|
+
.map(line => line.trim())
|
|
84
|
+
.find(Boolean);
|
|
85
|
+
return first || undefined;
|
|
86
|
+
}
|
|
87
|
+
function resolveShellPath(executable, kind) {
|
|
88
|
+
if (!executable)
|
|
89
|
+
return undefined;
|
|
90
|
+
// Codex can keep its POSIX carrier spelling on Windows (for example
|
|
91
|
+
// `/bin/bash`). It is a logical shell name, not a Windows root-relative
|
|
92
|
+
// filesystem path, so resolve it through the trusted Windows PATH lookup.
|
|
93
|
+
if (executable.startsWith('/') && !executable.startsWith('//')) {
|
|
94
|
+
const basename = path.posix.basename(executable);
|
|
95
|
+
return resolveBareShellExecutable(basename, kind);
|
|
96
|
+
}
|
|
97
|
+
if (isAbsoluteWindowsPath(executable))
|
|
98
|
+
return executable;
|
|
99
|
+
return resolveBareShellExecutable(executable, kind);
|
|
100
|
+
}
|
|
101
|
+
function isAppExecutionAliasPath(value) {
|
|
102
|
+
return /[\\/]AppData[\\/]Local[\\/]Microsoft[\\/]WindowsApps[\\/]/i.test(value);
|
|
103
|
+
}
|
|
104
|
+
function shellQuote(value) {
|
|
105
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
106
|
+
}
|
|
107
|
+
function probeAuthenticode(filePath) {
|
|
108
|
+
const script = [
|
|
109
|
+
'$ErrorActionPreference = "Stop"',
|
|
110
|
+
`$p = ${shellQuote(filePath)}`,
|
|
111
|
+
'$item = Get-Item -LiteralPath $p -Force',
|
|
112
|
+
'if ($item.PSIsContainer) { exit 17 }',
|
|
113
|
+
'$sig = Get-AuthenticodeSignature -LiteralPath $item.FullName',
|
|
114
|
+
'[pscustomobject]@{',
|
|
115
|
+
' status = [string]$sig.Status',
|
|
116
|
+
' subject = if ($sig.SignerCertificate) { [string]$sig.SignerCertificate.Subject } else { "" }',
|
|
117
|
+
' issuer = if ($sig.SignerCertificate) { [string]$sig.SignerCertificate.Issuer } else { "" }',
|
|
118
|
+
'} | ConvertTo-Json -Compress',
|
|
119
|
+
].join('\n');
|
|
120
|
+
const result = spawnSync(systemPowerShellPath(), [
|
|
121
|
+
'-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script,
|
|
122
|
+
], {
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
env: systemPowerShellEnvironment(),
|
|
125
|
+
timeout: 5000,
|
|
126
|
+
windowsHide: true,
|
|
127
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
128
|
+
});
|
|
129
|
+
if (result.status !== 0 || result.error || !String(result.stdout || '').trim())
|
|
130
|
+
return undefined;
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(String(result.stdout));
|
|
133
|
+
return {
|
|
134
|
+
status: typeof parsed.status === 'string' ? parsed.status : undefined,
|
|
135
|
+
subject: typeof parsed.subject === 'string' ? parsed.subject : undefined,
|
|
136
|
+
issuer: typeof parsed.issuer === 'string' ? parsed.issuer : undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function cacheKey(filePath) {
|
|
144
|
+
try {
|
|
145
|
+
return fs.realpathSync.native(filePath).toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return filePath.replaceAll('\\', '/').toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Verify the concrete executable selected by Codex. Non-Windows callers keep
|
|
153
|
+
* the existing parser behavior; only Windows enters the Authenticode path.
|
|
154
|
+
*/
|
|
155
|
+
export function isTrustedWindowsShellExecutable(executable, kind) {
|
|
156
|
+
if (process.platform !== 'win32')
|
|
157
|
+
return true;
|
|
158
|
+
const resolved = resolveShellPath(executable, kind);
|
|
159
|
+
if (!resolved)
|
|
160
|
+
return false;
|
|
161
|
+
let finalPath = resolved;
|
|
162
|
+
try {
|
|
163
|
+
finalPath = fs.realpathSync.native(resolved);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Keep the original path for the signature probe; the stat/probe below
|
|
167
|
+
// will fail closed if the executable is not accessible.
|
|
168
|
+
}
|
|
169
|
+
// If realpath could not escape an App Execution Alias, do not authenticate
|
|
170
|
+
// the alias stub itself. A resolved WindowsApps package binary is allowed
|
|
171
|
+
// and is checked below like any other signed executable.
|
|
172
|
+
if (isAppExecutionAliasPath(finalPath))
|
|
173
|
+
return false;
|
|
174
|
+
let stat;
|
|
175
|
+
try {
|
|
176
|
+
stat = fs.statSync(finalPath);
|
|
177
|
+
if (!stat.isFile())
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const key = cacheKey(finalPath);
|
|
184
|
+
const previous = signatureCache.get(key);
|
|
185
|
+
if (previous && previous.size === stat.size && previous.mtimeMs === stat.mtimeMs) {
|
|
186
|
+
return previous.trusted;
|
|
187
|
+
}
|
|
188
|
+
const trusted = isTrustedShellSignature(kind, probeAuthenticode(finalPath) ?? {});
|
|
189
|
+
signatureCache.set(key, { size: stat.size, mtimeMs: stat.mtimeMs, trusted });
|
|
190
|
+
while (signatureCache.size > MAX_CACHE_ENTRIES) {
|
|
191
|
+
const oldest = signatureCache.keys().next().value;
|
|
192
|
+
if (!oldest)
|
|
193
|
+
break;
|
|
194
|
+
signatureCache.delete(oldest);
|
|
195
|
+
}
|
|
196
|
+
return trusted;
|
|
197
|
+
}
|
|
198
|
+
/** Test hook: clear cached signature decisions after a fixture is replaced. */
|
|
199
|
+
export function clearWindowsShellTrustCache() {
|
|
200
|
+
signatureCache.clear();
|
|
201
|
+
}
|
package/kits/docs/INDEX.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
| 文档 | 路径 | 说明 |
|
|
26
26
|
|------|------|------|
|
|
27
27
|
| 命令集目录 | `evolcore/INDEX.md` | msg/group/agent/aid/storage/ctl/rpc/bench 全集索引 |
|
|
28
|
-
| 联系人命令 | `evolcore/contact.md` | Contact Book
|
|
28
|
+
| 联系人命令 | `evolcore/contact.md` | Contact Book 添加申请、拉黑与托管会话权限边界 |
|
|
29
29
|
|
|
30
30
|
## 身份
|
|
31
31
|
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
| 命令集 | 用途 | 触发词 | 适用场景 | 文档 |
|
|
30
30
|
|--------|------|--------|----------|------|
|
|
31
31
|
| `ec msg` | 私聊收发消息 | 回复/发消息/拉取/撤回/查在线 | 有对端(peerId) | `msg.md` |
|
|
32
|
-
| `ec group` | 群聊收发与群管理 |
|
|
32
|
+
| `ec group` | 群聊收发与群管理 | 群发/建群/邀请/踢人/退群/群成员/角色/封禁/规则/协作 | 群聊(groupId) | `group.md`;完整协作流程见 `group-collaboration.md` |
|
|
33
33
|
| `ec group rules` | 群规则文件发布与上下文注入 | 群规则/工作流程/职责分工/rules.md/发布规则 | AUN 群聊(groupId) | `group-rules.md` |
|
|
34
34
|
| `ec aid` | AID 身份管理 | 身份/证书/名片/探测对端 | 任意有渠道场景 | `aid.md` |
|
|
35
35
|
| `ec fs` | AUN 文件系统统一入口 | 上传/下载/看文件/列目录/删文件/配额/群空间 | 任意有渠道场景 | `fs.md` |
|
|
36
36
|
| `ec storage` | 文件存储底层调试入口 | storage 调试/旧命令/底层上传下载 | 任意有渠道场景 | `storage.md` |
|
|
37
|
-
| `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md` |
|
|
38
|
-
| `ec contact` | Contact Book 与访问控制 |
|
|
37
|
+
| `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md`;完整创建与 Bootstrap 见 `agent-create.md` |
|
|
38
|
+
| `ec contact` | Contact Book 与访问控制 | 申请添加、拉黑/解除拉黑、查拉黑 | Agent 当前私聊可申请添加;管理写操作按角色限制 | `contact.md` |
|
|
39
39
|
| `ec rpc` | 底层 AUN RPC(逃生通道) | 直接调协议方法 | 高级/兜底 | `rpc.md` |
|
|
40
40
|
|
|
41
41
|
## 会话自管理类(不连 AUN,操作当前会话)
|