evolcore 0.0.11 → 0.0.12
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 +22 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/claude-runner.js +35 -2
- package/dist/agents/codex-app-server-client.js +120 -14
- package/dist/agents/codex-runner.js +273 -35
- package/dist/agents/ecagent-runner.js +36 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/channels/aun.js +288 -108
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +170 -6
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +31 -31
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +4 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +30 -7
- package/dist/core/auth/operation-catalog.js +187 -25
- package/dist/core/bootstrap-messages.js +18 -0
- package/dist/core/bootstrap-service.js +63 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +147 -35
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +41 -21
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +236 -78
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +1 -0
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/tool-policy.js +73 -30
- package/dist/core/protected-paths.js +7 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +384 -98
- package/dist/ipc.js +218 -9
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +22 -0
- package/kits/templates/roles/member.json +27 -0
- package/kits/templates/roles/visitor.json +33 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/core/command/evol-menu-version-gate.js +0 -39
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
process.env.AUN_LOG_INI_DISABLE = '1';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import { getPackageRoot } from '../paths.js';
|
|
6
|
+
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
7
|
+
import { ipcQuery } from '../ipc.js';
|
|
8
|
+
import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
|
|
7
9
|
import { cmdInit } from './init.js';
|
|
8
10
|
import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom, cmdInitAun } from './init-channel.js';
|
|
9
11
|
import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
|
|
@@ -16,6 +18,7 @@ import { cmdQueue } from './queue-command.js';
|
|
|
16
18
|
import { cmdStart, cmdStop, cmdRestart, cmdStatus, cmdLogs, cmdWatchCommand, cmdMv, cmdDiagnose } from './daemon-commands.js';
|
|
17
19
|
import { logCliCommand } from './command-log.js';
|
|
18
20
|
import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
|
|
21
|
+
import { assertManagedTopLevelCommandAllowed, ManagedCommandNotAllowedError } from './managed-command-guard.js';
|
|
19
22
|
const MAIN_HELP = `用法: ec <command> [选项]
|
|
20
23
|
|
|
21
24
|
服务:
|
|
@@ -87,6 +90,16 @@ process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
|
|
|
87
90
|
return; process.stderr.write((w.stack ?? String(w)) + '\n'); });
|
|
88
91
|
export async function main(args) {
|
|
89
92
|
const cmd = args[0] || 'start';
|
|
93
|
+
try {
|
|
94
|
+
assertManagedTopLevelCommandAllowed(cmd, args);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (!(error instanceof ManagedCommandNotAllowedError))
|
|
98
|
+
throw error;
|
|
99
|
+
console.error(`❌ [${error.code}] ${error.message}`);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
90
103
|
logCliCommand(cmd, args);
|
|
91
104
|
if (cmd === '--version' || cmd === '-v' || cmd === '-V') {
|
|
92
105
|
const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
|
|
@@ -229,7 +242,22 @@ export async function main(args) {
|
|
|
229
242
|
printLifecycleHelp('status');
|
|
230
243
|
break;
|
|
231
244
|
}
|
|
232
|
-
|
|
245
|
+
if (process.env.EVOLCORE_SESSION_ID) {
|
|
246
|
+
const sessionId = process.env.EVOLCORE_SESSION_ID;
|
|
247
|
+
const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
|
|
248
|
+
const result = await ipcQuery(resolvePaths().socket, {
|
|
249
|
+
type: 'status.auth', sessionId, delegationToken,
|
|
250
|
+
});
|
|
251
|
+
if (!result?.ok) {
|
|
252
|
+
console.error(`❌ ${result?.error || 'status is not allowed in this task'}`);
|
|
253
|
+
process.exitCode = 1;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
await cmdStatus();
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
await cmdStatus();
|
|
260
|
+
}
|
|
233
261
|
break;
|
|
234
262
|
case 'logs':
|
|
235
263
|
if (wantsHelp(args.slice(1))) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { wantsHelp } from './help.js';
|
|
2
|
+
const MANAGED_TASK_COMMANDS = new Set([
|
|
3
|
+
'aid',
|
|
4
|
+
'agent',
|
|
5
|
+
'config',
|
|
6
|
+
'contact',
|
|
7
|
+
'ctl',
|
|
8
|
+
'fs',
|
|
9
|
+
'group',
|
|
10
|
+
'handoff',
|
|
11
|
+
'model',
|
|
12
|
+
'msg',
|
|
13
|
+
'queue',
|
|
14
|
+
'response',
|
|
15
|
+
'status',
|
|
16
|
+
'storage',
|
|
17
|
+
'trigger',
|
|
18
|
+
'version',
|
|
19
|
+
'-v',
|
|
20
|
+
'-V',
|
|
21
|
+
'--version',
|
|
22
|
+
]);
|
|
23
|
+
export class ManagedCommandNotAllowedError extends Error {
|
|
24
|
+
command;
|
|
25
|
+
code = 'NOT_ALLOWED';
|
|
26
|
+
constructor(command) {
|
|
27
|
+
super(`Command ${command} is not available in a managed Agent task`);
|
|
28
|
+
this.command = command;
|
|
29
|
+
this.name = 'ManagedCommandNotAllowedError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Local maintenance commands do not have daemon authorization entrypoints.
|
|
34
|
+
* Keep them outside managed tasks so they cannot read or mutate host state.
|
|
35
|
+
*/
|
|
36
|
+
export function assertManagedTopLevelCommandAllowed(command, args, env = process.env) {
|
|
37
|
+
if (!env.EVOLCORE_SESSION_ID || wantsHelp(args.slice(1)) || command === '--help' || command === '-h')
|
|
38
|
+
return;
|
|
39
|
+
if (!MANAGED_TASK_COMMANDS.has(command))
|
|
40
|
+
throw new ManagedCommandNotAllowedError(command);
|
|
41
|
+
}
|
package/dist/cli/model.js
CHANGED
|
@@ -46,10 +46,8 @@ function fail(formatJson, code, message, data) {
|
|
|
46
46
|
process.exit(1);
|
|
47
47
|
}
|
|
48
48
|
function isManagedSessionEnv(args) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
&& !getArgValue(args, '--peer')
|
|
52
|
-
&& !getArgValue(args, '--role');
|
|
49
|
+
void args;
|
|
50
|
+
return !!process.env.EVOLCORE_SESSION_ID;
|
|
53
51
|
}
|
|
54
52
|
function isManagedTargetedWriteEnv(args) {
|
|
55
53
|
return !!process.env.EVOLCORE_SESSION_ID
|
|
@@ -368,6 +366,11 @@ async function cmdInfo(args, formatJson) {
|
|
|
368
366
|
const modelId = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
|
|
369
367
|
if (!modelId)
|
|
370
368
|
fail(formatJson, 'MISSING_MODEL_ID', 'info 需要 <model-id>');
|
|
369
|
+
if (isManagedSessionEnv(args)) {
|
|
370
|
+
const out = await runCtlCommand(`/model info ${modelId}`, formatJson);
|
|
371
|
+
printCtlResult(out, formatJson);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
371
374
|
const sel = parseSelector(args, formatJson);
|
|
372
375
|
const self = getArgValue(args, '--self');
|
|
373
376
|
const info = await getModelInfo(modelId, self);
|
|
@@ -587,5 +590,9 @@ export async function cmdModel(args) {
|
|
|
587
590
|
console.log(HELP);
|
|
588
591
|
return;
|
|
589
592
|
}
|
|
593
|
+
if (process.env.EVOLCORE_SESSION_ID
|
|
594
|
+
&& (getArgValue(args, '--self') || getArgValue(args, '--peer') || getArgValue(args, '--role'))) {
|
|
595
|
+
fail(formatJson, 'NOT_ALLOWED', 'Managed model commands cannot select --self, --peer, or --role; they always use the authenticated current relation');
|
|
596
|
+
}
|
|
590
597
|
await dispatch(sub, args, formatJson);
|
|
591
598
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolvePaths } from '../paths.js';
|
|
2
2
|
import { ipcQuery } from '../ipc.js';
|
|
3
|
+
import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
|
|
3
4
|
import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
|
|
4
5
|
export async function cmdQueue(args) {
|
|
5
6
|
if (args.length === 0 || isHelpFlag(args[0]) || wantsHelp(args)) {
|
|
@@ -72,7 +73,16 @@ export async function cmdQueue(args) {
|
|
|
72
73
|
: interrupt ? 'interrupt'
|
|
73
74
|
: undefined;
|
|
74
75
|
const socketPath = resolvePaths().socket;
|
|
75
|
-
const
|
|
76
|
+
const managed = process.env.EVOLCORE_SESSION_ID;
|
|
77
|
+
const result = await ipcQuery(socketPath, managed ? {
|
|
78
|
+
type: 'queue.op',
|
|
79
|
+
agent,
|
|
80
|
+
action,
|
|
81
|
+
messageId: cancelMsgId,
|
|
82
|
+
sessionKey,
|
|
83
|
+
sessionId: managed,
|
|
84
|
+
delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
|
|
85
|
+
} : {
|
|
76
86
|
type: 'queue-snapshot',
|
|
77
87
|
agent,
|
|
78
88
|
action,
|
package/dist/cli/response.js
CHANGED
|
@@ -15,6 +15,9 @@ import { readField, writeField, determineFieldScope, normalizePeer, ModelScopeEr
|
|
|
15
15
|
import { readFieldWithSource, resolveAgentConfig, ConfigTarget } from '../config/config-manager.js';
|
|
16
16
|
import { ResponseModeRegistry } from '../response-system/registry.js';
|
|
17
17
|
import { registerBuiltinModes } from '../response-system/modes/index.js';
|
|
18
|
+
import { ipcQuery } from '../ipc.js';
|
|
19
|
+
import { resolvePaths } from '../paths.js';
|
|
20
|
+
import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
|
|
18
21
|
const RESPONSE_MODE_FIELD = 'responseMode';
|
|
19
22
|
const RESPONSE_MODE_PARAMS_FIELD = 'responseModeParams';
|
|
20
23
|
/**
|
|
@@ -54,6 +57,71 @@ function fail(formatJson, code, message) {
|
|
|
54
57
|
}
|
|
55
58
|
process.exit(1);
|
|
56
59
|
}
|
|
60
|
+
async function managedConfig(fieldArgs, formatJson) {
|
|
61
|
+
const sessionId = process.env.EVOLCORE_SESSION_ID;
|
|
62
|
+
const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
|
|
63
|
+
if (!sessionId)
|
|
64
|
+
fail(formatJson, 'NO_SESSION', 'EVOLCORE_SESSION_ID is not set');
|
|
65
|
+
if (!delegationToken)
|
|
66
|
+
fail(formatJson, 'DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} is not set`);
|
|
67
|
+
const response = await ipcQuery(resolvePaths().socket, {
|
|
68
|
+
type: 'config.op',
|
|
69
|
+
argv: ['config', ...fieldArgs],
|
|
70
|
+
sessionId,
|
|
71
|
+
delegationToken,
|
|
72
|
+
}, 10_000);
|
|
73
|
+
if (!response)
|
|
74
|
+
fail(formatJson, 'DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
|
|
75
|
+
if (!response.ok || !response.result?.ok) {
|
|
76
|
+
fail(formatJson, response.code || 'CONFIG_AUTH_FAILED', response.error || 'response config operation failed');
|
|
77
|
+
}
|
|
78
|
+
return response.result;
|
|
79
|
+
}
|
|
80
|
+
async function runManagedResponse(args, formatJson) {
|
|
81
|
+
if (getArgValue(args, '--self') || getArgValue(args, '--peer')) {
|
|
82
|
+
fail(formatJson, 'NOT_ALLOWED', 'Managed response commands always use the authenticated current relation');
|
|
83
|
+
}
|
|
84
|
+
const sub = args[0];
|
|
85
|
+
if (sub === 'set') {
|
|
86
|
+
const id = args[1];
|
|
87
|
+
if (!id)
|
|
88
|
+
fail(formatJson, 'MISSING_ID', 'set requires a mode id');
|
|
89
|
+
if (!buildRegistry().has(id))
|
|
90
|
+
fail(formatJson, 'UNKNOWN_MODE', `Unknown mode: ${id}`);
|
|
91
|
+
await managedConfig(['set', RESPONSE_MODE_FIELD, id], formatJson);
|
|
92
|
+
return emit(formatJson, { ok: true, scope: 'relation', mode: id }, () => `✓ 已设置响应模式为 ${id}(作用域: relation)`);
|
|
93
|
+
}
|
|
94
|
+
if (sub === 'reset') {
|
|
95
|
+
await managedConfig(['unset', RESPONSE_MODE_FIELD], formatJson);
|
|
96
|
+
return emit(formatJson, { ok: true, scope: 'relation' }, () => '✓ 已清除当前关系的响应模式设置');
|
|
97
|
+
}
|
|
98
|
+
if (sub === 'current') {
|
|
99
|
+
const modeResult = await managedConfig(['get', RESPONSE_MODE_FIELD], formatJson);
|
|
100
|
+
const paramsResult = await managedConfig(['get', RESPONSE_MODE_PARAMS_FIELD], formatJson);
|
|
101
|
+
const configuredMode = typeof modeResult.value === 'string' ? modeResult.value : undefined;
|
|
102
|
+
const mode = configuredMode ?? buildRegistry().getPreferredId();
|
|
103
|
+
return emit(formatJson, {
|
|
104
|
+
ok: true,
|
|
105
|
+
scope: 'relation',
|
|
106
|
+
responseMode: mode ?? null,
|
|
107
|
+
source: configuredMode ? 'config' : 'preferred',
|
|
108
|
+
sourceTarget: modeResult.source?.target ?? null,
|
|
109
|
+
responseModeParams: paramsResult.value ?? null,
|
|
110
|
+
}, () => `当前响应模式: ${mode ?? '(无)'}`);
|
|
111
|
+
}
|
|
112
|
+
if (sub === 'config') {
|
|
113
|
+
if (args[1] === 'set') {
|
|
114
|
+
fail(formatJson, 'NOT_ALLOWED', 'responseModeParams cannot be modified as an opaque object from a managed task');
|
|
115
|
+
}
|
|
116
|
+
const result = await managedConfig(['get', RESPONSE_MODE_PARAMS_FIELD], formatJson);
|
|
117
|
+
const params = result.value ?? {};
|
|
118
|
+
const modeId = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
|
|
119
|
+
if (modeId)
|
|
120
|
+
return emit(formatJson, { ok: true, mode: modeId, config: params[modeId] ?? {} }, () => `${modeId} 配置: ${JSON.stringify(params[modeId] ?? {}, null, 2)}`);
|
|
121
|
+
return emit(formatJson, { ok: true, responseModeParams: params }, () => `所有模式配置: ${JSON.stringify(params, null, 2)}`);
|
|
122
|
+
}
|
|
123
|
+
fail(formatJson, 'NOT_ALLOWED', `response ${sub} is not available in a managed task`);
|
|
124
|
+
}
|
|
57
125
|
/** 解析 --self / --peer 为作用域选择器 */
|
|
58
126
|
function parseSelector(args, formatJson) {
|
|
59
127
|
const self = getArgValue(args, '--self');
|
|
@@ -331,6 +399,9 @@ export async function cmdResponse(args) {
|
|
|
331
399
|
console.log(HELP);
|
|
332
400
|
return;
|
|
333
401
|
}
|
|
402
|
+
if (process.env.EVOLCORE_SESSION_ID && sub !== 'list' && sub !== 'info') {
|
|
403
|
+
return runManagedResponse(args, formatJson);
|
|
404
|
+
}
|
|
334
405
|
switch (sub) {
|
|
335
406
|
case 'list': return cmdList(args, formatJson);
|
|
336
407
|
case 'current': return cmdCurrent(args, formatJson);
|
|
@@ -6,8 +6,7 @@ import { resolvePaths, getPackageRoot, daemonControlDir } from '../paths.js';
|
|
|
6
6
|
import { loadDaemonConfig } from '../config-store.js';
|
|
7
7
|
import * as platform from '../utils/cross-platform.js';
|
|
8
8
|
import { EventBus } from '../core/event-bus.js';
|
|
9
|
-
import { tryUpgrade,
|
|
10
|
-
import { resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG } from '../aun/aid/client.js';
|
|
9
|
+
import { tryUpgrade, tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
|
|
11
10
|
import { isValidAid } from '../aun/aid/index.js';
|
|
12
11
|
import { msgSend } from '../aun/msg/index.js';
|
|
13
12
|
import { scanInstances, cleanupInstances, writeRestartMonitor, removeRestartMonitor, isRestartMonitorWinner, findOrphanProcesses, killOrphans } from '../utils/instance-registry.js';
|
|
@@ -181,21 +180,7 @@ export async function cmdRestartMonitor() {
|
|
|
181
180
|
await notifyDaemonOwners(p, `⚠️ evolcore 升级失败,使用当前版本继续\n${upgrade.error ?? ''}`.trim(), log);
|
|
182
181
|
break;
|
|
183
182
|
}
|
|
184
|
-
//
|
|
185
|
-
const aunUpgrade = await tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG);
|
|
186
|
-
switch (aunUpgrade.status) {
|
|
187
|
-
case 'upgraded':
|
|
188
|
-
log(`✅ AUN SDK upgraded: ${aunUpgrade.from} → ${aunUpgrade.to}`);
|
|
189
|
-
await notifyDaemonOwners(p, `📦 AUN SDK 已升级 ${aunUpgrade.from} → ${aunUpgrade.to}`, log);
|
|
190
|
-
break;
|
|
191
|
-
case 'no-update':
|
|
192
|
-
break;
|
|
193
|
-
case 'failed':
|
|
194
|
-
log(`⚠ AUN SDK upgrade failed (${aunUpgrade.from} → ${aunUpgrade.to}): ${aunUpgrade.error}`);
|
|
195
|
-
await notifyDaemonOwners(p, `⚠️ AUN SDK 升级失败,使用当前版本继续\n${aunUpgrade.error ?? ''}`.trim(), log);
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
// EC Web 版本检查与升级(已安装才检查)
|
|
183
|
+
// EC Web 是独立全局包,已安装时单独检查升级。
|
|
199
184
|
const ecwebUpgrade = await tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME);
|
|
200
185
|
switch (ecwebUpgrade.status) {
|
|
201
186
|
case 'upgraded':
|
|
@@ -392,7 +377,7 @@ async function spawnAndWaitReady(p, log, timeout) {
|
|
|
392
377
|
const out = fs.openSync(stdoutLog, 'a');
|
|
393
378
|
const err = fs.openSync(stdoutLog, 'a');
|
|
394
379
|
const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
|
|
395
|
-
const child = spawn(
|
|
380
|
+
const child = spawn(process.execPath, ['--no-warnings=ExperimentalWarning', appMain], {
|
|
396
381
|
detached: true,
|
|
397
382
|
stdio: ['ignore', out, err],
|
|
398
383
|
windowsHide: true,
|
|
@@ -489,6 +474,7 @@ async function invokeClaude(claudePath, p, attempt, maxAttempts, timeout, log) {
|
|
|
489
474
|
timeout,
|
|
490
475
|
// Windows npm CLIs are .cmd shims; execFile needs a shell to run them.
|
|
491
476
|
shell: platform.isWindows,
|
|
477
|
+
windowsHide: platform.isWindows,
|
|
492
478
|
env: { ...process.env, CLAUDE_CODE_ENTRYPOINT: 'cli' },
|
|
493
479
|
maxBuffer: 10 * 1024 * 1024,
|
|
494
480
|
});
|
|
@@ -31,6 +31,10 @@ export async function cmdTrigger(args) {
|
|
|
31
31
|
}
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
if (process.env.EVOLCORE_SESSION_ID
|
|
35
|
+
&& (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path'))) {
|
|
36
|
+
throw new Error('Managed tasks cannot read trigger definitions, prompts, or scripts from local files; use inline prompt content');
|
|
37
|
+
}
|
|
34
38
|
try {
|
|
35
39
|
switch (sub) {
|
|
36
40
|
case 'list':
|
|
@@ -35,11 +35,15 @@ export function getManagementCommandPermissions(role) {
|
|
|
35
35
|
'role.revoke': { allow: true, scopes: ['agent'] },
|
|
36
36
|
'role.policy.read': { allow: true, scopes: ['agent'] },
|
|
37
37
|
'role.relation.read': { allow: true, scopes: ['agent'] },
|
|
38
|
+
'agent.baseagent.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
38
39
|
'observable.current': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true } },
|
|
40
|
+
'observable.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
39
41
|
'config.get': { allow: true, scopes: ['agent', 'relation'] },
|
|
40
42
|
'config.set': { allow: true, scopes: ['agent', 'relation'] },
|
|
41
43
|
'config.unset': { allow: true, scopes: ['agent', 'relation'] },
|
|
42
44
|
'role.policy.write': { allow: true, dangerous: true, scopes: ['agent'], constraints: { requireAgentAdmin: true } },
|
|
45
|
+
'ec.aid.show': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
46
|
+
'ec.aid.agentmd.put': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
43
47
|
'*': { allow: true },
|
|
44
48
|
// Reload is valid from an Agent's own channel and the daemon control plane.
|
|
45
49
|
// The control-channel gate still requires a daemon owner for cross-Agent reloads.
|
|
@@ -184,17 +184,25 @@ function mutateContactBookPreparedSyncInternal(selfAid, prepare) {
|
|
|
184
184
|
value: prepared.value,
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
-
prepared.beforeCommit?.({
|
|
187
|
+
const preparedChanges = prepared.beforeCommit?.({
|
|
188
188
|
contact: next,
|
|
189
189
|
previousContactRevision: previousRevision,
|
|
190
190
|
contactRevision: nextRevision,
|
|
191
191
|
});
|
|
192
192
|
const before = readTextOrNull(file);
|
|
193
193
|
const after = `${JSON.stringify(next, null, 2)}\n`;
|
|
194
|
-
const
|
|
194
|
+
const additionalChanges = preparedChanges === undefined
|
|
195
|
+
? []
|
|
196
|
+
: Array.isArray(preparedChanges) ? preparedChanges : [preparedChanges];
|
|
197
|
+
const changes = [
|
|
198
|
+
{ file, before, after },
|
|
199
|
+
...additionalChanges,
|
|
200
|
+
];
|
|
201
|
+
validateTransactionChanges(selfAid, changes);
|
|
195
202
|
writeJournal(selfAid, { schemaVersion: 1, state: 'prepared', selfAid, changes });
|
|
196
203
|
try {
|
|
197
|
-
|
|
204
|
+
for (const change of changes)
|
|
205
|
+
applyTransactionChange(change);
|
|
198
206
|
writeJournal(selfAid, { schemaVersion: 1, state: 'committed', selfAid, changes });
|
|
199
207
|
removeJournal(selfAid);
|
|
200
208
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
3
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
5
4
|
import { agentContactOperations } from '../paths.js';
|
|
6
5
|
import { listStaticAgentAdmins, listStaticAgentOwners } from './peer-role-resolver.js';
|
|
@@ -12,6 +11,7 @@ export const CONTACT_REQUEST_MAX_PER_WINDOW = 3;
|
|
|
12
11
|
export const CONTACT_REQUEST_MAX_PENDING = 100;
|
|
13
12
|
export const CONTACT_REQUEST_MAX_RETAINED = 1000;
|
|
14
13
|
export const CONTACT_REQUEST_NOTE_MAX_LENGTH = 500;
|
|
14
|
+
export const CONTACT_REQUEST_ID_MAX_LENGTH = 128;
|
|
15
15
|
export async function mutateContactWithOperation(input) {
|
|
16
16
|
const at = new Date(input.now ?? Date.now()).toISOString();
|
|
17
17
|
const logFile = agentContactOperations(input.selfAid);
|
|
@@ -26,7 +26,7 @@ export async function mutateContactWithOperation(input) {
|
|
|
26
26
|
mutation: input.mutation,
|
|
27
27
|
actor: input.actorId,
|
|
28
28
|
value: undefined,
|
|
29
|
-
beforeCommit: ({ contactRevision: nextRevision }) =>
|
|
29
|
+
beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
|
|
30
30
|
at,
|
|
31
31
|
op: input.operation,
|
|
32
32
|
primaryId: input.primaryId,
|
|
@@ -73,7 +73,17 @@ export async function submitContactRequest(input) {
|
|
|
73
73
|
&& Date.parse(record.at) > windowStart
|
|
74
74
|
&& Date.parse(record.at) <= nowMs)).length;
|
|
75
75
|
if (recentCount >= CONTACT_REQUEST_MAX_PER_WINDOW) {
|
|
76
|
-
return preparedWithExpirations(expiration, {
|
|
76
|
+
return preparedWithExpirations(expiration, {
|
|
77
|
+
code: 'rate-limited',
|
|
78
|
+
...(effectiveEntry?.status === 'pending' && effectiveEntry.pendingRequest
|
|
79
|
+
? {
|
|
80
|
+
requestId: effectiveEntry.pendingRequest.id,
|
|
81
|
+
submittedAt: effectiveEntry.pendingRequest.submittedAt,
|
|
82
|
+
expiresAt: effectiveEntry.pendingRequest.expiresAt,
|
|
83
|
+
}
|
|
84
|
+
: {}),
|
|
85
|
+
contactRevision: '',
|
|
86
|
+
}, logFile);
|
|
77
87
|
}
|
|
78
88
|
const counts = countRequestRecordsAfterExpirations(contact, nowMs);
|
|
79
89
|
const isExistingPending = effectiveEntry?.status === 'pending';
|
|
@@ -103,13 +113,14 @@ export async function submitContactRequest(input) {
|
|
|
103
113
|
mutation: { type: 'batch', mutations },
|
|
104
114
|
actor: applicantAid,
|
|
105
115
|
value,
|
|
106
|
-
beforeCommit: ({ contactRevision }) =>
|
|
116
|
+
beforeCommit: ({ contactRevision }) => operationLogChange(logFile, [
|
|
107
117
|
...expiration.events.map(event => ({ ...event, contactRevision })),
|
|
108
118
|
operationRecord({
|
|
109
119
|
at: nowIso,
|
|
110
120
|
op,
|
|
111
121
|
primaryId: applicantAid,
|
|
112
122
|
requestId,
|
|
123
|
+
expiresAt,
|
|
113
124
|
sourceChannelKey: input.sourceChannelKey,
|
|
114
125
|
...(note ? { note } : {}),
|
|
115
126
|
contactRevision,
|
|
@@ -143,6 +154,10 @@ export async function reviewContactRequest(input) {
|
|
|
143
154
|
if (!entry || entry.status !== 'pending' || entry.pendingRequest?.id !== input.requestId) {
|
|
144
155
|
return preparedNoop({ code: 'request-stale', contactRevision });
|
|
145
156
|
}
|
|
157
|
+
const requestMetadata = {
|
|
158
|
+
requestId: entry.pendingRequest.id,
|
|
159
|
+
expiresAt: entry.pendingRequest.expiresAt,
|
|
160
|
+
};
|
|
146
161
|
if (Date.parse(entry.pendingRequest.expiresAt) <= nowMs) {
|
|
147
162
|
const mutation = {
|
|
148
163
|
type: 'expire-request',
|
|
@@ -153,8 +168,8 @@ export async function reviewContactRequest(input) {
|
|
|
153
168
|
return {
|
|
154
169
|
mutation,
|
|
155
170
|
actor: input.actorId,
|
|
156
|
-
value: { code: 'request-expired', contactRevision },
|
|
157
|
-
beforeCommit: ({ contactRevision: nextRevision }) =>
|
|
171
|
+
value: { code: 'request-expired', contactRevision, ...requestMetadata },
|
|
172
|
+
beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
|
|
158
173
|
at: nowIso,
|
|
159
174
|
op: 'expire',
|
|
160
175
|
primaryId: input.primaryId,
|
|
@@ -177,8 +192,8 @@ export async function reviewContactRequest(input) {
|
|
|
177
192
|
now: nowIso,
|
|
178
193
|
},
|
|
179
194
|
actor: input.actorId,
|
|
180
|
-
value: { code, contactRevision },
|
|
181
|
-
beforeCommit: ({ contactRevision: nextRevision }) =>
|
|
195
|
+
value: { code, contactRevision, ...requestMetadata },
|
|
196
|
+
beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
|
|
182
197
|
at: nowIso,
|
|
183
198
|
op: input.decision === 'approve' ? 'approve' : input.decision === 'reject' ? 'reject' : 'block',
|
|
184
199
|
primaryId: input.primaryId,
|
|
@@ -208,7 +223,7 @@ export async function expirePendingContactRequests(selfAid, now = Date.now()) {
|
|
|
208
223
|
mutation: { type: 'batch', mutations: expiration.mutations },
|
|
209
224
|
actor: 'contact-request-expiry',
|
|
210
225
|
value: expiration.mutations.length,
|
|
211
|
-
beforeCommit: ({ contactRevision }) =>
|
|
226
|
+
beforeCommit: ({ contactRevision }) => operationLogChange(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
|
|
212
227
|
};
|
|
213
228
|
});
|
|
214
229
|
return result.value;
|
|
@@ -226,6 +241,91 @@ export async function listPendingContactRequests(selfAid, now = Date.now()) {
|
|
|
226
241
|
}))));
|
|
227
242
|
return result.value;
|
|
228
243
|
}
|
|
244
|
+
/** Return only the authenticated applicant's own admission/request state. */
|
|
245
|
+
export async function queryOwnContactRequestStatus(input) {
|
|
246
|
+
const applicantAid = String(input.applicantAid || '').trim();
|
|
247
|
+
if (!isValidAid(applicantAid))
|
|
248
|
+
return protocolResult('NOT_ALLOWED');
|
|
249
|
+
const now = input.now ?? Date.now();
|
|
250
|
+
const rawRequestId = input.requestId;
|
|
251
|
+
if (rawRequestId !== undefined && typeof rawRequestId !== 'string')
|
|
252
|
+
return protocolResult('NOT_ALLOWED');
|
|
253
|
+
const requestedRequestId = rawRequestId?.trim();
|
|
254
|
+
if (requestedRequestId !== undefined
|
|
255
|
+
&& (!requestedRequestId || requestedRequestId.length > CONTACT_REQUEST_ID_MAX_LENGTH)) {
|
|
256
|
+
return protocolResult('NOT_ALLOWED');
|
|
257
|
+
}
|
|
258
|
+
const owners = listAgentOwners(input.selfAid);
|
|
259
|
+
const result = await mutateContactBookPrepared(input.selfAid, ({ contact }) => {
|
|
260
|
+
const logFile = agentContactOperations(input.selfAid);
|
|
261
|
+
const expiration = collectExpiredMutations(contact, now);
|
|
262
|
+
const entry = effectiveEntryAfterExpirations(contact.contacts[applicantAid], now);
|
|
263
|
+
const operations = readOperationLog(logFile).filter(record => record.primaryId === applicantAid);
|
|
264
|
+
const latestRequestOperation = [...operations].reverse().find(record => (!!record.requestId
|
|
265
|
+
&& (record.op === 'request' || record.op === 'resubmit' || record.op === 'approve'
|
|
266
|
+
|| record.op === 'reject' || record.op === 'expire' || record.op === 'block')));
|
|
267
|
+
if (requestedRequestId !== undefined && latestRequestOperation?.requestId !== requestedRequestId) {
|
|
268
|
+
return preparedNoop(protocolResult('NOT_ALLOWED'));
|
|
269
|
+
}
|
|
270
|
+
const requestMetadata = requestMetadataFor(operations, latestRequestOperation?.requestId);
|
|
271
|
+
let value;
|
|
272
|
+
if (owners.includes(applicantAid)
|
|
273
|
+
|| (entry && (entry.status === undefined || entry.status === 'active'))) {
|
|
274
|
+
value = protocolResult('APPROVED', requestMetadata);
|
|
275
|
+
}
|
|
276
|
+
else if (entry?.status === 'blocked') {
|
|
277
|
+
value = protocolResult('NOT_ALLOWED');
|
|
278
|
+
}
|
|
279
|
+
else if (entry?.status === 'pending' && entry.pendingRequest) {
|
|
280
|
+
value = protocolResult('PENDING', {
|
|
281
|
+
requestId: entry.pendingRequest.id,
|
|
282
|
+
expiresAt: entry.pendingRequest.expiresAt,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (entry?.status === 'declined') {
|
|
286
|
+
value = protocolResult('EXPIRED', {
|
|
287
|
+
requestId: latestRequestOperation?.op === 'expire' ? latestRequestOperation.requestId : requestMetadata.requestId,
|
|
288
|
+
expiresAt: entry.declinedAt ?? requestMetadata.expiresAt,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else if (latestRequestOperation?.op === 'reject') {
|
|
292
|
+
value = protocolResult('REJECTED', requestMetadata);
|
|
293
|
+
}
|
|
294
|
+
else if (latestRequestOperation?.op === 'expire') {
|
|
295
|
+
value = protocolResult('EXPIRED', requestMetadata);
|
|
296
|
+
}
|
|
297
|
+
else if (owners.length === 0) {
|
|
298
|
+
value = protocolResult('NOT_ALLOWED');
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
value = protocolResult('CONTACT_REQUIRED');
|
|
302
|
+
}
|
|
303
|
+
if (expiration.mutations.length === 0)
|
|
304
|
+
return preparedNoop(value);
|
|
305
|
+
return {
|
|
306
|
+
mutation: { type: 'batch', mutations: expiration.mutations },
|
|
307
|
+
actor: 'contact-request-expiry',
|
|
308
|
+
value,
|
|
309
|
+
beforeCommit: ({ contactRevision }) => operationLogChange(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
return result.value;
|
|
313
|
+
}
|
|
314
|
+
export function contactRequestSubmissionProtocolResult(result) {
|
|
315
|
+
const metadata = { requestId: result.requestId, expiresAt: result.expiresAt };
|
|
316
|
+
switch (result.code) {
|
|
317
|
+
case 'submitted':
|
|
318
|
+
case 'resubmitted':
|
|
319
|
+
return protocolResult('SUBMITTED', metadata);
|
|
320
|
+
case 'already-contact':
|
|
321
|
+
case 'owner':
|
|
322
|
+
return protocolResult('APPROVED', metadata);
|
|
323
|
+
case 'rate-limited':
|
|
324
|
+
return protocolResult('RATE_LIMITED', metadata);
|
|
325
|
+
default:
|
|
326
|
+
return protocolResult('NOT_ALLOWED', metadata);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
229
329
|
export function findContactRequestNote(selfAid, requestId) {
|
|
230
330
|
const records = readOperationLog(agentContactOperations(selfAid));
|
|
231
331
|
return [...records].reverse().find(record => (record.requestId === requestId
|
|
@@ -237,6 +337,24 @@ function normalizeNote(value) {
|
|
|
237
337
|
return null;
|
|
238
338
|
return note;
|
|
239
339
|
}
|
|
340
|
+
function requestMetadataFor(operations, requestId) {
|
|
341
|
+
if (!requestId)
|
|
342
|
+
return {};
|
|
343
|
+
const submitted = [...operations].reverse().find(record => (record.requestId === requestId && (record.op === 'request' || record.op === 'resubmit')));
|
|
344
|
+
const submittedAt = submitted ? Date.parse(submitted.at) : Number.NaN;
|
|
345
|
+
return {
|
|
346
|
+
requestId,
|
|
347
|
+
expiresAt: submitted?.expiresAt
|
|
348
|
+
?? (Number.isFinite(submittedAt) ? new Date(submittedAt + CONTACT_REQUEST_TTL_MS).toISOString() : undefined),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function protocolResult(status, metadata = {}) {
|
|
352
|
+
return {
|
|
353
|
+
status,
|
|
354
|
+
requestId: metadata.requestId ?? null,
|
|
355
|
+
expiresAt: metadata.expiresAt ?? null,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
240
358
|
function collectExpiredMutations(book, now) {
|
|
241
359
|
const mutations = [];
|
|
242
360
|
const events = [];
|
|
@@ -286,7 +404,7 @@ function preparedWithExpirations(expiration, value, logFile) {
|
|
|
286
404
|
mutation: { type: 'batch', mutations: expiration.mutations },
|
|
287
405
|
actor: 'contact-request-expiry',
|
|
288
406
|
value,
|
|
289
|
-
beforeCommit: ({ contactRevision }) =>
|
|
407
|
+
beforeCommit: ({ contactRevision }) => operationLogChange(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
|
|
290
408
|
};
|
|
291
409
|
}
|
|
292
410
|
function preparedNoop(value) {
|
|
@@ -310,7 +428,14 @@ function readOperationLog(file) {
|
|
|
310
428
|
const record = JSON.parse(line);
|
|
311
429
|
if (!record || typeof record !== 'object' || typeof record.at !== 'string'
|
|
312
430
|
|| !Number.isFinite(Date.parse(record.at)) || typeof record.op !== 'string'
|
|
313
|
-
||
|
|
431
|
+
|| !['request', 'resubmit', 'approve', 'reject', 'expire', 'block', 'unblock', 'manual-add'].includes(record.op)
|
|
432
|
+
|| typeof record.primaryId !== 'string' || !isValidAid(record.primaryId)
|
|
433
|
+
|| (record.requestId !== undefined && typeof record.requestId !== 'string')
|
|
434
|
+
|| (record.expiresAt !== undefined && (!isValidIsoDate(record.expiresAt)))
|
|
435
|
+
|| (record.actorId !== undefined && typeof record.actorId !== 'string')
|
|
436
|
+
|| (record.sourceChannelKey !== undefined && typeof record.sourceChannelKey !== 'string')
|
|
437
|
+
|| (record.note !== undefined && (typeof record.note !== 'string' || record.note.length > CONTACT_REQUEST_NOTE_MAX_LENGTH))
|
|
438
|
+
|| typeof record.contactRevision !== 'string')
|
|
314
439
|
throw new Error('invalid record');
|
|
315
440
|
records.push(record);
|
|
316
441
|
}
|
|
@@ -320,11 +445,32 @@ function readOperationLog(file) {
|
|
|
320
445
|
}
|
|
321
446
|
return records;
|
|
322
447
|
}
|
|
323
|
-
function
|
|
448
|
+
function isValidIsoDate(value) {
|
|
449
|
+
return Number.isFinite(Date.parse(value));
|
|
450
|
+
}
|
|
451
|
+
function operationLogChange(file, records) {
|
|
324
452
|
if (records.length === 0)
|
|
325
|
-
return;
|
|
326
|
-
|
|
327
|
-
|
|
453
|
+
return undefined;
|
|
454
|
+
// Validate the existing log while the Contact Book lock is held. The log is
|
|
455
|
+
// part of the same journal transaction as contact.json, so a malformed log
|
|
456
|
+
// must fail closed before either file is changed.
|
|
457
|
+
readOperationLog(file);
|
|
458
|
+
let before;
|
|
459
|
+
try {
|
|
460
|
+
before = fs.readFileSync(file, 'utf8');
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
if (error?.code === 'ENOENT')
|
|
464
|
+
before = null;
|
|
465
|
+
else
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
const prefix = before && !before.endsWith('\n') ? `${before}\n` : (before ?? '');
|
|
469
|
+
return {
|
|
470
|
+
file,
|
|
471
|
+
before,
|
|
472
|
+
after: `${prefix}${records.map(record => JSON.stringify(record)).join('\n')}\n`,
|
|
473
|
+
};
|
|
328
474
|
}
|
|
329
475
|
function operationRecord(input) {
|
|
330
476
|
return { id: `evt_${crypto.randomUUID().replace(/-/g, '')}`, ...input };
|