evolcore 0.0.17 → 0.0.19
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 +51 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +113 -24
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +43 -24
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/p2p.js +20 -8
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +82 -14
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/data-migration.js +10 -4
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +66 -8
- package/dist/core/message/response-engine.js +147 -10
- package/dist/core/permission/ec-command-parser.js +148 -22
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +19 -7
- package/dist/index.js +357 -48
- package/dist/ipc.js +81 -5
- package/dist/paths.js +0 -3
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/docs/path-registry.md +1 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/02-navigation.md +2 -2
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
|
@@ -64,6 +64,23 @@ async function probeDaemon(socketPath, timeoutMs = 1000) {
|
|
|
64
64
|
return null;
|
|
65
65
|
return response;
|
|
66
66
|
}
|
|
67
|
+
/** Ask the daemon to run its own graceful shutdown lifecycle. */
|
|
68
|
+
export async function requestDaemonShutdown(socketPath, timeoutMs = 3_000, reason = 'cli', expectedPids) {
|
|
69
|
+
const daemon = await probeDaemon(socketPath, Math.min(timeoutMs, 1_000));
|
|
70
|
+
if (!daemon || (expectedPids && !expectedPids.has(daemon.pid)))
|
|
71
|
+
return null;
|
|
72
|
+
const response = await ipcQuery(socketPath, {
|
|
73
|
+
type: 'shutdown',
|
|
74
|
+
reason,
|
|
75
|
+
expectedPid: daemon.pid,
|
|
76
|
+
}, timeoutMs);
|
|
77
|
+
if (response?.ok !== true || response.accepted !== true
|
|
78
|
+
|| !Number.isInteger(response.pid) || response.pid <= 0
|
|
79
|
+
|| response.pid !== daemon.pid) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return response.pid;
|
|
83
|
+
}
|
|
67
84
|
function hasVisibleDaemonPid(status, ping) {
|
|
68
85
|
return status.mains.some(entry => entry.alive && entry.record.pid === ping.pid);
|
|
69
86
|
}
|
|
@@ -201,6 +218,33 @@ function formatLocalTime(ms) {
|
|
|
201
218
|
const d = new Date(ms);
|
|
202
219
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
|
203
220
|
}
|
|
221
|
+
function formatRunningDuration(ms) {
|
|
222
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
223
|
+
const seconds = totalSeconds % 60;
|
|
224
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
225
|
+
if (totalMinutes === 0)
|
|
226
|
+
return `${seconds}秒`;
|
|
227
|
+
const minutes = totalMinutes % 60;
|
|
228
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
229
|
+
if (totalHours === 0)
|
|
230
|
+
return `${minutes}分${seconds}秒`;
|
|
231
|
+
const hours = totalHours % 24;
|
|
232
|
+
const days = Math.floor(totalHours / 24);
|
|
233
|
+
if (days === 0)
|
|
234
|
+
return `${hours}小时${minutes}分${seconds}秒`;
|
|
235
|
+
return `${days}天${hours}小时${minutes}分${seconds}秒`;
|
|
236
|
+
}
|
|
237
|
+
function resolveRunningDuration(pid, uptime) {
|
|
238
|
+
if (typeof uptime === 'number' && Number.isFinite(uptime) && uptime >= 0) {
|
|
239
|
+
return formatRunningDuration(uptime);
|
|
240
|
+
}
|
|
241
|
+
const processStartedAt = getProcessStartTime(pid);
|
|
242
|
+
if (processStartedAt !== null) {
|
|
243
|
+
return formatRunningDuration(Date.now() - processStartedAt);
|
|
244
|
+
}
|
|
245
|
+
const instance = scanInstances().mains.find(entry => entry.alive && entry.record.pid === pid);
|
|
246
|
+
return instance ? formatRunningDuration(Date.now() - instance.record.startedAt) : '未知';
|
|
247
|
+
}
|
|
204
248
|
function printStartupInfo(opts = {}) {
|
|
205
249
|
const pkgRoot = getPackageRoot();
|
|
206
250
|
const isNpmInstall = pkgRoot.includes('node_modules');
|
|
@@ -256,7 +300,7 @@ export async function cmdStart(opts = {}) {
|
|
|
256
300
|
const existingDaemon = await probeDaemon(p.socket);
|
|
257
301
|
if (existingDaemon) {
|
|
258
302
|
console.log(` EvolCore is already running (PID: ${existingDaemon.pid}, IPC reachable)`);
|
|
259
|
-
console.log(
|
|
303
|
+
console.log(` 已在运行;已运行时长: ${resolveRunningDuration(existingDaemon.pid, existingDaemon.uptime)};部署/脚本可直接复用当前实例`);
|
|
260
304
|
return;
|
|
261
305
|
}
|
|
262
306
|
ensureDataDirs();
|
|
@@ -356,7 +400,7 @@ export async function cmdStart(opts = {}) {
|
|
|
356
400
|
console.log(` ${symbol} ${aid} — 最后活动 ${ago} (${info.event})`);
|
|
357
401
|
}
|
|
358
402
|
}
|
|
359
|
-
console.log(
|
|
403
|
+
console.log(` 已在运行;已运行时长: ${formatRunningDuration(Date.now() - first.record.startedAt)};部署/脚本可继续轮询 IPC ready,或直接复用当前实例`);
|
|
360
404
|
console.log(' 使用 ec restart 重启,或 ec stop 先停止');
|
|
361
405
|
return;
|
|
362
406
|
}
|
|
@@ -562,10 +606,11 @@ export async function cmdStart(opts = {}) {
|
|
|
562
606
|
};
|
|
563
607
|
setTimeout(checkReady, 1000);
|
|
564
608
|
}
|
|
565
|
-
async function stopPid(pid) {
|
|
609
|
+
async function stopPid(pid, gracefulRequested = false) {
|
|
566
610
|
console.log(`🛑 Stopping EvolCore (PID: ${pid})...`);
|
|
567
|
-
|
|
568
|
-
|
|
611
|
+
if (!gracefulRequested)
|
|
612
|
+
platform.killProcess(pid);
|
|
613
|
+
if (await platform.waitForProcessExit(pid, gracefulRequested ? 30_000 : 10_000)) {
|
|
569
614
|
console.log('✓ EvolCore stopped');
|
|
570
615
|
return true;
|
|
571
616
|
}
|
|
@@ -608,7 +653,10 @@ export async function cmdStop() {
|
|
|
608
653
|
if (runtimeOrphans.length > 0) {
|
|
609
654
|
console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(orphan => orphan.pid).join(', ')}`);
|
|
610
655
|
}
|
|
611
|
-
const
|
|
656
|
+
const gracefulPid = ping && pids.has(ping.pid)
|
|
657
|
+
? await requestDaemonShutdown(p.socket, 3_000, 'ec stop', pids)
|
|
658
|
+
: null;
|
|
659
|
+
const stopped = await Promise.all([...pids].map(pid => stopPid(pid, pid === gracefulPid)));
|
|
612
660
|
if (stopped.some(result => !result)) {
|
|
613
661
|
process.exitCode = 1;
|
|
614
662
|
return;
|
|
@@ -675,7 +723,12 @@ export async function cmdRestart(opts = {}) {
|
|
|
675
723
|
if (aliveMains.length > 1) {
|
|
676
724
|
console.log(`⚠ 检测到 ${aliveMains.length} 个 main 实例,将一并停止: ${aliveMains.map(m => m.record.pid).join(', ')}`);
|
|
677
725
|
}
|
|
678
|
-
const
|
|
726
|
+
const mainPids = new Set(aliveMains.map(entry => entry.record.pid));
|
|
727
|
+
const currentPing = await probeDaemon(socketPath);
|
|
728
|
+
const gracefulPid = currentPing && mainPids.has(currentPing.pid)
|
|
729
|
+
? await requestDaemonShutdown(socketPath, 3_000, 'ec restart', mainPids)
|
|
730
|
+
: null;
|
|
731
|
+
const stopped = await Promise.all(aliveMains.map(m => stopPid(m.record.pid, m.record.pid === gracefulPid)));
|
|
679
732
|
if (stopped.some(result => !result)) {
|
|
680
733
|
console.error('❌ Restart aborted because an existing EvolCore process could not be stopped');
|
|
681
734
|
process.exitCode = 1;
|
|
@@ -703,7 +756,12 @@ export async function cmdRestart(opts = {}) {
|
|
|
703
756
|
const runtimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
|
|
704
757
|
if (runtimeOrphans.length > 0) {
|
|
705
758
|
console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(o => o.pid).join(', ')}`);
|
|
706
|
-
const
|
|
759
|
+
const orphanPids = new Set(runtimeOrphans.map(orphan => orphan.pid));
|
|
760
|
+
const currentPing = await probeDaemon(socketPath);
|
|
761
|
+
const gracefulPid = currentPing && orphanPids.has(currentPing.pid)
|
|
762
|
+
? await requestDaemonShutdown(socketPath, 3_000, 'ec restart', orphanPids)
|
|
763
|
+
: null;
|
|
764
|
+
const stopped = await Promise.all(runtimeOrphans.map(o => stopPid(o.pid, o.pid === gracefulPid)));
|
|
707
765
|
if (stopped.some(result => !result)) {
|
|
708
766
|
console.error('❌ Restart aborted because an unregistered EvolCore process could not be stopped');
|
|
709
767
|
process.exitCode = 1;
|
|
@@ -1359,7 +1417,7 @@ export function cmdLogs(args) {
|
|
|
1359
1417
|
// ==================== Watch ====================
|
|
1360
1418
|
let watchUseColor = false;
|
|
1361
1419
|
const WATCH_BRACKET_TS_RE = /^\[(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)\]/;
|
|
1362
|
-
const WATCH_JSON_TS_RE = /"ts"\s*:\s*"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)"/;
|
|
1420
|
+
const WATCH_JSON_TS_RE = /"(?:ts|timestamp|startedAt|firedAt|finishedAt)"\s*:\s*(?:"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)"|(\d+(?:\.\d+)?))/;
|
|
1363
1421
|
function parseWatchTs(s) {
|
|
1364
1422
|
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z)?$/);
|
|
1365
1423
|
if (!m)
|
|
@@ -1371,10 +1429,21 @@ function parseWatchTs(s) {
|
|
|
1371
1429
|
return new Date(+y, +mo - 1, +d, +h, +mi, +se, msNum).getTime();
|
|
1372
1430
|
}
|
|
1373
1431
|
function extractWatchTs(line) {
|
|
1374
|
-
const
|
|
1375
|
-
if (
|
|
1432
|
+
const bracket = line.match(WATCH_BRACKET_TS_RE);
|
|
1433
|
+
if (bracket) {
|
|
1434
|
+
const t = parseWatchTs(bracket[1]);
|
|
1435
|
+
return isNaN(t) ? null : t;
|
|
1436
|
+
}
|
|
1437
|
+
const json = line.match(WATCH_JSON_TS_RE);
|
|
1438
|
+
if (!json)
|
|
1376
1439
|
return null;
|
|
1377
|
-
|
|
1440
|
+
if (json[2] !== undefined) {
|
|
1441
|
+
const numeric = Number(json[2]);
|
|
1442
|
+
if (!Number.isFinite(numeric) || numeric <= 0)
|
|
1443
|
+
return null;
|
|
1444
|
+
return numeric < 100_000_000_000 ? numeric * 1000 : numeric;
|
|
1445
|
+
}
|
|
1446
|
+
const t = parseWatchTs(json[1]);
|
|
1378
1447
|
return isNaN(t) ? null : t;
|
|
1379
1448
|
}
|
|
1380
1449
|
function toLocalTimeStr(epoch) {
|
|
@@ -1791,8 +1860,7 @@ function cmdWatch(filterTypes) {
|
|
|
1791
1860
|
const all = fs.readdirSync(p.logs).filter(f => f.endsWith('.log')).map(f => path.join(p.logs, f));
|
|
1792
1861
|
return filterLogFiles(all, filterTypes);
|
|
1793
1862
|
};
|
|
1794
|
-
|
|
1795
|
-
const shortName = (f) => path.basename(f, '.log').replace(/-\d{8}-\d{2}$/, '');
|
|
1863
|
+
const shortName = shortLogNameLocal;
|
|
1796
1864
|
// 计算最长文件名用于对齐
|
|
1797
1865
|
let maxNameLen = 0;
|
|
1798
1866
|
const updateMaxName = () => {
|
package/dist/cli/init.js
CHANGED
|
@@ -82,11 +82,11 @@ function ecagentUnavailableReason() {
|
|
|
82
82
|
function buildDefaults(chosen, available, projectsDefaultPath) {
|
|
83
83
|
const baseagents = {};
|
|
84
84
|
for (const b of available) {
|
|
85
|
-
// ecagent is bundled but opt-in; do not declare it merely because it is available.
|
|
86
|
-
if (b === 'ecagent' && chosen !== 'ecagent')
|
|
87
|
-
continue;
|
|
88
85
|
baseagents[b] = {};
|
|
89
86
|
}
|
|
87
|
+
// ecagent is bundled with EvolCore, so declare it even when the caller
|
|
88
|
+
// supplies a reduced availability list (for example, a test probe).
|
|
89
|
+
baseagents.ecagent ??= {};
|
|
90
90
|
return {
|
|
91
91
|
$schema_version: 1,
|
|
92
92
|
active_baseagent: chosen,
|
|
@@ -125,9 +125,12 @@ function applyExplicitAutostart(enabled, runtimeRoot) {
|
|
|
125
125
|
console.log(`❌ ${autostartPlatformLabel() || '登录自启'}配置失败: ${result.error || '未知错误'}`);
|
|
126
126
|
}
|
|
127
127
|
else {
|
|
128
|
+
const backend = enabled && result.backend ? `(方式:${result.backend})` : '';
|
|
128
129
|
console.log(enabled
|
|
129
|
-
? `✓ 已启用 ${autostartPlatformLabel() || '登录'}
|
|
130
|
+
? `✓ 已启用 ${autostartPlatformLabel() || '登录'}自启${backend}`
|
|
130
131
|
: `✓ 已禁用 ${autostartPlatformLabel() || '登录'}自启`);
|
|
132
|
+
if (result.warning)
|
|
133
|
+
console.log(`⚠ ${result.warning}`);
|
|
131
134
|
}
|
|
132
135
|
return result.ok;
|
|
133
136
|
}
|
|
@@ -508,9 +511,12 @@ export async function initTail(options = {}) {
|
|
|
508
511
|
console.log(` ❌ ${autostartPlatformLabel() || '登录自启'}配置失败: ${result.error || '未知错误'}`);
|
|
509
512
|
return false;
|
|
510
513
|
}
|
|
514
|
+
const backend = enabled && result.backend ? `(方式:${result.backend})` : '';
|
|
511
515
|
console.log(enabled
|
|
512
|
-
? ` ✓ 已启用 ${autostartPlatformLabel() || '登录'}
|
|
516
|
+
? ` ✓ 已启用 ${autostartPlatformLabel() || '登录'}自启${backend}`
|
|
513
517
|
: ` ✓ 已禁用 ${autostartPlatformLabel() || '登录'}自启`);
|
|
518
|
+
if (result.warning)
|
|
519
|
+
console.log(` ⚠ ${result.warning}`);
|
|
514
520
|
return true;
|
|
515
521
|
}
|
|
516
522
|
async function handleOwnersPrompt(rl) {
|
|
@@ -827,11 +833,19 @@ export async function cmdInitNonInteractive(opts, dependencies = {}) {
|
|
|
827
833
|
catch (e) {
|
|
828
834
|
fail('IO_ERROR', `failed to write daemon.json: ${e?.message || e}`, EXIT_RUNTIME, format);
|
|
829
835
|
}
|
|
836
|
+
let autostartBackend;
|
|
837
|
+
let autostartWarning;
|
|
830
838
|
if (opts.autoStart !== undefined) {
|
|
831
839
|
const autostart = configureAutostart(opts.autoStart, p.root);
|
|
832
840
|
if (!autostart.ok) {
|
|
833
841
|
fail('AUTOSTART_CONFIG_FAILED', autostart.error || 'failed to configure login autostart', EXIT_RUNTIME, format);
|
|
834
842
|
}
|
|
843
|
+
if (opts.autoStart === true && autostart.backend) {
|
|
844
|
+
// Keep the selected Windows backend visible to automation callers as
|
|
845
|
+
// well as in data/autostart-state.json.
|
|
846
|
+
autostartBackend = autostart.backend;
|
|
847
|
+
}
|
|
848
|
+
autostartWarning = autostart.warning;
|
|
835
849
|
}
|
|
836
850
|
// ── 10. 输出 init.result ──
|
|
837
851
|
const result = {
|
|
@@ -845,6 +859,8 @@ export async function cmdInitNonInteractive(opts, dependencies = {}) {
|
|
|
845
859
|
projectsDefaultPath: projectsDefaultPath ?? null,
|
|
846
860
|
defaultsPath: p.defaultsConfig,
|
|
847
861
|
daemonConfigPath: p.daemonConfig,
|
|
862
|
+
...(autostartBackend ? { autoStartBackend: autostartBackend } : {}),
|
|
863
|
+
...(autostartWarning ? { autoStartWarning: autostartWarning } : {}),
|
|
848
864
|
...(forced ? { forced: true, previousOwners } : {}),
|
|
849
865
|
};
|
|
850
866
|
emitResult(result, format);
|
|
@@ -14,6 +14,7 @@ import { WEB_PACKAGE_NAME } from '../product.js';
|
|
|
14
14
|
import { shouldSuppressRealRestart } from '../utils/restart-safety.js';
|
|
15
15
|
import { rotateStdoutLog } from '../utils/log-writer.js';
|
|
16
16
|
import { inspectDataMigrationRequirement } from '../core/data-migration.js';
|
|
17
|
+
import { requestDaemonShutdown } from './daemon-commands.js';
|
|
17
18
|
const execFileAsync = promisify(execFile);
|
|
18
19
|
// 清理 Claude Code 环境变量,防止 SDK 认为是嵌套会话
|
|
19
20
|
function cleanEnv() {
|
|
@@ -105,11 +106,14 @@ export async function cmdRestartMonitor() {
|
|
|
105
106
|
if (runtimeOrphans.length > 0) {
|
|
106
107
|
const pids = runtimeOrphans.map(o => o.pid).join(', ');
|
|
107
108
|
log(`Stopping unregistered EvolCore process(es): ${pids}`);
|
|
109
|
+
const orphanPids = new Set(runtimeOrphans.map(orphan => orphan.pid));
|
|
110
|
+
const ping = await requestDaemonShutdown(p.socket, 3_000, 'restart-monitor orphan cleanup', orphanPids);
|
|
108
111
|
for (const orphan of runtimeOrphans) {
|
|
109
|
-
|
|
112
|
+
if (orphan.pid !== ping)
|
|
113
|
+
platform.killProcess(orphan.pid, false);
|
|
110
114
|
}
|
|
111
115
|
const exited = await Promise.all(runtimeOrphans.map(async (orphan) => {
|
|
112
|
-
if (await platform.waitForProcessExit(orphan.pid, 10_000))
|
|
116
|
+
if (await platform.waitForProcessExit(orphan.pid, orphan.pid === ping ? 30_000 : 10_000))
|
|
113
117
|
return true;
|
|
114
118
|
platform.killProcess(orphan.pid, true);
|
|
115
119
|
return platform.waitForProcessExit(orphan.pid, 5_000);
|
|
@@ -130,12 +134,15 @@ export async function cmdRestartMonitor() {
|
|
|
130
134
|
if (aliveMains.length > 0) {
|
|
131
135
|
const oldPids = aliveMains.map(m => m.record.pid);
|
|
132
136
|
log(`Monitoring ${oldPids.length} main process(es): ${oldPids.join(', ')}`);
|
|
133
|
-
|
|
137
|
+
const gracefulPid = await requestDaemonShutdown(p.socket, 3_000, 'restart-monitor', new Set(oldPids));
|
|
138
|
+
// 没有收到 IPC shutdown 确认的进程才走信号兜底
|
|
134
139
|
for (const pid of oldPids) {
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
if (pid !== gracefulPid) {
|
|
141
|
+
try {
|
|
142
|
+
platform.killProcess(pid, false);
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
137
145
|
}
|
|
138
|
-
catch { }
|
|
139
146
|
}
|
|
140
147
|
const exited = await Promise.all(oldPids.map(async (oldPid) => {
|
|
141
148
|
if (await platform.waitForProcessExit(oldPid, 30_000)) {
|
package/dist/cli/watch-logs.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
/**
|
|
2
|
+
/** 去掉轮转后缀(按小时、按日及旧版带连字符日期)。入参可为文件名或绝对路径。 */
|
|
3
3
|
export function shortLogName(file) {
|
|
4
4
|
return path.basename(file, '.log')
|
|
5
|
-
.replace(/-\d{8}
|
|
5
|
+
.replace(/-\d{8}(?:-\d{2})?$/, '') // -YYYYMMDD[-HH](按日/小时轮转)
|
|
6
6
|
.replace(/-\d{4}-\d{2}-\d{2}$/, ''); // -YYYY-MM-DD(按日轮转,如 ts-sdk)
|
|
7
7
|
}
|
|
8
8
|
/** 从 .log 文件名列表推导去重、字母序的类型列表。 */
|
|
@@ -3,11 +3,15 @@ import { expectedRoleRank } from './role-ranks.js';
|
|
|
3
3
|
export { expectedRoleRank, ROLE_RANKS } from './role-ranks.js';
|
|
4
4
|
export const BUILTIN_USER_ROLES = ['member', 'visitor'];
|
|
5
5
|
export const MANAGEMENT_ROLES = ['owner', 'admin'];
|
|
6
|
+
export const DAEMON_OWNER_ROLE = 'daemon-owner';
|
|
6
7
|
export function isManagementRole(role) {
|
|
7
8
|
return role === 'owner' || role === 'admin';
|
|
8
9
|
}
|
|
10
|
+
export function isDaemonOwnerRole(role) {
|
|
11
|
+
return role === DAEMON_OWNER_ROLE;
|
|
12
|
+
}
|
|
9
13
|
export function isReservedRoleName(role) {
|
|
10
|
-
return isManagementRole(role);
|
|
14
|
+
return isManagementRole(role) || isDaemonOwnerRole(role);
|
|
11
15
|
}
|
|
12
16
|
/** Built-in user policy data comes exclusively from kits/templates/roles. */
|
|
13
17
|
export function getBuiltinRolesConfig() {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const ROLE_RANKS = Object.freeze({
|
|
2
|
+
/** Process-plane rank for display/audit only; it is never relation-assignable. */
|
|
3
|
+
'daemon-owner': 1000,
|
|
2
4
|
owner: 900,
|
|
3
5
|
admin: 700,
|
|
4
6
|
custom: 500,
|
|
@@ -6,6 +8,8 @@ export const ROLE_RANKS = Object.freeze({
|
|
|
6
8
|
visitor: 100,
|
|
7
9
|
});
|
|
8
10
|
export function expectedRoleRank(role) {
|
|
11
|
+
if (role === 'daemon-owner')
|
|
12
|
+
return ROLE_RANKS['daemon-owner'];
|
|
9
13
|
if (role === 'owner')
|
|
10
14
|
return ROLE_RANKS.owner;
|
|
11
15
|
if (role === 'admin')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
function compactPart(value) {
|
|
3
|
+
const text = String(value ?? '').trim();
|
|
4
|
+
return text || undefined;
|
|
5
|
+
}
|
|
6
|
+
function stableDigest(parts) {
|
|
7
|
+
const material = parts.map(part => compactPart(part) ?? '-').join('\u0000');
|
|
8
|
+
return crypto.createHash('sha256').update(material).digest('hex').slice(0, 20);
|
|
9
|
+
}
|
|
10
|
+
/** Stable across source/mirror records for one tool-call lifecycle. */
|
|
11
|
+
export function buildToolLifecycleEventKey(input) {
|
|
12
|
+
return `tool:${stableDigest([
|
|
13
|
+
input.sessionId ?? 'unknown',
|
|
14
|
+
input.callId ?? input.correlationId ?? input.requestId ?? 'unknown',
|
|
15
|
+
])}`;
|
|
16
|
+
}
|
|
17
|
+
/** Stable across repeated projections of one authorization decision. */
|
|
18
|
+
export function buildAuthorizationEventKey(input) {
|
|
19
|
+
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
20
|
+
if (lifecycleId) {
|
|
21
|
+
return `${buildToolLifecycleEventKey({
|
|
22
|
+
sessionId: input.sessionId,
|
|
23
|
+
callId: input.callId,
|
|
24
|
+
correlationId: input.correlationId,
|
|
25
|
+
requestId: input.requestId,
|
|
26
|
+
})}:authorization:${stableDigest([input.operation])}`;
|
|
27
|
+
}
|
|
28
|
+
return `authorization:${stableDigest([input.sessionId ?? 'unknown', input.operation])}`;
|
|
29
|
+
}
|
|
@@ -77,6 +77,10 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
77
77
|
}
|
|
78
78
|
function structuredRecordKey(value) {
|
|
79
79
|
const nested = nestedRecord(value);
|
|
80
|
+
const eventKey = firstDefined(value, nested, ['eventKey', 'event_key']);
|
|
81
|
+
const eventPhase = firstDefined(value, nested, ['eventPhase', 'event_phase']);
|
|
82
|
+
if (eventKey)
|
|
83
|
+
return `${String(eventKey)}${eventPhase ? `:${String(eventPhase)}` : ''}`;
|
|
80
84
|
const correlation = firstDefined(value, nested, [
|
|
81
85
|
'correlationId', 'correlation_id', 'callId', 'call_id', 'toolUseId', 'tool_use_id',
|
|
82
86
|
'requestId', 'request_id', 'msgId', 'operationId',
|
|
@@ -96,17 +100,23 @@ function isCorrelatableRecord(value) {
|
|
|
96
100
|
}
|
|
97
101
|
function stableHash(value) {
|
|
98
102
|
const nested = nestedRecord(value);
|
|
103
|
+
const eventKey = firstDefined(value, nested, ['eventKey', 'event_key']);
|
|
99
104
|
const canonical = {
|
|
105
|
+
eventKey,
|
|
106
|
+
eventPhase: firstDefined(value, nested, ['eventPhase', 'event_phase']),
|
|
100
107
|
type: canonicalEventType(value),
|
|
101
108
|
toolName: firstDefined(value, nested, ['toolName', 'tool', 'name']),
|
|
102
|
-
callId: firstDefined(value, nested, ['callId', 'call_id', 'toolUseId', 'tool_use_id']),
|
|
103
|
-
correlationId: firstDefined(value, nested, ['correlationId', 'correlation_id']),
|
|
104
|
-
requestId: firstDefined(value, nested, ['requestId', 'request_id']),
|
|
109
|
+
callId: eventKey ? undefined : firstDefined(value, nested, ['callId', 'call_id', 'toolUseId', 'tool_use_id']),
|
|
110
|
+
correlationId: eventKey ? undefined : firstDefined(value, nested, ['correlationId', 'correlation_id']),
|
|
111
|
+
requestId: eventKey ? undefined : firstDefined(value, nested, ['requestId', 'request_id']),
|
|
105
112
|
sessionId: firstDefined(value, nested, ['sessionId', 'session_id']),
|
|
106
113
|
agentAid: firstDefined(value, nested, ['agentAid', 'agent_aid', 'selfAid']),
|
|
107
114
|
permissionMode: firstDefined(value, nested, ['permissionMode', 'permission_mode']),
|
|
108
115
|
isError: firstDefined(value, nested, ['isError', 'is_error']) ?? (value.ok === false || nested?.ok === false ? true : undefined),
|
|
109
116
|
errorCode: firstDefined(value, nested, ['errorCode', 'error_code']),
|
|
117
|
+
decision: firstDefined(value, nested, ['decision']),
|
|
118
|
+
executed: firstDefined(value, nested, ['executed']),
|
|
119
|
+
executionState: firstDefined(value, nested, ['executionState', 'execution_state']),
|
|
110
120
|
input: firstDefined(value, nested, ['input']),
|
|
111
121
|
result: firstDefined(value, nested, ['result', 'content']),
|
|
112
122
|
error: firstDefined(value, nested, ['error', 'errorMessage']),
|
|
@@ -5,20 +5,8 @@ import { authorizeCommand, authorizeResolvedConfigCommand } from './operation-au
|
|
|
5
5
|
import { auditCommandAuthorization } from './authorization-audit.js';
|
|
6
6
|
import { hasTrustedPrincipal } from './authenticated-actor.js';
|
|
7
7
|
import { resolveRuntimePermissionMode } from '../role/runtime-policy.js';
|
|
8
|
-
const CROSS_AGENT_READ_OPERATIONS = new Set([
|
|
9
|
-
'trigger.list',
|
|
10
|
-
'trigger.show',
|
|
11
|
-
'trigger.history',
|
|
12
|
-
]);
|
|
13
8
|
export function isCrossAgentTriggerOperationAllowed(input) {
|
|
14
|
-
|
|
15
|
-
return true;
|
|
16
|
-
if (!input.taskAgentAid || !input.targetManagement)
|
|
17
|
-
return false;
|
|
18
|
-
return CROSS_AGENT_READ_OPERATIONS.has(input.operation);
|
|
19
|
-
}
|
|
20
|
-
export function isCrossAgentTriggerReadOperation(operation) {
|
|
21
|
-
return CROSS_AGENT_READ_OPERATIONS.has(operation);
|
|
9
|
+
return input.control || input.daemonOwner === true || input.taskAgentAid === input.targetAgentAid;
|
|
22
10
|
}
|
|
23
11
|
export function buildAuthSubject(input) {
|
|
24
12
|
const chatType = input.chatType === 'group' ? 'group' : 'private';
|
|
@@ -34,12 +22,14 @@ export function buildAuthSubject(input) {
|
|
|
34
22
|
peerType: input.peerType,
|
|
35
23
|
});
|
|
36
24
|
const processOwners = input.processOwners ?? [];
|
|
37
|
-
const
|
|
25
|
+
const isDaemonService = input.trustedProcessRole === 'daemon-service';
|
|
26
|
+
const isDaemonOwner = !isDaemonService && isProcessOwner({
|
|
38
27
|
actor: roleDetail.actor,
|
|
39
28
|
processOwners,
|
|
40
29
|
});
|
|
41
30
|
const suppliedRole = input.identity?.role && input.identity.role !== 'none' ? input.identity.role : undefined;
|
|
42
|
-
const role = suppliedRole ??
|
|
31
|
+
const role = suppliedRole ?? roleDetail.effectiveRole ?? 'none';
|
|
32
|
+
const processRole = isDaemonService ? 'daemon-service' : isDaemonOwner ? 'daemon-owner' : 'none';
|
|
43
33
|
return {
|
|
44
34
|
selfAid: input.selfAid,
|
|
45
35
|
actorId,
|
|
@@ -52,11 +42,13 @@ export function buildAuthSubject(input) {
|
|
|
52
42
|
conversationId,
|
|
53
43
|
peerKey: input.channelType && conversationId ? formatPeerKey(input.channelType, conversationId) : undefined,
|
|
54
44
|
role,
|
|
55
|
-
|
|
45
|
+
relationRole: role,
|
|
46
|
+
processRole,
|
|
47
|
+
roleSource: roleDetail.source,
|
|
56
48
|
identity: input.identity ?? roleToSessionIdentity(role === 'none' ? null : role),
|
|
57
49
|
isDaemonOwner,
|
|
58
50
|
fromControlChannel: !!input.fromControlChannel,
|
|
59
|
-
allowAccess:
|
|
51
|
+
allowAccess: processRole !== 'none' || (suppliedRole
|
|
60
52
|
? checkRoleAccess(suppliedRole, input.selfAid)
|
|
61
53
|
: roleDetail.allowAccess && checkRoleAccess(role, input.selfAid)),
|
|
62
54
|
permissionMode: input.permissionMode
|
|
@@ -104,6 +96,7 @@ export function authorizeOperation(params) {
|
|
|
104
96
|
selfAid: params.subject.selfAid,
|
|
105
97
|
peerKey: params.subject.peerKey,
|
|
106
98
|
role: params.subject.role,
|
|
99
|
+
processRole: params.subject.processRole,
|
|
107
100
|
isDaemonOwner: params.subject.isDaemonOwner,
|
|
108
101
|
fromControlChannel: params.subject.fromControlChannel,
|
|
109
102
|
allowExplicitRelationTarget: params.allowExplicitRelationTarget,
|
|
@@ -183,10 +176,13 @@ function auditDecision(params, decision) {
|
|
|
183
176
|
peerKey: params.subject.peerKey,
|
|
184
177
|
channel: params.subject.channel,
|
|
185
178
|
channelId: params.subject.channelId,
|
|
186
|
-
role: params.subject.role,
|
|
179
|
+
role: decision.allow ? decision.role : params.subject.role,
|
|
180
|
+
processRole: params.subject.processRole,
|
|
187
181
|
isDaemonOwner: params.subject.isDaemonOwner,
|
|
188
182
|
fromControlChannel: params.subject.fromControlChannel,
|
|
189
183
|
decision: decision.allow ? 'allow' : 'deny',
|
|
184
|
+
executed: false,
|
|
185
|
+
executionState: decision.allow ? 'authorized' : 'blocked',
|
|
190
186
|
code: decision.allow ? undefined : decision.code,
|
|
191
187
|
reason: decision.allow ? undefined : decision.reason,
|
|
192
188
|
matchedRule: decision.matchedRule,
|